says
says

Reputation: 119

Positional argument after keyword argument and regular parameter after * parameter

I have to use Python 2.7 and I'm running into an issue that I wasn't having in Python 3.

def foo(*dirs, arg2, arg3=None, arg4=None, arg5=False, arg6=False):

I'd call the function like so:

foo('folder_name', arg2=100, arg3=['string', 'string2'], arg4='test6', arg5=True, arg6=False)

But I get an error message saying:

                     def foo(*dirs, arg2, arg3=None, arg4=None, arg5=False, arg6=False):
                                       ^
SyntaxError: invalid syntax

In the definition of the function I can see it says regular parameter after * parameter. I did some research and can see *dirs has to be the last parameters specified.

So I can change the function parameters to this:

def foo(arg2, arg3=None, arg4=None, arg5=False, arg6=False, *dirs):

However, when I do this I then get an error message saying: SyntaxError: non-keyword arg after keyword arg

I'm not sure how to resolve this issue. Does anyone know a way to resolve this? Any help would be much appreciated!

Upvotes: 3

Views: 21105

Answers (2)

adatzer
adatzer

Reputation: 576

If the dirs parameter specifies one argument (positional or keyword) for foo, e.g. one string or one list of strings, in other words IF you intend to call foo like:

foo("folder_name", other args)

or

foo(["folder_name1", "folder_name2, ...], other args)

then you don't need to prepend its name with *, and the "workaround" of your answer is actually the way to go. See also: https://docs.python.org/2/tutorial/controlflow.html#more-on-defining-functions .

But if dirs specifies an arbitrary sequence of arguments and you want to be able to do something like:

foo(other args, "folder_name1", "folder_name2",...)

there is a difference between python 2 and python 3, as you describe in your question. As you can see https://docs.python.org/2/glossary.html#term-parameter versus https://docs.python.org/3/glossary.html#term-parameter , in Python3 there are keyword-only parameters:

Keyword-only parameters can be defined by including a single var-positional parameter or bare * in the parameter list of the function definition before them

That's why in Python3 you didn't have problem defining foo as:

def foo(*dirs, arg2, arg3=None, arg4=None, arg5=False, arg6=False):

and calling it like:

foo('folder_name', arg2=100, arg3=['string', 'string2'], arg4='test6', arg5=True, arg6=False)

Notice, that in the call, you correctly provided all args after "folder_name" (even arg2) as keyword arguments, and so worked in Python3.

But in Python2, there are no keyword-only parameters. So, as you mention, you cannot have neither regular parameter after * parameter nor non-keyword arg after keyword arg. That's why even if you leave *dirs last in the parameter list, you cannot call foo supplying the previous arguments as keyword ones. What you could do, though, is to still leave *dirs last in the parameter list, and in the call to foo supply the previous args as positional. Example (python 2.7):

def bar(a=1, b=2, *c):   # see below
    return (a, b, c)
bar(1, 2, 3)      # => (1, 2, (3,))
bar(1, b=3, 3)    # => Error: non-keyword arg after keyword arg
bar(1, 2)         # => (1 2 ())
bar(1)            # => Error: bar() takes at least 2 arguments (1 given)

So, as you can see, even though it lets you define bar with default values for a, b (see positional-or-keyword in links above), they are not used, i.e. a and b are positional (and required).

Lastly, also check these links from python2 documentation: https://docs.python.org/2/tutorial/controlflow.html#unpacking-argument-lists and https://docs.python.org/2/faq/programming.html#what-is-the-difference-between-arguments-and-parameters

Upvotes: 1

says
says

Reputation: 119

I found a work around, but am open to other ideas if anyone has any.

I changed *dirs in the function definition to a keyword argument dirs=None then when I call foo(dirs='folder_name', other arguments) works fine.

Upvotes: 1

Related Questions