Python Functions: Advanced Concepts

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.

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.

Get certified with our courses

Learn Python properly through small, easy-to-digest lessons, progress tracking, quizzes to test your knowledge, and practice sessions. Each course will earn you a downloadable course certificate.

Related articles

Leave a Comment

Python Newsletter

Before you leave, you may want to sign up for my newsletter.

Tips & Tricks
News
Course discounts

Googler exit intent popup

No spam & you can unsubscribe at any time.