Learning Python - Mark Lutz [287]
Finally, keep the timing module we wrote here filed away for future reference—we’ll repurpose it to measure performance of alternative numeric square root operations in an exercise at the end of this chapter. If you’re interested in pursuing this topic further, we’ll also experiment with techniques for timing dictionary comprehensions versus for loops interactively.[46]
* * *
[45] Also note how we must pass functions in to the timer manually here. In Chapters 38 and 39 we'll see decorator-based timer alternatives with which timed functions are called normally.
[46] For more fun, apply a simple user-defined function like def f(I): return(I) in all five iteration techniques timed. The results are similar to using a built-in function like abs: among the five iteration techniques, map is fastest today if all five call a function, built-in or not, but slowest when the others do not. That is, map appears to be slower simply because it requires function calls, and function calls are relatively slow in general. Since map can't avoid calling functions, it can lose by association.
Function Gotchas
Now that we’ve reached the end of the function story, let’s review some common pitfalls. Functions have some jagged edges that you might not expect. They’re all obscure, and a few have started to fall away from the language completely in recent releases, but most have been known to trip up new users.
Local Names Are Detected Statically
As you know, Python classifies names assigned in a function as locals by default; they live in the function’s scope and exist only while the function is running. What you may not realize is that Python detects locals statically, when it compiles the def’s code, rather than by noticing assignments as they happen at runtime. This leads to one of the most common oddities posted on the Python newsgroup by beginners.
Normally, a name that isn’t assigned in a function is looked up in the enclosing module:
>>> X = 99
>>> def selector(): # X used but not assigned
... print(X) # X found in global scope
...
>>> selector()
99
Here, the X in the function resolves to the X in the module. But watch what happens if you add an assignment to X after the reference:
>>> def selector():
... print(X) # Does not yet exist!
... X = 88 # X classified as a local name (everywhere)
... # Can also happen for "import X", "def X"...
>>> selector()
...error text omitted...
UnboundLocalError: local variable 'X' referenced before assignment
You get the name usage error shown here, but the reason is subtle. Python reads and compiles this code when it’s typed interactively or imported from a module. While compiling, Python sees the assignment to X and decides that X will be a local name everywhere in the function. But when the function is actually run, because the assignment hasn’t yet happened when the print executes, Python says you’re using an undefined name. According to its name rules, it should say this; the local X is used before being assigned. In fact, any assignment in a function body makes a name local. Imports, =, nested defs, nested classes, and so on are all susceptible to this behavior.
The problem occurs because assigned names are treated as locals everywhere in a function, not just after the statements where they're assigned. The previous example is ambiguous: was the intention to print the global X and create a local X, or is this a real programming error? Because Python treats X as a local everywhere, it’s seen as an error; if you mean to print the global X, you need to declare it in a global statement:
>>> def selector():
... global X # Force X to be global (everywhere)
... print(X)
... X = 88
...
>>> selector()
99
Remember, though, that this means