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.
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).
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]
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.
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.
>>>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.
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.