Odds and Ends

So far, we’ve seen what it takes to write our own functions in Python. There are a handful of additional function-related ideas we’d like to introduce in this section:

  • lambda creates anonymous functions.

  • apply calls functions with argument tuples.

  • map runs a function over a sequence and collects results.

  • Functions return None if they don’t use a return statement.

  • Functions present design choices.

  • Functions are objects, just like numbers and strings.

lambda Expressions

Besides the def statement, Python also provides an expression form that generates function objects. Because of its similarity to a tool in the LISP language, it’s called lambda. Its general form is the keyword lambda, followed by one or more arguments, followed by an expression after a colon:

                  
                  lambda argument1, argument2,... argumentN : Expression using arguments

Function objects returned by lambda expressions are exactly the same as those created and assigned by def. But the lambda has a few differences that make it useful in specialized roles:

lambda is an expression, not a statement

Because of this, a lambda can appear in places a def can’t—inside a list constant, for example. As an expression, the lambda returns a value (a new function), which can be assigned a name optionally; the def statement always assigns the new function to the name in the header, instead of returning it as a result.

lambda bodies are a single expression, not a block of statements

The lambda’s body is similar to what you’d put in a def body’s return statement; simply type the result as a naked expression, instead of explicitly returning it. Because it’s limited to an expression, lambda is less general than a def; you can only squeeze so much logic into a lambda body without using statements such as if.

Apart from those distinctions, the def and lambda do the same sort of work. For instance, we’ve seen how to make functions with def statements:

>>> def func(x, y, z): return x + y + z
...
>>> func(2, 3, 4)
9

But you can achieve the same effect with a lambda expression, by explicitly assigning its result to a name:

>>> f = lambda x, y, z: x + y + z
>>> f(2, 3, 4)
9

Here, f is assigned the function object the lambda expression creates (this is how def works, but the assignment is automatic). Defaults work on lambda arguments too, just like the def:

>>> x = (lambda a="fee", b="fie", c="foe": a + b + c)
>>> x("wee")
'weefiefoe'

lambdas come in handy as a shorthand for functions. For instance, we’ll see later that callback handlers are frequently coded as lambda expressions embedded directly in a registration call, instead of being defined elsewhere in a file and referenced by name.

The apply Built-in

Some programs need to call arbitrary functions in a generic fashion, without knowing their names or arguments ahead of time. We’ll see examples of where this can be useful later, but by way of introduction, the apply built-in function does the job. For instance, after running the code in the prior section, you can call the generated functions by passing them as arguments to apply, along with a tuple of arguments:

>>> apply(func, (2, 3, 4))
9
>>> apply(f, (2, 3, 4))
9

apply simply calls the passed-in function, matching the passed-in arguments list with the function’s expected arguments. Since the arguments list is passed in as a tuple (a data structure), it can be computed at runtime by a program. The real power of apply is that it doesn’t need to know how many arguments a function is being called with; for example, you can use if logic to select from a set of functions and argument lists, and use apply to call any:

if <test>:
    action, args = func1, (1,)
else:
    action, args = func2, (1, 2, 3)
. . . 
apply(action, args)

The map Built-in

One of the more common things programs do with lists is to apply an operation to each node and collect the results. For instance, updating all the counters in a list can be done easily with a for loop:

>>> counters = [1, 2, 3, 4]
>>>
>>> updated = []
>>> for x in counters:
...     updated.append(x + 10)              # add 10 to each item
...
>>> updated
[11, 12, 13, 14]

Because this is such a common operation, Python provides a built-in that does most of the work for you: the map function applies a passed-in function to each item in a sequence object and returns a list containing all the function call results. For example:

>>> def inc(x): return x + 10               # function to be run
...
>>> map(inc, counters)                      # collect results
[11, 12, 13, 14]

Since map expects a function, it also happens to be one of the places where lambdas commonly appear:

>>> map((lambda x: x + 3), counters)        # function expression
[4, 5, 6, 7]

map is the simplest representative of a class of Python built-ins used for functional programming (which mostly just means tools that apply functions to sequences). Its relatives filter out items based on a test (filter) and apply operations to pairs of items (reduce). We say more about these built-in tools in Chapter 8.

