Declaring functions

By Martin McBride, 2021-10-09
Tags: function positional parameter named parameter optional parameter args variable length args list kwargs keyword args list
Categories: python language intermediate python


We covered simple Python functions here. This article looks at more advanced ways to declare function parameters.

We will look at:

  • Positional parameters.
  • Optional parameters.
  • Variable numbers of parameters.
  • Keyword parameters.
  • Positional only parameters.
  • Keyword only parameters.

This article looks at how we can declare various functions. See the related article on how we can call functions.

Positional parameters

Here is a simple Python function declaration, that we will extend in this article:

def fancy_print(s):
    out = '[' + s + ']'
    print(out)

This is simple enough, the function takes one parameter, and whatever value you pass in gets printed out with enclosing square brackets.

Here is how we would call it:

fancy_print('Hello')

The result would be:

[Hello]

Optional parameters

Suppose we wanted to extend our function so that instead of enclosing our output string in square brackets, we could choose what symbols to use. That is fairly easy to do:

def fancy_print(s, before, after):
    out = before + s + after
    print(out)

fancy_print('Hello', '<', '>')   # Prints '<Hello>'

This is good, but what if we usually want to use square brackets, and only use different brackets quite rarely. In that case, we can use optional parameters (sometimes called defaulted parameters), like this:

def fancy_print(s, before='[', after=']'):
    out = before + s + after
    print(out)

The function now has one mandatory parameter, s, and two optional parameters, before and after. We only need to supply the optional parameters if we don't want to use the default values. Here are some examples:

fancy_print('Hello')             # Prints '[Hello]'
fancy_print('Hello', '(')        # Prints '(Hello]'
fancy_print('Hello', '<', '>')   # Prints '<Hello>'

In this case:

  • If we supply one parameter, s, the default square brackets are used.
  • If we supply two parameters, s and before, the opening square bracket is replaced by the extra parameter.
  • If we supply three parameters, s, before and after, both square brackets are replaced by the extra parameters.

Recipe for optional parameters

In a function declaration, the optional parameters must be placed after the non-optional parameters.

This is simply to avoid confusion. For example, suppose you were allowed to declare a function like this:

def bad_fancy_print(before='[', after=']', s):  # You CAN'T do this!

This declaration is a syntax error, for good reason. Suppose we called the function like this:

bad_fancy_print('a', 'b')

What would that mean? If we assigned the two values to the parameters before and after, it would mean that s has no value. But if we must always pass three parameters, it means that before and after aren't optional. It makes no sense to define positional parameters after optional parameters, so it isn't allowed.

Calling parameters by name

Suppose we wanted to set after but keep the default value for before? We can do this by naming the parameter when we call it:

fancy_print('Hello', after='>')   # Prints '[Hello>'

