What is the significance of a single, lone asterisk in a functions documentation?

Hello all,

I keep coming across this and my Google Fu (actually DuckDuckGo Judo) cannot find the answer; what is the significance of a single, lone asterisk in the local parameter list of a functions documentation?

For example…

os.mkdir(path, mode=0o777, *, dir_fd=None)

…to be clear I’m not asking about *args or **kwargs, but simply ‘*’.

Thanks in advance!

This is a marker that all later arguments must be passed by keyword. To expand your example:

>>> os.open('existing_dir', os.O_DIRECTORY)
3
>>> os.listdir(3)
[]
>>> os.mkdir('some_dir', 0o644, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: mkdir() takes at most 2 positional arguments (3 given)
>>> os.mkdir('some_dir', 0o644, dir_fd=3)
>>> os.listdir(3)
['some_dir']

Similarly, you may run into a lone / in some signatures. This notes that all previous arguments must be passed positionally, not by keyword. This one is much newer and rarer; I don’t have a great example ready for you off the top of my head :slight_smile:

4 Likes

Consider the following function signature: func(foo, bar='spam', *args, baz='eggs', **kwargs). That should help you to understand the case of the lone asterisk. If you take away the parameter name args, leaving func(foo, bar='spam', *, baz='eggs', **kwargs), then there’s nothing into which the * operator can pack additional positional arguments. A call with more than the given positional arguments will thus raise a TypeError, which is intentionally supported by the compiler. OTOH, any number of keyword arguments can be passed.

2 Likes

Thanks to @zware and @eryksun, so from your replies I understand that an asterisk in a function signature precedes the name of a local variable into which a tuple containing any additional positional arguments will be placed - effectively allowing an arbitrary number of positional arguments to be passed into the function.

However, if the asterisk is not followed by a variable name and appears on it’s own, this explicitly states that there is nothing into which any extra positional arguments can be packed and thus the implication is that all subsequent arguments must be keyword arguments.

I hope that I’ve got that right.

That sounds right to me. Also, as Zachary mentioned, there’s also newer support for indicating position-only arguments via /. For example, for the signature func(foo, bar, /, baz='spam', *, qux='eggs', **kwargs), the parameters foo and bar cannot be passed as keyword arguments.

2 Likes

Hello, sorry to reincarnate the thread, but I’ve been coming back to this of late and something occurred to me that I wondered if you could shed some light on…

What is the point in using a / to denote that all previous arguments must be passed positionally? Why does it matter what syntax the caller uses to define a particular parameter? for example…

def func(a, b, /):
    pass

func('foo', 'bar')
func('foo', b='bar')
func(a='foo', b='bar')

Practically they all seem the same to me, however only the first one is valid. Why though? I can’t think of why it would matter to the function itself whether it was passed an argument as 'foo' or a='foo' , because inside the function the string 'foo' just gets stored in the variable a.

To be clear, I understand why one can’t place default parameters before non-default parameters in the function definition, as there is a practical reason for that.

I think I understand why all the arguments that follow *args must be keyword arguments (because if **kwargs is included in the function definition, any surplus positional arguments after *args would not have a parameter name associated with them and consequently there would be no key associated with them in the dictionary stored in kwargs.)

the / however I just can’t fathom the reason for…

class A:
    def do_something_with(self, some_arg):
        pass

    def method(self, /, **kwargs):
        self.do_something_with(kwargs['self'])

Without /, the only way to do the above is something like

class A:
    def do_something_with(self, some_arg):
        pass

    def method(_hope_this_does_not_conflict, **kwargs):
        _hope_this_does_not_conflict.do_something_with(kwargs['self'])

or


class A:
    def do_something_with(self, some_arg):
        pass

    def method(*args, **kwargs):
        if len(args) != 1:
            raise TypeError('something about wrong args')
        args[0].do_something_with(kwargs['self'])

This is obviously very much a contrived example and doesn’t actually make a whole lot of sense as something you would actually do, but I hope it at least illustrates the idea :slight_smile:

For some history, / was originally introduced to be able to represent the signatures of some functions implemented in C which take positional-only arguments. It was later recognized to be a useful-enough addition to make it legal Python syntax.

1 Like

Thanks!

I don’t fully understand the example because I’ve not done much work with classes but I’ll be working more with them soon so I’ll come back here in the near future…

The python documentation has an index. The first entry is for Symbols, such as ‘*’. Under *, there are 11 entries, the first being function definition, which leads here. Learning to use this and the rest of the index should help you greatly.

1 Like