Python “Procedures”

In Python functions, return statements are optional. When a function doesn’t return a value explicitly, the function exits when control falls off the end. Technically, all functions return a value; if you don’t provide a return, your function returns the None object automatically:

>>> def proc(x):
...     print x        # no return is a None return
...
>>> x = proc('testing 123...')
testing 123...
>>> print x
None

Functions such as this without a return are Python’s equivalent of what are called procedures in some languages (such as Pascal). They’re usually called as a statement (and the None result is ignored), since they do their business without computing a useful result. This is worth knowing, because Python won’t tell you if you try to use the result of a function that doesn’t return one. For instance, assigning the result of a list append method won’t raise an error, but you’ll really get back None, not the modified list:

>>> list = [1, 2, 3]
>>> list = list.append(4)      # append is a 'procedure'
>>> print list                 # append changes list in-place 
None

Function Design Concepts

When you start using functions, you’re faced with choices about how to glue components together—for instance, how to decompose a task into functions, how functions should communicate, and so on. Some of this falls into the category of structured analysis and design, which is too broad a topic to discuss in this book. But here are a few general hints for Python beginners:

Use arguments for inputs and return for outputs

Generally speaking, you should strive to make a function independent of things outside of it. Arguments and return statements are often the best way to isolate dependencies.

Use global variables only when absolutely necessary

Global variables (i.e., names in the enclosing module) are usually a poor way to communicate with a function. They can create dependencies that make programs difficult to change.

Don’t change mutable arguments unless the caller expects it

Functions can also change parts of mutable objects passed in. But as with global variables, this implies lots of coupling between the caller and callee, which can make a function too specific and brittle.

Table 4.3 summarizes the ways functions can talk to the outside world; inputs may come from items in the left column, and results may be sent out in any of the forms on the right. Politically correct function designers usually only use arguments for inputs and return statements for outputs. But there are plenty of exceptions, including Python’s OOP support—as we’ll see in Chapter 6, Python classes depend on changing a passed-in mutable object. Class functions set attributes of an automatically passed-in self object, to change per-object state information (e.g., self.name = 'bob'); side effects aren’t dangerous if they’re expected.

Table 4-3. Common Function Inputs and Outputs

Function Inputs

Function Outputs

Arguments

Return statement

Global (module) variables

Mutable arguments

Files, streams

Global (module) variables

Functions Are Objects: Indirect Calls

Because Python functions are objects at runtime, you can write programs that process them generically. Function objects can be assigned, passed to other functions, stored in data structures, and so on, as if they were simple numbers or strings. Function objects happen to export a special operation; they can be called by listing arguments in parentheses after a function expression. But functions belong to the same general category as other objects.

For instance, as we’ve seen, there’s really nothing special about the name we use in a def statement: it’s just a variable assigned in the current scope, as if it had appeared on the left of an = sign. After a def runs, the function name is a reference to an object; you can reassign that object to other names and call it through any reference—not just the original name:

>>> def echo(message):           # echo assigned to a function object
...     print message
...
>>> x = echo                     # now x references it too
>>> x('Hello world!')            # call the object by adding ()
Hello world!

Since arguments are passed by assigning objects, it’s just as easy to pass functions to other functions, as arguments; the callee may then call the passed-in function just by adding arguments in parentheses:

>>> def indirect(func, arg):
...     func(arg)                         # call object by adding ()
...
>>> indirect(echo, 'Hello jello!')        # pass function to a function
Hello jello!

You can even stuff function objects into data structures, as though they were integers or strings. Since Python compound types can contain any sort of object, there’s no special case here either:

>>> schedule = [ (echo, 'Spam!'), (echo, 'Ham!') ]
>>> for (func, arg) in schedule:
...     apply(func, (arg,))
...
Spam!
Ham!

This code simply steps through the schedule list, calling the echo function with one argument each time through. As we hope you’re starting to notice by now, Python’s lack of type declarations makes for an incredibly flexible programming language. Notice the use of apply to run functions generically, the single-item tuple in the second argument to apply, and the tuple unpacking assignment in the for loop header (all ideas introduced earlier).

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.