Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function calls are bleeding scope, stateful, failing to initialize parameters? [duplicate]

Tags:

python

scope

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?

like image 463
gotgenes Avatar asked Jun 06 '09 06:06

gotgenes


2 Answers

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

like image 94
Kai Avatar answered Sep 20 '22 09:09

Kai


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.

like image 36
Unknown Avatar answered Sep 20 '22 09:09

Unknown