Do you know how to force keyword arguments, create a function decorator, create anonymous functions, or unpack an array or dictionary into a function’s arguments? In this article, Python functions advanced concepts, we’ll cover some of the more advanced concepts surrounding functions. I’ve put these in a separate article because I don’t want to confuse beginners with these concepts.
You may want to read our introduction to Python functions first.
Table of Contents
Forced keyword arguments
Keyword arguments have a number of advantages:
- You’re not forced to a particular order in which you supply your arguments—the name matters, not the position.
- Keyword arguments provide clarity. Without looking up the function itself, you can often guess what the argument is used for by looking at the names.
That’s nice, but you probably already knew these things. What you might not know is that you can also force keyword arguments. The details are described in PEP 3202, but it comes down to using an asterisk before the arguments you want to force as keyword arguments. Or, as shown below, before everything, forcing all arguments to be keyword arguments:
>>> def f(*, a, b): ... print(a, b) ... >>> f(1, 2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes 0 positional arguments but 2 were given >>> f(a=1, b=2) 1 2 >>>
This advanced function trick could come in handy when you’re developing some kind of library. Perhaps you don’t want to commit to a certain order of arguments (yet). In such cases, this is the way to force users of your library to use named arguments, making the order irrelevant.
Using * and ** for function arguments
Some functions require a long list of arguments. Although this should be avoided altogether, for example, by using data classes, it’s not always up to you. In such cases, the second-best option is to create a dictionary with all the named arguments and pass that to the function instead. It will generally make your code more readable. You can unpack a dictionary for use with named keywords by using the **
prefix:
>>> def f(a, b): ... print(a, b) ... >>> args = { "a": 1, "b": 2 } >>> f(**args) 1 2
Similarly, we can use a single *
to unpack a list and feed its content as positional arguments to a function:
>>> def f(a, b, c): ... print(a, b, c) ... >>> l = [1, 2, 3] >>> f(*l) 1 2 3
Decorating your functions
Decorators are wrappers around a function that modify the behavior of the function in a certain way. There are many use-cases for decorators, and you may have used them before when working with frameworks like Flask.
Let’s create our own decorator; it’s simpler than you might expect and might come in handy someday:
def print_argument(func): def wrapper(the_number): print("Argument for", func.__name__, "is", the_number) return func(the_number) return wrapper @print_argument def add_one(x): return x + 1 print(add_one(1))
Inside print_argument
, we define a wrapper function. This function prints the argument and the name of the called function. Next, it executes the actual function and returns its result as if the function was called regularly. With @print_argument
we apply our decorator to a function. Perhaps unnecessary to say: this decorator can be re-used for other functions too.
The output of our little script will be:
Argument for add_one is 1 2
Anonymous functions
Sometimes, naming a function is not worth the trouble. An example is when you’re sure the function will only be used once. For such cases, Python offers us anonymous functions, also called lambda functions.
A lambda function can be assigned to a variable, creating a concise way of defining a function:
>>> add_one = lambda x: x + 1 >>> add_one(3) 4
It gets more interesting when you need to use a function as an argument. In such cases, the function is often used only once. As you may know, map
applies a function to all elements of an iterable object. We can use a lambda when calling map:
>>> numbers = [1, 2, 3, 4] >>> times_two = map(lambda x: x * 2, numbers) >>> list(times_two) [2, 4, 6, 8] >>>
In fact, this is a pattern that you’ll see often. When you need to apply a relatively simple operation on each element of an iterable object, using map()
in combination with a lambda function is concise and efficient.