python / cpython

The Python programming language

Home Page:https://www.python.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Disallow iterable argument unpacking after a keyword argument?

brandtbucher opened this issue · comments

BPO 38560
Nosy @gvanrossum, @rhettinger, @MojoVampire, @brandtbucher

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = <Date 2019-10-29.17:28:43.417>
created_at = <Date 2019-10-23.00:20:42.309>
labels = ['interpreter-core', 'type-bug', 'invalid', '3.9']
title = 'Disallow iterable argument unpacking after a keyword argument?'
updated_at = <Date 2019-10-29.17:28:43.413>
user = 'https://github.com/brandtbucher'

bugs.python.org fields:

activity = <Date 2019-10-29.17:28:43.413>
actor = 'gvanrossum'
assignee = 'none'
closed = True
closed_date = <Date 2019-10-29.17:28:43.417>
closer = 'gvanrossum'
components = ['Interpreter Core']
creation = <Date 2019-10-23.00:20:42.309>
creator = 'brandtbucher'
dependencies = []
files = []
hgrepos = []
issue_num = 38560
keywords = []
message_count = 6.0
messages = ['355195', '355251', '355267', '355658', '355660', '355663']
nosy_count = 4.0
nosy_names = ['gvanrossum', 'rhettinger', 'josh.r', 'brandtbucher']
pr_nums = []
priority = 'normal'
resolution = 'not a bug'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue38560'
versions = ['Python 3.9']

Calls of the form f(name=value, *args) are currently legal syntax. The resulting argument binding is awkward, and almost never does what you want/expect it to:

>>> def f(x, y, z):
...     print(x, y, z)
... 

>>> f(x=0, *(1, 2))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for argument 'x'

>>> f(y=0, *(1, 2))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for argument 'y'

>>> f(z=0, *(1, 2))
1 2 0

I'm not sure if this is intentional, or an oversight. Every other way of passing positional arguments after keyword arguments results in an error:

f(kwarg=kwarg, arg)  # SyntaxError: positional argument follows keyword argument
f(**kwargs, arg)     # SyntaxError: positional argument follows keyword argument unpacking
f(**kwargs, *args)   # SyntaxError: iterable argument unpacking follows keyword argument unpacking

I think this case should raise a "SyntaxError: iterable argument unpacking follows keyword argument".

I'd like to work on this if we believe it should be changed.

I'd be +1 on this, but I'm worried about existing code relying on the functional use case from your example.

If we are going to discourage it, I think we either have to:

  1. Have DeprecationWarning that turns into a SyntaxError, or
  2. Never truly remove it, but make it a SyntaxWarning immediately and leave it that way indefinitely

I've found one occurrence of this in the CPython codebase, in test_ast.py. Basically it makes sure that the following expression parses and compiles correctly:

f(1,2,c=3,*d,**e)

I doubt that this is to protect against regressions in this specific syntax. More likely it's just trying to create as many of the different argument passing AST nodes as possible in one call (it's the only test for function calls with arguments). It can probably be slightly refactored:

f(1,2,*c,d=3,**e)

I’ve thought about it and I’m +1 on DeprecationWarning in 3.9 and SyntaxError in 3.10.

Removing any type of legal function call is tricky, but this is such obscure, sneaky syntax that it’s likely an accident/bug if it does pop up (that’s how I discovered it). And every instance can be trivially refactored.

Changes to the grammar of the language need to be discussed on python-dev (especially ones that can break existing code and ones that change syntax that has worked for many years). Arguably, this is something that should just go into a lint tool.

Well, as you point out, f(z=0, *(1, 2)) is legal, and the parser doesn't know what the names of the keyword arguments are. SO this cannot be changed.

nice