Learning Python - Mark Lutz [289]
Today, another way to achieve the effect of mutable defaults in a possibly less confusing way is to use the function attributes we discussed in Chapter 19:
>>> def saver():
... saver.x.append(1)
... print(saver.x)
...
>>> saver.x = []
>>> saver()
[1]
>>> saver()
[1, 1]
>>> saver()
[1, 1, 1]
The function name is global to the function itself, but it need not be declared because it isn’t changed directly within the function. This isn’t used in exactly the same way, but when coded like this, the attachment of an object to the function is much more explicit (and arguably less magical).
Functions Without returns
In Python functions, return (and yield) statements are optional. When a function doesn’t return a value explicitly, the function exits when control falls off the end of the function body. Technically, all functions return a value; if you don’t provide a return statement, 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. They’re usually invoked as statements, and the None results are ignored, as 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 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
As mentioned in Common Coding Gotchas in Chapter 15, such functions do their business as a side effect and are usually designed to be run as statements, not expressions.
Enclosing Scope Loop Variables
We described this gotcha in Chapter 17’s discussion of enclosing function scopes, but as a reminder, be careful about relying on enclosing function scope lookup for variables that are changed by enclosing loops—all such references will remember the value of the last loop iteration. Use defaults to save loop variable values instead (see Chapter 17 for more details on this topic).
Chapter Summary
This chapter wrapped up our coverage of built-in comprehension and iteration tools. It explored list comprehensions in the context of functional tools and presented generator functions and expressions as additional iteration protocol tools. As a finale, we also measured the performance of iteration alternatives, and we closed with a review of common function-related mistakes to help you avoid pitfalls.
This concludes the functions part of this book. In the next part, we will study modules—the topmost organizational structure in Python, and the structure in which our functions always live. After that, we will explore classes, tools that are largely packages of functions with special first arguments. As we’ll see, user-defined classes can implement objects that tap into the iteration protocol, just like the generators and iterables we met here. Everything we have learned in this part of the book will apply when functions pop up later in the context of class methods.
Before moving on to modules, though, be sure to work through this chapter’s quiz and the exercises for this part of the book, to practice what we’ve learned about functions here.
Test Your Knowledge: Quiz
What is the difference between enclosing a list comprehension in square brackets and parentheses?
How are generators and iterators related?
How can you tell if a function is a generator function?
What does a yield statement do?
How are map calls and list comprehensions related? Compare and contrast the two.
Test Your Knowledge: Answers
List comprehensions in square brackets produce the result list all