Function Basics

Although we haven’t gotten very formal about it, we’ve already been using functions in earlier chapters. For instance, to make a file object, we call the built-in open function. Similarly, we use the len built-in function to ask for the number of items in a collection object.

In this chapter, we will learn how to write new functions in Python. Functions we write ourselves behave the same way as the built-ins we’ve already seen—they are called in expressions, are passed values, and return results. But writing functions requires a few new ideas; here’s an introduction to the main concepts:

def creates a function object and assigns it to a name

Python functions are written with a new statement, the def. Unlike functions in compiled languages such as C, def is an executable statement—when run, it generates a new function object and assigns it to the function’s name. As with all assignments, the function name becomes a reference to the function object.

return sends a result object back to the caller

When a function is called, the caller stops until the function finishes its work and returns control to the caller. Functions that compute a value send it back to the caller with a return statement.

global declares module-level variables that are to be assigned

By default, all names assigned in a function are local to that function and exist only while the function runs. To assign a name in the enclosing module, functions need to list it in a global statement.

Arguments are passed by assignment (object reference)

In Python, arguments are passed to functions by assignment (i.e., by object reference). As we’ll see, this isn’t quite like C’s passing rules or C++’s reference parameters—the caller and function share objects by references, but there is no name aliasing (changing an argument name doesn’t also change a name in the caller).

Arguments, return types, and variables are not declared

As with everything in Python, there are no type constraints on functions. In fact, nothing about a function needs to be declared ahead of time; we can pass in arguments of any type, return any sort of object, and so on. As one consequence, a single function can often be applied to a variety of object types.

Let’s expand on these ideas and look at a few first examples.

General Form

The def statement creates a function object and assigns it a function name. As with all compound Python statements, it consists of a header line, followed by a block of indented statements. The indented statements become the function’s body—the code Python executes each time the function is called. The header specifies a function name (which is assigned the function object), along with a list of arguments (sometimes called parameters ), which are assigned to the objects passed in parentheses at the point of call:

def <name>(arg1, arg2,... argN):
    <statements>
    return <value>

The Python return statement can show up in function bodies; it ends the function call and sends a result back to the caller. It consists of an object expression that gives the function’s result. The return is optional; if it’s not present, a function exits when control flow falls off the end of the function body. Technically, a function without a return returns the None object automatically (more on this later in this chapter).

Definitions and Calls

Let’s jump into a simple example. There are really two sides to the function picture: a definition (the def that creates a function) and a call (an expression that tells Python to run the function). A definition follows the general format above; here’s one that defines a function called times, which returns the product of its two arguments:

>>> def times(x, y):      # create and assign function
...     return x * y      # body executed when called
...

When Python runs this def, it creates a new function object that packages the function’s code and assigns it the name times. After the def has run, the program can run (call) the function by adding parentheses after the function name; the parenthesis may optionally contain one or more object arguments, to be passed (assigned) to the names in the function’s header:

>>> times(2, 4)           # arguments in parentheses
8
>>> times('Ni', 4)        # functions are 'typeless'
'NiNiNiNi'

In the first line, we pass two arguments to times: the name x in the function header is assigned the value 2, y is assigned 4, and the function’s body is run. In this case, the body is just a return statement, which sends back the result 8 as the value of the call expression.

In the second call, we pass in a string and an integer to x and y instead. Recall that * works on both numbers and sequences; because there are no type declarations in functions, you can use times to multiply numbers or repeat sequences. Python is known as a dynamically typed language: types are associated with objects at runtime, rather than declared in the program itself. In fact, a given name can be assigned to objects of different types at different times.[27]

Example: Intersecting Sequences

Here’s a more realistic example that illustrates function basics. Near the end of Chapter 3, we saw a for loop that collected items in common in two strings. We noted there that the code wasn’t as useful as it could be because it was set up to work only on specific variables and could not be rerun later. Of course, you could cut and paste the code to each place it needs to be run, but this isn’t a general solution; you’d still have to edit each copy to support different sequence names, and changing the algorithm requires changing multiple copies.

Definition

By now, you can probably guess that the solution to this dilemma is to package the for loop inside a function. By putting the code in a function, it becomes a tool that can be run as many times as you like. And by allowing callers to pass in arbitrary arguments to be processed, you make it general enough to work on any two sequences you wish to intersect. In effect, wrapping the code in a function makes it a general intersection utility:

def intersect(seq1, seq2):
    res = []                     # start empty
    for x in seq1:               # scan seq1
        if x in seq2:            # common item?
            res.append(x)        # add to end
    return res

The transformation from simple code to this function is straightforward; you’ve just nested the original logic under a def header and made the objects on which it operates parameters. Since this function computes a result, you’ve also added a return statement to send it back to the caller.

Calls

>>> s1 = "SPAM"
>>> s2 = "SCAM"

>>> intersect(s1, s2)               # strings
['S', 'A', 'M']

>>> intersect([1, 2, 3], (1, 4))    # mixed types
[1]

Again, we pass in different types of objects to our function—first two strings and then a list and a tuple (mixed types). Since you don’t have to specify the types of arguments ahead of time, the intersect function happily iterates though any kind of sequence objects you send it.[28]



[27] If you’ve used compiled languages such as C or C++, you’ll probably find that Python’s dynamic typing makes for an incredibly flexible programming language. It also means that some errors a compiler roots out aren’t caught by Python until a program runs (adding a string to an integer, for instance). Luckily, errors are easy to find and repair in Python.

[28] Technically, any object that responds to indexing. The for loop and in tests work by repeatedly indexing an object; when we study classes in Chapter 6, you’ll see how to implement indexing for user-defined objects too, and hence iteration and membership.

Get Learning Python now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.