Bizarre behavior with mutable default arguments
Arnaud Delobelle
arnodel at googlemail.com
Tue Jan 1 15:26:23 EST 2008
On Jan 1, 6:48 pm, "Gabriel Genellina" <gagsl-... at yahoo.com.ar> wrote:
> En Tue, 01 Jan 2008 15:45:00 -0200, bukzor <workithar... at gmail.com>
> escribi�:
[...]
> >> I'm confused by what you mean by 'early binding'. Can you give a quick-
> >> n-dirty example?
> > Is an 'early bound' variable synonymous with a 'static' variable (in
> > C)?
>
> No. It means, in which moment the name gets its value assigned. Usually
> Python does "late binding", that is, names are resolved at the time the
> code is executed, not when it's compiled or defined.
> Consider this example:
>
> z = 1
> def foo(a)
> print a+z
> foo(3) # prints 4
> z = 20
> foo(3) # prints 23
>
> The second time it prints 23, not 4, because the value for z is searched
> when the code is executed, so the relevant value for z is 20.
> Note that if you later assign a non-numeric value to z, foo(3) will fail.
>
> If you want to achieve the effect of "early binding", that is, you want to
> "freeze" z to be always what it was at the time the function was defined,
> you can do that using a default argument:
>
> z = 1
> def foo(a, z=z)
> print a+z
> z = None
> foo(3) # prints 4
>
> This way, foo(3) will always print 4, independently of the current value
> of z. Moreover, you can `del z` and foo will continue to work.
>
> This is what I think Chris Mellon was refering to. This specific default
> argument semantics allows one to achieve the effect of "early binding" in
> a language which is mostly "late binding". If someone changes this, he has
> to come with another way of faking early binding semantics at least as
> simple as this, else we're solving an [inexistant for me] problem but
> creating another one.
>
> --
> Gabriel Genellina
Let me say again that I believe the current behaviour to be the
correct one. But I don't think this 'early binding' is critical for
this sort of example. There are lots of ways to solve the problem of
having persistent state across function calls, for example:
* using classes
* using modules
* or simply nested functions:
def getfoo(z):
def foo(a):
print a + z
return foo
>>> z = 1
>>> foo = getfoo(z)
>>> z = None
>>> foo(3)
4
And with nonlocal, we could even modify z inside foo and this change
would persist across calls. This will be a much cleaner solution than
the current def bar(x, y, _hidden=[startvalue]).
Also, note that it's easy to implement default arguments in pure
python-without-default-arguments using a decorator:
def default(**defaults):
defaults = defaults.items()
def decorator(f):
def decorated(*args, **kwargs):
for name, val in defaults:
kwargs.setdefault(name, val)
return f(*args, **kwargs)
return decorated
return decorator
Here is your example:
>>> z=1
>>> @default(z=z)
... def foo(a, z):
... print a + z
...
>>> z=None
>>> foo(3)
4
Another example, using mutables:
>>> @default(history=[])
... def bar(x, history):
... history.append(x)
... return list(history)
...
>>> map(bar, 'spam')
[['s'], ['s', 'p'], ['s', 'p', 'a'], ['s', 'p', 'a', 'm']]
--
Arnaud
More information about the Python-list
mailing list