Holden Karau created SPARK-59069:
------------------------------------

             Summary: Transpile Python UDFs with explicit positional-only 
parameters
                 Key: SPARK-59069
                 URL: https://issues.apache.org/jira/browse/SPARK-59069
             Project: Spark
          Issue Type: Sub-task
          Components: PySpark
    Affects Versions: 4.4.0
            Reporter: Holden Karau


The transpiler refuses a UDF with positional-only parameters outright:

```python
def f(a, b, /):
    return a + b
```

falls back to interpreted Python, alongside defaults, `*args`, `**kwargs` and
keyword-only parameters. For those four the refusal is necessary: the transpiled
expression references its inputs positionally through `_udf_param_N`, and a 
call site
that omits a defaulted argument, or passes one by keyword only, leaves a 
placeholder
pointing at a position nothing bound.

A positional-only parameter has no such gap. It is *always* bound by position 
-- that is
what positional-only means -- so the placeholder scheme fits it exactly. The 
refusal was
over-broad, and it came about only because `_get_parameter_list` read
`node.args.args` and never `node.args.posonlyargs`, so the parameter list would 
simply
have been short by however many positional-only parameters the function 
declared. Refusing
was the safe response to that, not a statement about the feature.

Two parts:

**1. Lower them.** Read `posonlyargs + args` everywhere the positional 
parameter list is
derived -- `_get_parameter_list`, `_param_category_combos` (so each parameter's
input-type category lands in the right slot), and the located-versus-held lambda
parameter comparison in `_get_function_from_ast`. A shared `_positional_args` 
helper
keeps the concatenation in one place. Drop `posonlyargs` from the refusal list; 
a
*defaulted* positional-only parameter still hits the `defaults` check, as it 
must.

**2. Do not resolve a positional-only kwarg to a slot.** 
`UserDefinedFunction.__call__`
rewrites user kwargs to positional order, so that a transpiled `_udf_param_N` 
expression
sees plain column references rather than a `NamedArgumentExpression` (which 
breaks nested
calls such as `isnotnull`). Once part 1 makes these UDFs candidates, that 
rewrite would
happily resolve `f(a=col)` for a positional-only `a` -- silently turning a call 
**Python
itself rejects** into a valid one:

```python
def f(a, /):
    return a + 1

f(a=1)            # TypeError: f() got some positional-only arguments passed as 
keyword arguments
udf_f(a=col)      # would have succeeded, with transpilation on
```

So track which public parameters are positional-only and exclude them from the 
rewrite.
Left unresolved, the kwarg reaches the JVM as a `NamedArgumentExpression`, 
which drops the
transpiled expression and routes to the interpreted path -- where the worker's 
own keyword
call raises the same `TypeError` Python would. The fallback does the right 
thing; it just
has to be allowed to happen.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to