2

I have found strange Python's behavior (or probably I don't understand how inheritance and/or default values of attributes works).

For given code

class A(object):
    def __init__(self, s):
        self.s = s
        print "in init", self.s

class B(A):
    def __init__(self, s = set()):
        super(B, self).__init__(s)
        print "after super", self.s
        self.s.add('foo')
        print '--------------'

if __name__ == "__main__":
    a = B()
    b = B()

I get following output:

in init set([])
after super set([])
--------------
in init set(['foo']) # Why it has value set in other object?!
after super set(['foo'])
--------------

Of course desired behavior would be to init self.s in second object (b) with an empty set, but for unknown reason it gets state from previous object. Why does it happen? How to obtain desired behavior?

Thanks!

radious
  • 812
  • 6
  • 21

2 Answers2

9

You've fallen prey to the mutable default argument trap. The set() that you specified as the default in B's __init__ is the same object that will be passed to each new B that you create. Putting it into self doesn't make a new copy, it just creates a reference to the same object.

You probably want to make a copy in each new object, so do it explicitly:

class A(object):
    def __init__(self, s):
        self.s = copy.copy(s)
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Using `copy()` works in this particular case, though the most common Python pattern is to use `None` as the default, as I give an example of below. – Matt Good Mar 11 '13 at 16:36
  • @MattGood, I made my recommendation based on a gut feeling that a copy would be most appropriate even when an explicit argument was given. It's unusual for an object to hold and modify a reference to something outside of itself. – Mark Ransom Mar 11 '13 at 16:45
1

When you set a default for an argument, the value is computed at the time you define the function, not when you call it. So, it's using the same set() that was created when you defined the class, and adding more and more items to it.

Instead, the typical Python pattern is to use None as the default, and then explicitly initialize a new set() (or list, dict, or any object you want) within the body of __init__.

class B(A):
  def __init__(self, s=None):
    if s is None:
      s = set()
Matt Good
  • 3,027
  • 22
  • 15