Reputation: 1086
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
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
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
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 leaf
s, 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