Python function calls are bleeding scope, stateful

2019-06-22 06:36发布

问题:

Before I have the audacity to file a bug report, I thought I'd check my assumptions among wiser Pythonistas here. I encountered a baffling case today, so I whittled it down to a toy example, shown below:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

"""
A little script to demonstrate that a function won't re-initialize its
list parameters between calls, but instead allows them to retain state.

"""

def bleedscope(a=[], b=[]):
    """
    On each call, unless explicitly passed, both `a` and `b` should be
    initialized as empty lists.

    """

    c = a
    if b:
        c.extend(b)
    return len(c)


x = bleedscope(b=[1])
print x     # Should be 1, as expected.
x = bleedscope(b=[2])
print x     # Expect also to be 1, but it's 2. `a` is retained.
x = bleedscope(a=[1])
print x     # Now 1 as expected.
x = bleedscope(b=[3])
print x     # 1 as expected? No, it's 3! Insanity!

I thought function arguments were local in scope to the function, and were garbage-collected at the end of a function call, never to retain state between them. I have tested the above script on Python 2.5.2 and Python 2.6.1, though, and my understanding does not the results. Argument a certainly retains state between most of these calls; the most perplexing one being the final call to bleedscope, where it skips the state of the previous call and goes back to the state at the end of the second (i.e., [1, 2]). [I suggest running this in your favorite debugger to see for yourself. If you don't have one, I suggest Winpdb as a solid FOSS standalone Python debugger.]

What's going on here?

回答1:

In Python default parameter values only get initialized when the def call is parsed. In the case of an object (such as your lists), it gets reused between calls. Take a look at this article about it, which also provides the necessary workaround:

http://effbot.org/zone/default-values.htm



回答2:

This is your problem:

def bleedscope(a=[], b=[]):

it should be

def bleedscope(a=None, b=None):
    if a is None: a = []
    if b is None: b = []

The default parameters are only executed once when the function is parsed, thus using the same 2 lists every time.



回答3:

It's a FAQ.



回答4:

Funnily enough, your input and your output are quite similar, for totally accidental reasons.

Actually what happens with Python is that the default values for a and b in your method declaration are "static" values. They are instanciated once at the method definition. So your default "a" is pushed each time you do not pass an "a" as argument.

Put a "print a" at the beginning of your method to see that happen.



标签: python scope