28

I've seen this SO question (this is not a duplicate): Python bare asterisk in function argument

In python-3.x you can add a bare * to the function arguments, this means that (quote from docs):

Parameters after “*” or “*identifier” are keyword-only parameters and may only be passed used keyword arguments.

Ok, so, I've defined a function:

>>> def f(a, b, *, c=1, d=2, e=3):
...     print('Hello, world!')
... 

I can pass c, d and e variable values only by specifying keywords:

>>> f(1, 2, 10, 20, 30)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 5 were given
>>> f(1, 2, c=10, d=20, e=30)
Hello, world!

Questions are:

  • What is the motivation for this kind of restriction/syntax sugar?
  • What use cases does it cover?
  • Is it really used in third-party libraries that switched to python3?

Some "real-world" examples would help a lot. Thanks in advance.

miken32
  • 42,008
  • 16
  • 111
  • 154
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • 2
    It's good for cases that need the named argument to enhance readability, e.g. a window: `Window(width = 800, height = 600, fullscreen = true)` – Rapptz Apr 13 '14 at 03:30
  • 4
    [PEP 3102](http://legacy.python.org/dev/peps/pep-3102/) explains the rationale pretty clearly. – BrenBarn Apr 13 '14 at 03:34
  • @BrenBarn thank you for the link, it helps with the "motivation" part a lot. Real-world examples and use cases would complete the topic. – alecxe Apr 13 '14 at 03:50
  • 1
    As far as i know, [`curio`](https://github.com/dabeaz/curio) is using this feature in its `new_tasks` function – andy Nov 25 '15 at 02:01
  • The reply posted here gives an intuitive explanation: https://stackoverflow.com/a/39284225/1436851 – Antoni Jan 22 '19 at 05:24
  • The explanation posted [here](https://stackoverflow.com/a/39284225/1436851) gives also an intuitive explanation – Antoni Jan 22 '19 at 05:25

2 Answers2

28

PEP 3102 explains the rationale pretty clearly: the point is to allow functions to accept various "options" that are essentially orthogonal in nature. Specifying these positionally is awkward both on the defining and calling side, since they don't have any obvious "priority" that would translate into a positional order.

There are lots of example of functions that would benefit from this in various libraries. For instance, the call signature of pandas.read_csv is:

def parser_f(filepath_or_buffer,
                 sep=sep,
                 dialect=None,
                 compression=None,

                 doublequote=True,
                 escapechar=None,
                 quotechar='"',
                 quoting=csv.QUOTE_MINIMAL,
                 skipinitialspace=False,
                 lineterminator=None,

                 header='infer',
                 index_col=None,
                 names=None,
                 prefix=None,
                 skiprows=None,
                 skipfooter=None,
                 skip_footer=0,
                 na_values=None,
                 na_fvalues=None,
                 true_values=None,
                 false_values=None,
                 delimiter=None,
                 converters=None,
                 dtype=None,
                 usecols=None,

                 engine='c',
                 delim_whitespace=False,
                 as_recarray=False,
                 na_filter=True,
                 compact_ints=False,
                 use_unsigned=False,
                 low_memory=_c_parser_defaults['low_memory'],
                 buffer_lines=None,
                 warn_bad_lines=True,
                 error_bad_lines=True,

                 keep_default_na=True,
                 thousands=None,
                 comment=None,
                 decimal=b'.',

                 parse_dates=False,
                 keep_date_col=False,
                 dayfirst=False,
                 date_parser=None,

                 memory_map=False,
                 nrows=None,
                 iterator=False,
                 chunksize=None,

                 verbose=False,
                 encoding=None,
                 squeeze=False,
                 mangle_dupe_cols=True,
                 tupleize_cols=False,
                 infer_datetime_format=False):

Except for the filepath, most of these are orthogonal options that specify different aspects of how a CSV file is to be parsed. There's no particular reason why they would be passed in any particular order. You'd go nuts keeping track of any positional order for these. It makes more sense to pass them as keywords.

Now, you can see that pandas doesn't actually define them as keyword-only arguments, presumably to maintain compatibility with Python 2. I would imagine that many libraries have refrained from using the syntax for the same reason. I don't know offhand which libraries (if any) have started using it.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 5
    A reason to force them to be keyword arguments is so that people don't rely on the order, and thus you can change it in the future, or add new keywords, (ex.: `ignore_comments='#'`), that logically would be next to `lineterminator`. – Davidmh Apr 25 '14 at 07:40
  • 2
    Wow, that function takes way too many parameters. Something must clearly be wrong designed. –  Apr 29 '14 at 14:13
  • This function is a great example how to not write code. – Wodzu Dec 17 '20 at 12:39
  • @Wodzu How to better write it? – P i Aug 21 '21 at 18:27
4

For those who are coming from or/and used ruby

Below expression in python

def f(a, b, *, c=1, d=2, e=3):

is similar to

def f(a,b, options={})
  c = options[:c] || 1
  d = options[:d] || 2
  e = options[:e] || 3
end

in ruby.

Since, python is explicit is better than implicit langauge, it requires * (splat) operator in parameters.

PS: I never used python, if i am mistaken please correct me.

Community
  • 1
  • 1
Paritosh Piplewar
  • 7,982
  • 5
  • 26
  • 41