简体   繁体   中英

with `*args`, keyword arguments become positional?

Example, say I define a function

def myfunc(a=1, *args):
    print(a)
    # *args have some other functionality, for example passed to a class instance, or whatever

Problem is: when calling myfunc , I can't pass the expected positional arguments for *args first, then pass keyword argument a . For example, if the expected *args contain just a number, I can't do

myfunc(3, a=4)

This would raise an error, stating multiple assignments received for a. I can't do myfunc(a=4, b=5, 3) obviously. I can only do

myfunc(4, 3)

So now it seems, with the *args , the keyword argument a really becomes positional?

The example here is a function, but it also applies to classes. Say a subclass with __init__(a=3, *args) wants some keyword argument a and some other 99 required arguments to pass to super. I wouldn't want to repeat the 99 arguments definition in the subclass, but rather do *args and pass that to super. But now the keyword argument a becomes positional.

This class and subclass example is my real problem in my coding. I want a subclass with an optional, keyword argument a , and then 99 required arguments for its super. I don't see a good, clean way around this problem.

I can't be the first to bump into this, but I couldn't find any answer.

a isn't positional-only:

>>> import inspect
>>> def myfunc(a=1, *args):
...     print(a)
>>> inspect.signature(myfunc).parameters['a'].kind
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>

And indeed it can be passed in as keyword-argument:

>>> myfunc(a=4)
4

However, as you noted, as soon as you want to provide subsequent positional arguments, you have to provide it as positional parameter. But that has nothing to do with *args , that's just how positional arguments work:

>>> def myfunc2(a, b):
...     print(a)
>>> myfunc2(1, a=2)
TypeError: myfunc2() got multiple values for argument 'a'

It either has to be the first of all positional arguments or all arguments have to be passed by name.

However *args complicates the situation somewhat, because it only collects positional arguments. That means that if it should collect anything you also have to pass in all previous parameters by position.

But in case you want to roll your own special case you could accept just *args, **kwargs and first check if it's passed as keyword (named) argument or, if not, as first positional argument:

>>> def myfunc3(*args, **kwargs):
...     if 'a' in kwargs:
...         a = kwargs.pop('a')
...     else:
...         a = args[0]
...         args = args[1:]
...     print(a, args, kwargs)
>>> myfunc3(1, a=2)
2 (1,) {}
>>> myfunc3(2, 1)
2 (1,) {}

That should give you the results you expected but you need to be explicit in the documentation how the argument should be passed in because it somewhat breaks the semantics of Pythons signature model.

Python 3 introduced keyword-only arguments: https://www.python.org/dev/peps/pep-3102/

So you can do:

def myfunc(*args, a=1):
    print(a)

And then myfunc(3, a=4) would work fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM