login
Holden Web
What you'll need to know tomorrow

More About Functions

Now that you've got the basics of functions down, we'll build on that knowledge with keyword parameters, switches, importing functions, and more!

Arbitrary Keyword Parameters

We learned earlier that when an unknown number of positional arguments will be provided, you can capture the extra ones (that don't correspond to any of the formal parameters) by specifying a parameter name that is preceded by a single asterisk (*). In much the same way, you can capture keyword arguments whose names do not correspond to the name of any parameter. To do that, we'll prefix the last defined parameter with two asterisks, and call a dict-parameter. Create a new program in the editor window as shown:

Code
""" Demonstrates capture of keyword arguments"""

def keywords(**kwargs):
    "Prints the keys and arguments passed through"
    for key in kwargs:
        print("{0}: {1} ".format(key, kwargs[key]))

def keywords_as_dict(**kwargs):
    "Returns the keyword arguments as a dict"
    return kwargs

if __name__ == "__main__":
    keywords(guido="Founder of Python", python="Used by NASA and Google")
    print(keywords_as_dict(guido="Founder of Python", python="Used by NASA and Google"))

Save it as keyword_args.py, and run it. Your output looks like this:

Output
guido: Founder of Python
python: Used by NASA and Google
{'guido': 'Founder of Python', 'python': 'Used by NASA and Google'}
Modern Python Since Python 3.7, dictionaries preserve insertion order. The original course was written for Python 3.1, where dict ordering was unpredictable, so the original output showed python: before guido:. On current Python the keys appear in the order they were passed to the function call—guido first, then python.

The program has two functions that capture general keyword arguments. When you call such a function, the interpreter matches up the positional and keyword arguments with their corresponding parameters, then takes any unmatched keyword arguments and puts them into a dict, which it binds to the dict-parameter. The first function, keywords(), iterates over the keys of the dict, printing the keys (which are the names of the unmatched keyword arguments) and the associated values (which are the values following the equals signs). The second function, keywords_as_dict(), just returns the keyword arguments, demonstrating that the dict-parameter is in fact a dict.

Parameters, Sequence-Parameters, and Dict-Parameters

Sometimes you need to mix different argument-passing methods. In an earlier lesson, you learned how to include specific positional parameters in a function that also uses a sequence-parameter. You can also specify keyword parameters in a function that has a dict-parameter.

Suppose you want a function that prints the description of a college course including a name that is a standard positional parameter, an instructor, any number of students, and possibly other staff with assigned roles. To do this in Python, you combine multiple types of parameters. You'll use positional parameters, as well as a sequence-parameter and a dict-parameter. Let's give it a try in a program. Type the code below as shown:

Code
def description(name, instructor, *students, **staff):
    """Print out a course description.
    name:           Name of the course
    instructor:     Name of the instructor
    *students, ...: List of student names (positional arguments)
    **staff, ...:   List of additional staff (keyword arguments)
    """

    print("=" * 40)
    print("Course Name:", name)
    print("Instructor:", instructor)
    print("-" * 40)
    for title, name in staff.items():
        print(title.capitalize(), ": ", name)
    print("{0:-^40}".format(" registered students "))
    for student in students:
        print(student)

if __name__ == "__main__":
    description("Python 101",
            "Steve Holden",
            "Georgie Peorgie",
            "Mary Lamb",
            "Penny Rice",
            publisher="O'Reilly School of Technology",
            author="Python Software Foundation"
            )

    description("Django 101",
            "Jacob Kaplan-Moss",
            "Baa-Baa Blacksheep",
            "Mary Contrary",
            "Missy Muffet",
            "Peter Piper",
            publisher="O'Reilly School of Technology",
            author="Django Software Foundation",
            editor="Daniel Greenfeld"
            )

Save it as courses.py, and run it:

Output
========================================
Course Name: Python 101
Instructor: Steve Holden
----------------------------------------
Publisher :  O'Reilly School of Technology
Author :  Python Software Foundation
--------- registered students ----------
Georgie Peorgie
Mary Lamb
Penny Rice
========================================
Course Name: Django 101
Instructor: Jacob Kaplan-Moss
----------------------------------------
Publisher :  O'Reilly School of Technology
Author :  Django Software Foundation
Editor :  Daniel Greenfeld
--------- registered students ----------
Baa-Baa Blacksheep
Mary Contrary
Missy Muffet
Peter Piper

Let's take a closer look:

Observe
def description(name, instructor, *students, **staff):
    """Print out a course description.
    name:           Name of the course
    instructor:     Name of the instructor
    *students, ...: List of student names (positional arguments)
    **staff, ...:   List of additional staff (keyword arguments)
    """

    print("=" * 40)
    print("Course Name:", name)
    print("Instructor:", instructor)
    print("-" * 40)
    for title, name in staff.items():
        print(title.capitalize(), ": ", name)
    print("{0:-^40}".format(" registered students "))
    for student in students:
        print(student)

if __name__ == "__main__":
    description("Python 101",
            "Steve Holden",
            "Georgie Peorgie",
            "Mary Lamb",
            "Penny Rice",
            publisher="O'Reilly School of Technology",
            author="Python Software Foundation"
            )

    description("Django 101",
            "Jacob Kaplan-Moss",
            "Baa-Baa Blacksheep",
            "Mary Contrary",
            "Missy Muffet",
            "Peter Piper",
            publisher="O'Reilly School of Technology",
            author="Django Software Foundation",
            editor="Daniel Greenfeld"
            )

The first and second parameters (name and instructor) are positional, and so are bound to the first and second arguments of any call. Any additional positional arguments are placed into the students tuple. Finally, any keyword arguments are placed into the staff dict.

The name and instructor parameters are printed out. The function then iterates over the items (each item is a (key, value) pair of the staff dict-parameter) to print details about any additional staff. Finally, the function loops through the students to list the individuals taking the class.

Warning Take care when using sequence- and dict-parameters. With regular (positional and keyword) parameters, you can usually determine the interface of the function (that is, how it should be called) from the function and parameter names. When sequence- and dict-parameters are used, this is more difficult to determine.

If you do use sequence- and dict-parameters, make sure you document the purpose of each parameter in the function's docstring. This is good practice in any case, but especially so when the interface is more complex.

Let's take a closer look at what our docstrings give us. Try these commands in an interactive session:

Code and output
>>> import courses
>>> help(courses.description)
Help on function description in module courses:

description(name, instructor, *students, **staff)
    Print out a course description.
    name:           Name of the course
    instructor:     Name of the instructor
    *students, ...: List of student names (positional arguments)
    **staff, ...:   List of additional staff (keyword arguments)

By documenting your function correctly, you've provided useful information to anyone who imports your module. (Your fellow programmers thank you!) Of course, the module itself can also have useful documentation, though in this case, there just wasn't much to provide. Continue your previous interactive session to verify that your documentation appears as expected:

Code and output
>>> help(courses)
Help on module courses:

NAME
    courses

FUNCTIONS
    description(name, instructor, *students, **staff)
        Print out a course description.
        name:           Name of the course
        instructor:     Name of the instructor
        *students, ...: List of student names (positional arguments)
        **staff, ...:   List of additional staff (keyword arguments)

FILE
    /users/smiller/python1/courses.py

Nice! The interpreter created a manual page for your module, just from the documentation strings that you entered. Now anyone who wants to use your module can import it into an interactive session and learn all about it using Python's standard help() function!

Importing Functions and help()

In the previous lesson, you learned about imports, including how to bring functions you've written into other programs. Now let's go over a handy trick that all Python developers love. First, we'll import the keyword_args.py module you wrote earlier in this lesson and run the built-in help() function over it. To get out of the help interface, just press q. Then type the commands below as shown:

Code and output
>>> import keyword_args
>>> help(keyword_args)
Help on module keyword_args:

NAME
    keyword_args - Demonstrates capture of keyword arguments

FUNCTIONS
    keywords(**kwargs)
        Prints the keys and arguments passed through

    keywords_as_dict(**kwargs)
        Returns the keyword arguments as a dict

FILE
    /users/smiller/python1/keyword_args.py

So, thanks to the help() method, we can use the interactive interpreter to find important information about the functions we've written. These code statements are really driven by the docstrings you write into your Python code. All of the functions of a module are part of its documentation.

If all of this isn't enough to make you start sprinkling doc strings around your code, then nothing will persuade you! You can document modules, functions, and classes just by making their first executable statement a documentation string. That's the kind of simple power that Python is known for.

Function Execution by Dispatch

So far, when you've needed to control the flow of a program in Python, you've used the if statement to choose between two alternatives. But what if you need to select from multiple options? One way is to use if, elif, and else, but that can become unwieldy—especially when large numbers of alternatives are involved. If you had a hundred or a thousand lines of code between the if statements, the resulting program could likely be difficult to read, and even more difficult to maintain.

Thankfully, Python gives you a good way to work around this using tools you've already learned. You can write each alternative set of actions as a function, and then use a dictionary to define logic flow. The keys represent possible actions, and the functions are the actions themselves. This sounds a lot more complex than it actually is; let's use an example to clarify things:

Code and output
>>> def add(a, b):
...   return a + b
...
>>> def sub(a, b):
...   return a - b
...
>>> sw = {'adder':add, 'subber':sub}
>>> sw['adder'](3,2)
5
>>> sw['subber'](3,2)
1
>>> sw
{'adder': <function add at 0x...>, 'subber': <function sub at 0x...>}
Modern Python The memory addresses in the sw repr will differ every run; 0x... stands in for whatever your interpreter assigns. The important point is that the values are function objects.

First we created the two simple functions, add() and sub(), then we placed them inside the sw dict. Then we called them (like any other Python dict) by referencing their keys, and passed in arguments. This provides a nice, clean way of organizing and calling functions. In the last two lines of the example, we printed out the logic flow. When a dict of functions is used this way, it is called a dispatch table.

Ready for a more detailed example? Good! We'll put five functions into a dict, then use a while loop and an input statement to act as our user interface. We'll dispatch the appropriate function according to the user's input. A lot of this will look familiar to you. Let's go ahead and get it working. Create a new program as shown below:

Code
""" A program designed to display switching in Python """

import sys

def print_text(text, *args, **kwargs):
    """Print just the text value"""
    print('text: ' + text)

def print_args(text, *args, **kwargs):
    """Print just the argument list"""
    print('args:')
    for i, arg in enumerate(args):
        print('{0}: {1}'.format(i, arg))

def print_kwargs(text, *args, **kwargs):
    """Print just the keyword arguments"""
    print('keyword args:')
    for k, v in kwargs.items():
        print('{0}: {1}'.format(k, v))

def print_all(text, *args, **kwargs):
    """Prints everything"""
    print_text(text, *args, **kwargs)
    print_args(text, *args, **kwargs)
    print_kwargs(text, *args, **kwargs)

def quit(text, *args, **kwargs):
    """Terminates the program."""
    print("Quitting the program")
    sys.exit()

if __name__ == "__main__":
    switch = {
        'text': print_text,
        'args': print_args,
        'kwargs': print_kwargs,
        'all': print_all,
        'quit': quit
    }

    options = switch.keys()
    prompt = 'Pick an option from the list ({0}): '.format(', '.join(options))
    while True:
        inp = input(prompt)
        option = switch.get(inp, None)
        if option:
            option('Python','is','fun',course="Python 101",publisher="O'Reilly")
            print('-' * 40)
        else:
            print('Please select a valid option!')

Save it as switch.py, and run it. Try the different options. Also, try typing something that isn't one of the options. Before we start reviewing it, take a minute and check out the difference between this program and earlier ones in the course. Doesn't this one just look cleaner?

Now, let's look at the functions:

Observe
""" A program designed to display switching in Python """

import sys

def print_text(text, *args, **kwargs):
    """Print just the text value"""
    print('text: ' + text)

def print_args(text, *args, **kwargs):
    """Print just the argument list"""
    print('args:')
    for i, arg in enumerate(args):
        print('{0}: {1}'.format(i, arg))

def print_kwargs(text, *args, **kwargs):
    """Print just the keyword arguments"""
    print('keyword args:')
    for k, v in kwargs.items():
        print('{0}: {1}'.format(k, v))

def print_all(text, *args, **kwargs):
    """Prints everything"""
    print_text(text, *args, **kwargs)
    print_args(text, *args, **kwargs)
    print_kwargs(text, *args, **kwargs)

def quit(text, *args, **kwargs):
    """Terminates the program."""
    print("Quitting the program")
    sys.exit()

if __name__ == "__main__":
    switch = {
        'text': print_text,
        'args': print_args,
        'kwargs': print_kwargs,
        'all': print_all,
        'quit': quit
    }

    options = switch.keys()
    prompt = 'Pick an option from the list ({0}): '.format(', '.join(options))
    while True:
        inp = input(prompt)
        option = switch.get(inp, None)
        if option:
            option('Python','is','fun',course="Python 101",publisher="O'Reilly")
            print('-' * 40)
        else:
            print('Please select a valid option!')

All of the functions insist on the same arguments, even if most of them only use a portion of those arguments. The first three functions are clear enough; the fourth function just calls all three of them, and the last function uses the Python standard library sys module to quit the program.

Now, let's move on to everything that follows if __name__ == "__main__":. First, we create the switch dict, which has five elements—the values are each of the previously defined functions. Then, we construct an options list from the switch.keys()—keys of the switch dict. Then, we prompt the user for an option and start the input loop.

In the input loop, option = switch.get(inp, None) takes the user's option and either finds the function in question or returns a None object. If an option is found (if option), then the parameters are passed to the user-selected function. If no option is found, the user is prompted to 'Please select a valid option!'.

The result is a cleaner application where reuse or integration of new functions is much easier. For example, let's add in the description() function from the courses.py module you wrote earlier in this lesson. Modify the code and the switch dict as shown:

Code
""" A program designed to display switching in Python """

import sys
import courses

def print_text(text, *args, **kwargs):
    """Print just the text value"""
    print('text: ' + text)

def print_args(text, *args, **kwargs):
    """Print just the argument list"""
    print('args:')
    for i, arg in enumerate(args):
        print('{0}: {1}'.format(i, arg))

def print_kwargs(text, *args, **kwargs):
    """Print just the keyword arguments"""
    print('keyword args:')
    for k, v in kwargs.items():
        print('{0}: {1}'.format(k, v))

def print_all(text, *args, **kwargs):
    """Prints everything"""
    print_text(text, *args, **kwargs)
    print_args(text, *args, **kwargs)
    print_kwargs(text, *args, **kwargs)

def quit(text, *args, **kwargs):
    """Terminates the program."""
    print("Quitting the program")
    sys.exit()

if __name__ == "__main__":
    switch = {
        'text': print_text,
        'args': print_args,
        'kwargs': print_kwargs,
        'all': print_all,
        'course': courses.description,
        'quit': quit
    }

    options = switch.keys()
    prompt = 'Pick an option from the list ({0}): '.format(', '.join(options))
    while True:
        inp = input(prompt)
        option = switch.get(inp, None)
        if option:
            option('Python','is','fun',course="Python 101",publisher="O'Reilly")
            print('-' * 40)
        else:
            print('Please select a valid option!')

Save and run it. Choose the course option; your results may seem a little silly, but they are correct based on the argument being passed to the function—and the instructor does exist, and we think students are fun! See how easily we can integrate new functionality into our program? The logic doesn't change at all, only the data that drives it.

What's Your Function?

In this lesson, we reinforced what you already knew about functions and imports. You learned how to take code written inside of functions and use it in other places, and that documentation in docstrings can be really useful. You've reaped the benefits of splitting your own programs to make them more modular. And finally, you've seen how your earlier work could have been written more efficiently to benefit from this modular approach. In the next lesson, you'll learn about Python's classes and object-oriented programming.

Keep in mind, as we push on, that good practice for Python developers means never repeating any stanza of code twice. Instead, put it into a function, and call the function twice!

Alright then, let's keep this train rolling!