This is very useful if a function has a lot of optional parameters and you only want to set a few of them. In this case, parameter s is passed by position (we don't use its name, but we know that s is the first parameter). Parameter after is passed by name.

Generally, you can pass parameters by position or by name, so you can do this:

fancy_print(after='>', s='Hello')   # Prints '[Hello>'

Even though s doesn't have a default value you can still pass it by name, and if you pass it by name it doesn't have to be first. But, since it doesn't have a default value, you must pass it in one way or the other.

In summary, for simple functions (without variable numbers of arguments, discussed next):

  • Each parameter can be passed by position or name.
  • Positional parameters must appear first, and in the correct order.
  • Named parameters must appear after all the positional parameters, and can be in any order.

Variable number of parameters

Some Python functions, for example the print function, can accept any number of parameters. You can pass in as many values as you like and it will print them all:

print(1, 'xyz', 9)    # prints 1 xyz 9

How would you go about creating a function like this? Here is how we could make a multiple parameter version of fancy_print:

def multi_print(*args):
    for s in args:
        out = '[' + s + ']'
        print(out)

multi_print('abc', 'defgh', 'XYZ')

Notice that the *args parameter has an asterisk in front of it. This identifies it as a variable length parameter list. Although it is often given the name *args, you can call it anything you like. You could call it *myparams, for example

What this parameter does is collect all the parameters passed in, and place them in a tuple. Inside the body of the function, you can access the tuple of values via the args variable.

Our code loops over the tuple, and prints:

[abc]
[defgh]
[XYZ]

It is valid to call multi_print with no parameters. The tuple will be empty, and nothing will be printed.

Mixing parameter types

You can mix fixed and variable parameters in a function. For example, we could add a title to our multi_print function:

def multi_print(title, *args):
    print(title)
    print('--------')
    for s in args:
        out = '[' + s + ']'
        print(out)

multi_print('My list', 'abc', 'defgh', 'XYZ')

Here, title picks up the first parameter value, and args gets any remaining parameters as a tuple. The function must be called with at least one parameter because title needs a value. Here is what it prints:

My list
--------
[abc]
[defgh]
[XYZ]

You can also add optional parameters. Here we will add back the before and after parameters from the original code above:

def multi_print(title, *args, before='[', after=']'):
    print(title)
    print('--------')
    for s in args:
        out = before + s + after
        print(out)

multi_print('My list', 'abc', 'defgh', 'XYZ', before='<', after='>')

This prints:

My list
--------
<abc>
<defgh>
<XYZ>

Since we don't know how many parameters are in *args, there are a couple of restrictions on how it is used:

  • Any parameters that come before *args must be positional only. So in our case title cannot be passed by name, it must always be the first parameter.
  • Any parameters that come after *args must be keyword only. So before and after must be passed by name.

Here is an example of trying to pass before and after positionally (which won't work):

multi_print('My list', 'abc', 'defgh', 'XYZ', '<', '>') # Doesn't work!

The function has no way of knowing that we want to use the angle brackets as before and after, it will just treat them as part of *args.

Keyword parameters

Keyword parameters are a bit like variable parameter lists, but they work with named parameters. They are declared like this:

def some_function(**kwargs):

The form **kwargs identifies it as a keyword parameter list (it doesn't have to be called kwargs of course). It is called like this:

some_function(name='count', value=1):

This allows you to pass in named parameters that some_function doesn't even know about until it is called. The parameters are stored in a dictionary, accessed by the variable kwargs. Here is a simple function that uses this:

def kw_test(**kwargs):
    for kw in kwargs:
        print(kw, 'is', kwargs[kw])

kw_test(name='count', value=1)

This prints:

name is count
value is 1

An example of keyword parameters in action is the Python dictionary constructor:

d = dict(type='hat', style='stetson', color='brown', size='10')

This creates a dictionary like this:

{'style': 'stetson', 'color': 'brown', 'size': '10', 'type': 'hat'}

One thing to note about this is that the dictionary keys must be strings that form valid Python variable names. In general, of course, a dictionary key can be any immutable object.

Recipe for mixing parameters

There are four different ways of declaring parameters in Python functions:

  • Non-defaulted parameters - normal parameters, eg s
  • Variable length parameter list, eg *args (there cannot be more than one of these)
  • Defaulted parameters, eg before='['
  • Keyword parameter list, eg **kwargs (there cannot be more than one of these)

It would be quite rare to use all of these, but it isn't impossible. In general, it is best to declare the parameters in the order shown, that is:

  • All the non-defaulted parameters, if there are any
  • The variable length parameter list, if there is one
  • All the defaulted parameters, if there are any
  • The keyword parameter list, if there is one

Most other combinations will cause syntax errors. Some other combinations are legal but generally not that useful.

Position only parameters

As we saw before, we can generally pass parameters into a function by name or position:

fancy_print('Hello', '<', '>')
fancy_print('Hello', before='<', after='>')
fancy_print(before='<', s='Hello', after='>')

This means that the parameter names are part of the public interface to the function. If we changed the name before to prefix, then any existing code that uses the function and passes that parameter by name will break.

That is fair enough, it is very useful to have a before parameter, and the price we pay for that benefit is that we can't just randomly change the parameter name without consequences.

But what about the parameter s? That isn't a particularly meaningful name, and since it is the first parameter, it might be nice to be able to say that you cannot use the name s, you must use it as a positional parameter.

As of Python 3.8, we can do that with the dummy parameter /, like this:

def fancy_print_position(s, /, before='[', after=']'):
    out = before + s + after
    print(out)

/ isn't a parameter itself, but it splits the parameters into two groups:

  • The parameters before / are positional only, and cannot be passed by name.
  • The parameters after / can be passed by position or name.

For example:

fancy_print_position('Hello', '[')            # Valid
fancy_print_position('Hello', before='[')     # Valid
fancy_print_position(s='Hello', before='[')   # Error

Named only parameters

A related issue with fancy_print is that some users might be calling it using just positional parameters. This means that, if we want to add more optional parameters, we must always add them to the end of the function (otherwise it would break any code that relied on the existing positions).

This means that we could end up with a function that has lots of parameters, but they are all declared in some random order according to when they were added, rather than being grouped in a more sensible order.

We can avoid this by forcing some parameters passed by name, using the * dummy parameter:

def fancy_print_position(s, *, before='[', after=']'):
    out = before + s + after
    print(out)

In this case:

  • The parameters before * can be passed by position or name.
  • The parameters after * must be passed by name.

This forces users to name the before and after parameters, so they cannot rely on the order of the parameters. So if we decide to change the order of the parameters in a library update, it won't break any existing code.

You can use / and * together. In fact, using *args has a similar effect, because all the parameters before *args must be positional, and all the parameters after must be named.

Summary

Python provides several ways to add optional parameters to functions. This allows you to add extra functionality without complicating the default use of the function.

See also

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

Join the PythonInformer Newsletter

Sign up using this form to receive an email when new content is added:

Popular tags

2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest