TigerTV.ru
TigerTV.ru

Reputation: 1086

Python's initialization a list in a class

I don't understand python's behavior, I've got many the same errors:

File "./start.py", line 20, in print2
item.print2()
RuntimeError: maximum recursion depth exceeded

My code is the following:

#!/usr/bin/python

class Component:
    def print2(self):
        pass

class Composite(Component):
    items = []
    name = 'composite'

    def __init__(self, name):
        self.name = name

    def add(self, item):
        self.items.append(item)

    def print2(self):
        if self.items:
            for item in self.items:
                item.print2()
        else:
            print self.name

class Leaf(Component):
    name = 'leaf'

    def __init__(self, name):
        self.name = name

    def print2(self):
        print self.name

category = Composite('category1')
leaf = Composite('leaf1')
category.add(leaf)
leaf = Leaf('leaf2')
category.add(leaf)

category.print2()

When I add self.items = [] in the constructor ( __init__ ), it works fine. Could you explain the behavior?

Upvotes: 3

Views: 14595

Answers (3)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95873

Because all your objects are sharing items... that is a class variable, i.e. "static"

class Foo:
    # <anything assigned here is static>
    static_var1 = 'foo'
    def some_method(self, bar):
        self.x = bar # this is an *instance* variable

so in:

def print2(self):
    if self.items:
        for item in self.items:
            item.print2()

It iterates over the static items. Note:

In [11]: category.items
Out[11]: [<__main__.Composite at 0x103078978>, <__main__.Leaf at 0x103078ac8>]

In [12]: category.items[0].name
Out[12]: 'leaf1'

When you hit that first item, it calls item.print2(), which then starts iterating over items, and it hits 'leaf1' again, which again calls item.print2()...

Note, Python's object model is pretty simple, instance.method() is equivalent to InstanceClass.method(instance), i.e., the only "magic" that happens in methods is that if a method is accessed through an instance, what is actually returned is a bound method, which has bound the instance as the first argument to the function. So note:

In [15]: class Foo:
    ...:     def mutate(self, x):
    ...:         self.foo = x
    ...:

In [16]: f = Foo()

In [17]: f.mutate(42)

In [18]: f.foo
Out[18]: 42

In [19]: Foo.mutate(f, 99)

In [20]: f.foo
Out[20]: 99

Note, Foo.mutate is just a function. It doesn't care about what self actually is:

In [21]: class Bar:
    ...:     pass
    ...:

In [22]: b = Bar()

In [23]: Foo.mutate(b, 'foooooo')

In [24]: b.foo
Out[24]: 'foooooo'

Little bit of arcana, every single time a method is accessed, an entirely new bound-method object is created:

In [34]: f.mutate
Out[34]: <bound method Foo.mutate of <__main__.Foo object at 0x1030641d0>>

In [35]: methods = f.mutate, f.mutate, f.mutate

In [36]: [hex(id(m)) for m in methods]
Out[36]: ['0x10581de08', '0x10581d208', '0x105abc988']

Which leads to the seemingly weird:

In [37]: f.mutate is f.mutate
Out[37]: False

However, that wraps the same underlying function, Foo.mutate, which doesn't change, and is simply an attribute like any other on a class:

In [38]: Foo.mutate is Foo.mutate
Out[38]: True

Upvotes: 6

Alter
Alter

Reputation: 3464

What's happening here is two things
1. items was created as a class variable
2. leaf1 was added as a Component: Composite('leaf1') and therefore possesses the class variable items

These two together mean that leaf1.print2() will recursively call itself forever

Upvotes: 2

0x5453
0x5453

Reputation: 13589

items is a class variable, so it is shared by every instance of Composite. When you append into items, all instances get that item. So after adding the leafs, you have:

   Type                    Composite.items
------------------------------------------
 Composite    category1    [leaf1, leaf2]
                  |
 Composite      leaf1      [leaf1, leaf2]
                  |
      Leaf      leaf2

If print2 is called on leaf1, it will try to call print2 on each of the items in the class, which includes leaf1 again, and again, in an infinite recursion.

If you instead initialize self.items = [] inside __init__, it will be an instance variable instead of a class variable, so each Composite will have its own list of items.

Upvotes: 7

Related Questions