Recipe20.1.Getting Fresh Default Values at Each Function Call


Recipe 20.1. Getting Fresh Default Values at Each Function Call

Credit: Sean Ross

Problem

Python computes the default values for a function's optional arguments just once, when the function's def statement executes. However, for some of your functions, you'd like to ensure that the default values are fresh ones (i.e., new and independent copies) each time a function gets called.

Solution

A Python 2.4 decorator offers an elegant solution, and, with a slightly less terse syntax, it's a solution you can apply in version 2.3 too:

import copy def freshdefaults(f):     "a decorator to wrap f and keep its default values fresh between calls"     fdefaults = f.func_defaults     def refresher(*args, **kwds):         f.func_defaults = deepcopy(fdefaults)         return f(*args, **kwds)     # in 2.4, only: refresher._ _name_ _ = f._ _name_ _     return refresher # usage as a decorator, in python 2.4: @freshdefaults def packitem(item, pkg=[  ]):     pkg.append(item)     return pkg # usage in python 2.3: after the function definition, explicitly assign: # f = freshdefaults(f)

Discussion

A function's default values are evaluated once, and only once, at the time the function is defined (i.e., when the def statement executes). Beginning Python programmers are sometimes surprised by this fact; they try to use mutable default values and yet expect that the values will somehow be regenerated afresh each time they're needed.

Recommended Python practice is to not use mutable default values. Instead, you should use idioms such as:

def packitem(item, pkg=None):     if pkg is None:         pkg = [  ]     pkg.append(item)     return pkg

The freshdefaults decorator presented in this recipe provides another way to accomplish the same task. It eliminates the need to set as your default value anything but the value you intend that optional argument to have by default. In particular, you don't have to use None as the default value, rather than (say) square brackets [ ], as you do in the recommended idiom.

freshdefaults also removes the need to test each argument against the stand-in value (e.g., None) before assigning the intended value: this could be an important simplification in your code, where your functions need to have several optional arguments with mutable default values, as long as all of those default values can be deep-copied.

On the other hand, the implementation of freshdefaults needs several reasonably advanced concepts: decorators, closures, function attributes, and deep copying. All in all, this implementation is no doubt more difficult to explain to beginning Python programmers than the recommended idiom. Therefore, this recipe cannot really be recommended to beginners. However, advanced Pythonistas may find it useful.

Setting the Name of a Function

If an outer function just returns an inner function (often a closure), the name of the returned function object is fixed, which can be confusing when the name is shown during introspection or debugging:

>>> def make_adder(addend): ...   def adder(augend): return augend+addend ...   return adder ... >>> plus100 = make_adder(100) >>> plus_23 = make_adder(23) >>> print plus100(1000), plus_23(1000) 1100 1023 >>> print plus100, plus_23 <function adder at 0x386530> <function adder at 0x3825f0>

As you see, the functionality of plus100 and plus_23 is correct (they add 100 and 23 to their argument, respectively). Confusingly, however, their names are both 'adder', even though they are different functions. In Python 2.4, you can solve the problem by setting the _ _name_ _ attribute of the inner function right after the end of the inner function's def statement, and before the return statement from the outer function:

def make_adder(addend):     def adder(augend):         return augend+addend     adder._ _name_ _ = 'add_%s' % (addend,)     return adder

With this change in make_adder, the previous snippet would now produce more useful output:

>>> print plus100, plus_23 <function add_100 at 0x386530> <function add_23 at 0x3825f0>

Unfortunately, in Python 2.3, you cannot assign to the _ _name_ _ attribute of a function object; in that release, the attribute is read-only. If you want to obtain the same effect in Python 2.3, you must follow a more roundabout route, making and returning a new function object that differs from the other only in name:

import new def make_adder(addend):   def adder(augend): return augend+addend   return new.function(adder.func_code, adder.func_globals, 'add_%s' % (addend,),                       adder.func_defaults, adder.func_closure)


See Also

Python Language Reference documentation about decorators; Python Language Reference and Python in a Nutshell documentation about closures and function attributes; Python Library Reference and Python in a Nutshell documentation about standard library module copy, specifically function deepcopy.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net