4

I was going trough some code, and I came across the following function:

def foo(self, arg1=1, *, arg2=2):
    pass

I was surprised to see keyword arguments on the left side of the *, the positional arguments. I notice then that I can call foo in both of the following ways:

>>> foo(1)
>>> foo(arg1=1)

I think I would be expecting the second call to fail as I am calling foo using a named argument by providing the keyword arg1.

With that said, am I using positional arguments in both scenarios, or is the second call to foo a named argument?

renatodamas
  • 16,555
  • 8
  • 30
  • 51

4 Answers4

8

The best sentence that I found that best describes this is:

"The trick here is to realize that a “keyword argument” is a concept of the call site, not the declaration. But a “keyword only argument” is a concept of the declaration, not the call site."

Below is a concise explanation copied from here in case the link dies at some point.

def bar(a,    # <- this parameter is a normal python parameter
        b=1,  # <- this is a parameter with a default value
        *,    # <- all parameters after this are keyword only
        c=2,  # <- keyword only argument with default value
        d):   # <- keyword only argument without default value
    pass
renatodamas
  • 16,555
  • 8
  • 30
  • 51
6

The arg1 argument is allowed to be called as either a positional argument, or a keyword argument.

As of Python 3.8, it is possible to specify some arguments as positional only. See PEP 570. Prior to 3.8, this isn't possible unless you write a python C extension.

The 3.8 syntax looks like this (directly from the PEP):

def name(positional_only_parameters, /, positional_or_keyword_parameters,
         *, keyword_only_parameters): ...

...prior to 3.8, the only legal syntax is this:

def name(positional_or_keyword_parameters, *, keyword_only_parameters): ...
Rick
  • 43,029
  • 15
  • 76
  • 119
0

Despite the similarity in syntax, there is no relationship between keyword arguments and parameters with default values.

key=value is used both to assign a default value to a parameter when the function is defined, and to pass an argument for a parameter when the function is called.

A parameter without a default value can be assigned using a keyword argument:

def foo(a):
    return a + 3

assert foo(a=5) == 8

A parameter with a default value can be assigned without a keyword argument:

def foo(a=5):
    return a + 3

assert foo() == 8
assert foo(1) == 4
chepner
  • 497,756
  • 71
  • 530
  • 681
0

Is a complex answer by using the * like the argument ( the Python version, define and use it ).

In your case:

>>> foo(1,2,)
>>> foo(1,2)

Note that: Try to use this and see the difference ( without self ):

>>> def foo( arg1=1, *, arg2=2):
...     pass
...
>>> foo(arg1=1,arg2=2)
>>> def foo( arg1=1, *, arg2=2):
...     pass
...
>>> foo(arg1=1,arg2=2)
>>> foo(arg1=1)
>>> foo(arg1=1,)
>>> foo(arg2=2)
>>> foo(arg2=2,1)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> foo(2,1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes from 0 to 1 positional arguments but 2 were given
>>> foo(arg1=1,2)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
  • One star * defines positional arguments (you can receive any number of arguments and treat the passed arguments as a tuple);
  • Two stars ** define keywords arguments.

A good example comes from this tutorial (the keyword-only arguments without positional arguments part):

def with_previous(iterable, *, fillvalue=None):
    """Yield each iterable item along with the item before it."""
    previous = fillvalue
    for item in iterable:
        yield previous, item
        previous = item

>>> list(with_previous([2, 1, 3], fillvalue=0))
[(0, 2), (2, 1), (1, 3)]

That its value could be sent as positional arguments.

See this tutorial for more info about arguments.

Python 3.x introduces more intuitive keyword-only arguments with PEP-3102 (you can specify * in the argument list).

One good alternative answer is this: