bad recursion, still works

Fredrik Lundh fredrik at pythonware.com
Wed Jul 16 09:20:23 EDT 2008


Jeff wrote:

> Is this avoidable by using a call to list() in the definition instead?

No.  Default values are *always* evaluated when, and only when, the 
"def" statement is executed; see:

     http://docs.python.org/ref/function.html

Also note that "def" is an executable statement in Python, and that 
default arguments are evaluated in the "def" statement's environment. 
If you execute "def" multiple times, it'll create a new function object 
(with freshly calculated default values) each time.

:::

The workaround is, as others have mentioned, to use a placeholder value 
instead of modifying the default value.  None is a common value:

     def myfunc(value=None):
         if value is None:
             value = default()
         # go on an modify value

If you need to handle arbitrary objects (including None), you can use a 
sentinel object:

     sentinel = object()

     def myfunc(value=sentinel):
         if value is sentinel:
             value = default()

(in older code, written before "object" was introduced, you sometimes 
see things like "sentinel = ['placeholder']" used to create a non-false 
object with a unique identity; [] creates a new list every time it is 
evaluated.)

:::

Finally, it should be noted that more advanced Python code often uses 
this mechanism to its advantage; for example, if you create a bunch of 
UI buttons in a loop, you might try something like:

      for i in range(10):
          def callback():
              print "clicked button", i
          UI.Button("button %s" % i, callback)

only to find that all callbacks print the same value (most likely 9, in 
this case).  The reason for this is that Python's nested scopes bind to 
variables, not object values, so all callback instances will see the 
current (=last) value of the "i" variable.  To fix this, use explicit 
binding:

      for i in range(10):
          def callback(i=i):
              print "clicked button", i
          UI.Button("button %s" % i, callback)

The "i=i" part binds a local variable "i" to the *current* value of the 
outer variable "i".

Two other uses are local caches/memoization; e.g.

     def calculate(a, b, c, memo={}):
         try:
             value = memo[a, b, c] # return already calculated value
         except KeyError:
             value = do calculation on a, b, c
             memo[a, b, c] = value # update the memo dictionary
         return value

(this is especially nice for certain kinds of recursive algorithms)

and, for highly optimized code, local rebinding of global names:

     def this_one_must_be_fast(x, sin=math.sin, cos=math.cos):
         ...

Hope this helps more than it confuses.

</F>




More information about the Python-list mailing list