Python supports calling any callable object (i.e. functions, constructors, or even objects understanding __call__
method) specifying positional arguments, named arguments, or even both types of arguments.
When you pass named arguments, they must be after the positional arguments (if any is passed).
So you can call any function, like:
def f(a, b):
return a + b
In the following ways:
f(1, 2)
f(1, b=2)
f(a=1, b=2)
f(b=1, a=2) # Order doesn't matter among named arguments
While the following forms will trigger an error:
f(a=1, 2) # Named arguments must appear AFTER positional arguments
f(1, a=2) # You are passing the same argument twice: one by position, one by name
So, when you pass a named parameter, be sure you don't pass the same parameter twice (i.e. also by position), and also check that the argument name exists (also: if it is documented that the argument could/should be passed by name, respect their names on inheritance/override).
Additionally Python supports passing *arguments
and **keyword_arguments
. These are additional arguments you can process in a variadic way, as many languages support them.
*args
(the name doesn't matter - it must have an asterisk to be a positional variadic; it's a tuple) holds remaining unmatched positional arguments (instead of getting a TypeError for positional argument being unexpected, such argument gets into *args
as an element).
**kwargs
(the name doesn't matter - it must have two asterisks to be a named/keyword variadic; it's a dictionary) holds the remaining unmatched named arguments (instead of getting a TypeError for named argument being unexpected, such argument gets into **kwargs
as an element).
So, perhaps you see a function like this:
def f(*args, **kwargs):
...
You can call it with any parameters you want:
f(1, 2, 3, a=4, b=5, c=6)
Just keep the order: named arguments come after positional arguments.
You can declare a function like this:
f(m1, m2, ..., o1=1, o2=2, ..., *args, **kwargs):
pass
Understanding the following:
- m1, m2, ... are mandatory: when you call, you must fill them either positionally or respecting their name.
- o1, o2, ... are optional: when you call, you can omit such parameters (omitting them implies not passing them by position nor by name), and they will hold the value after the equal sign (such value is evaluated when the function is declared - avoid using mutable objects as their values).
- args and kwargs are what I explained you before: Any unmatched argument by position and by name go into these parameters. With these features, you will have a clear distinction between what is a parameter and what is an argument.
- All these parameters are optional. You can choose not to use mandatory and only optionals. You can choose not to use
*args
but yes **kwargs
, and so. But respect the order in the declaration: mandatory, optional, *args
, **kwargs
.
When you call the method and pass the arguments the semantic is very different so be wary:
f(1) # passes a positional argument. Has nothing to do with the parameter being mandatory.
f(a=1) # passes a named argument. Has nothing to do with the parameter being optional.
f(**i) # UNPACKS the positional arguments. Has nothing to do with the function having a *args parameter, but *args will hold any unpacked -but unmatched- positional argument from i (which is any type of sequence or generator)
f(**d) # UNPACKS its values as named arguments. Has nothing to do with the function having a **kwargs parameter, but **kwargs will hold any unpacked -but unmatched- argument from d (which is a dict having string keys).
When you make the call, you can pass them as you like, which has nothing to do with the actual method signature (i.e. expected parameters), but respect the order as with parameters: positional, named, *positionalUnpack, **keywordUnpack, or you will get a nice TypeError
.
Examples:
def f(a, b=1, *args, **kwargs):
pass
Valid calls:
f(1) # a = 1, b = 2, args = (), kwargs = {}
f(*[1]) #a = 1, b = 2, args = (), kwargs = {}
f(*[3, 4]) #a = 3, b = 4, args = (), kwargs = {}
f(**{'a':1, 'b':3}) #a = 1, b=3, args = (), kwargs = {}
f(1, *[2, 3, 4], **{'c': 5}) #a = 1, b=2, args=(3, 4), kwargs = {'c': 5}
Again beware:
- Do not pass the same parameter twice (a clash would occur between **unpacked arguments with other named arguments, or positional arguments, or *unpacked arguments).
If you want to pass *args or **kwargs to a super
call, ensure using the unpack syntax:
def my_method(self, a, b, *args, **kwargs):
super(MyClass, self).my_method(a+1, b+1, *args, **kwargs)