Lecture 8

Variadic functions and decorators

MCS 275 Spring 2022
David Dumas

Lecture 8: Variadic functions and decorators

Course bulletins:
  • Project 1 due Fri 4 Feb at 6:00pm central.
  • Project 1 autograder opens on Monday.
  • Homework 3 available.

Variadic functions

A function is variadic if it can accept a variable number of arguments. This is general CS terminology.

Python supports these. The syntax

def f(a,b,*args):
means that the first argument goes into variable a, the second into variable b, and any other arguments are put into a tuple which is assigned to args

Variadics and keyword arguments

The syntax

def f(a,b,**kwargs):
or
def f(a,b,*args,**kwargs):
puts extra keyword arguments into a dictionary called kwargs.

It is traditional to use the names args and kwargs, but it is not required.

Argument unpacking

Take arguments from a list or tuple:

L = [6,11,16]
f(1,*L) # calls f(1,6,11,16)

Take keyword arguments from a dict:

d = { "mcs275": "fun", "x": 42 }
f(1,z=0,**d) # calls f(1,z=0,mcs275="fun",x=42)

Think of * as "remove the brackets", and ** as "remove the curly braces".

Why?

Sometimes you may write a function that needs to pass most of its arguments on to another function.

Function arguments

Functions in Python can accept functions as arguments.

def dotwice(f):
    """Call function f twice"""
    f()
    f()

A better version works with functions that accept arguments:

def dotwice(f,*args,**kwargs):
    """Call function f twice (allowing arguments)"""
    f(*args,**kwargs)
    f(*args,**kwargs)

Returning functions

Functions in Python can return functions. Often this is used with a return value that is a defined inside the function body, making a "function factory".

def return_power(n):
    def inner(x): # function inside a function!
        """Raise x to a power"""
        return x**n
    return inner

Modifying functions

def return_twice_doer(f):
    """Return a new function which calls f twice"""
    def inner(*args,**kwargs):
        """Call a certain function twice"""
        f(*args,**kwargs)
        f(*args,**kwargs)
    return inner

Replacing functions

In some cases we might want to replace an existing function with a modified version of it (e.g. as returned by some other function).

def g(x):
    """Print the argument with a message"""
    print("Function got value",x) 

# actually, I wanted to always print that message twice!
g = return_twice_doer(g)

Decorator syntax

There is a shorter syntax to replace a function with a modified version.

@modifier
def fn(x,y):
    """Function body goes here"""

is equivalent to

def fn(x,y):
    """Function body goes here"""
fn = modifier(fn)

The symbol @modifier (or any @name) before a function definition is called a decorator.

Returning values

Usually, the inner function of a decorator should return the value of the (last) call to the argument function.

def return_twice_doer(f):
    """Return a new function which calls f twice"""
    def inner(*args,**kwargs):
        """Call a certain function twice"""
        f(*args,**kwargs)
        return f(*args,**kwargs)
    return inner

Decorator arguments

Python allows @decorator(arg1,arg2,...).

@dec(2)
def printsq(x):
    print(x*x)
is equivalent to
thisdec = dec(2)

@thisdec
def printsq(x):
    print(x*x)
In other words, if a decorator is given arguments, then the name after @ is expected to be a decorator factory.

A few built-in decorators

  • @functools.lru_cache(100) -- Save arguments and return values for up to 100 recent calls to a function; reuse stored return values when possible. Good for expensive operations.*
  • @classmethod -- Make a method a class method (callable from the class itself, gets class as first argument). E.g. for alternate constructors.
  • @atexit.register -- Ask that this function be called just before the program exits.

* In Python 3.9+ there is also the simpler functools.cache decorator which stores an unlimited number of past function calls..

Multiple decorators

Each must be on its own line.

@dec1
@dec2
@dec3
def f(x):
    """Function body goes here"""

replaces f with dec1(dec2(dec3(f))).

So the decorator closest to the function name acts first.

References

  • See Lutz, Chapter 18 for more about function arguments (including variadic functions).
  • Beazley & Jones, Chapter 7 has examples of variadic functions.
  • See Lutz, Chapter 39 for a detailed discussion of Python decorators.
  • See Beazley & Jones, Chapter 9 for several examples of decorators.

Acknowledgment

  • I reviewed course materials created by Danko Adrovic (UIC MSCS faculty member) while preparing a previous version of this lecture.

Revision history

  • 2022-01-26 Initial publication