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):The freshdefaults decorator presented in this recipe
if pkg is None:
pkg = [ ]
pkg.append(item)
return pkg
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):As you see, the functionality of plus100 and
... 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>
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):With this change in make_adder, the previous snippet
def adder(augend):
return augend+addend
adder._ _name_ _ = 'add_%s' % (addend,)
return adder
would now produce more useful output:
>>> print plus100, plus_23Unfortunately, in Python 2.3, you cannot assign to the _
<function add_100 at 0x386530> <function add_23 at 0x3825f0>
_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.