Python Functions

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? Let's explore some advanced tricks regarding Python functions.

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 argument 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
>>>

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 an array 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.


If you liked this page, please share it with a fellow learner: