2

Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument

I'm seeing some strange behavior related to variable scope while trying to run some unit tests (python 2.7):

I have a module containing a class called 'Dummy' which has a single list as an instance variable. In my unit tests for this module, I have two separate test cases that each create their own separate Dummy instance. Nevertheless, the Dummy instance in the test that runs second seems to contain the list that was created in the first test. Here's the code:

mymodule.py:

class Dummy(object):
    def __init__(self, mylist=[]):
        self.mylist = mylist

test_mymodule.py:

class TestDummy(unittest.TestCase):
    def testDummy1(self):
        d1 = Dummy()          # d1.mylist is empty, []
        d1.mylist.append(1)   # mylist = [1]
        self.assertEqual(d1.mylist, [1])

    def testDummy2(self):
        d2 = Dummy()          # d2.mylist = [1] !
        d2.mylist.append(2)   # d2.mylist = [1,2]
        self.assertEqual(d2.mylist, [2]) # FAILS

Additional details:

  1. If I put a print mylist as the first line in Dummy's init method, sure enough, on the second test run, it prints [1] as if mylist=[1] was passed in as an argument

  2. If I explicitly specify dd = Dummy(mylist=[]), then the problem disappears. It seems to have something to do with default argument values, but why in any case it should spill over into a different scope is unclear to me.

Community
  • 1
  • 1
mindthief
  • 12,755
  • 14
  • 57
  • 61
  • 3
    http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects – Mark Dickinson Nov 23 '12 at 09:10
  • @MarkDickinson That seemed different to me at first because I'm not setting the value of the argument inside the function like in that example. But I think what's happening is that once the instance variable is set equal to the argument in __init__, since it's by reference, when d1.mylist is changed in the original scope, it's actually changing the value of the default argument. Which is the problem described in your link. Does this sound right to you? – mindthief Nov 23 '12 at 09:21
  • @mindthief In your case the default argument is created when the class is created. So, all instances that use the default value actually share the same list. Never, ever use a mutable type as default. If you want an empty iterable instead of `None`, then use an empty tuple `()`. – Bakuriu Nov 23 '12 at 09:28
  • Right I get that, but what I meant was -- what is the mechanism by which the default argument value is changing, since I am not explicitly changing it? Note that the variable for the default argument is not the same as the instance variable. I could have for example called the argument 'somelist' and the instance variable 'mylist'. So I assume my setting `self.mylist = somelist`, and then changing d1.mylist in the calling context, is the culprit... – mindthief Nov 23 '12 at 09:34
  • 1
    @mindthief: Pretty much, yes. As a result of that assignment, `d1.mylist` (when `d1` is defined), `d2.mylist` (likewise) and the `__init__` method default are all the same object. So when you modify `d1.mylist` in `testDummy1`, you're modifying that default value. And then `d2.mylist` picks up the modified default. – Mark Dickinson Nov 23 '12 at 09:39
  • yep that definitely makes sense, thanks! – mindthief Nov 23 '12 at 09:41

1 Answers1

3

This has nothing to do with unit tests, but everything to do with how Python handles default arguments to functions. Function default arguments are evaluated once when the def statement is executed.

That means you have created one list which is used a default argument to __init__ and that list will live as long as the function exists.

It is easy to avoid by using None (or another sentinel value) as the default:

class Dummy(object):
    def __init__(self, mylist=None):
        self.mylist = mylist if mylist is not None else []
Duncan
  • 92,073
  • 11
  • 122
  • 156