Reputation: 6047
I have the following code:
class X(object):
items = []
class Y(X):
pass
class Z(X):
pass
I'm trying to create a decorator that will add an object only to Y's "item" list. So the following code,
@addItems
class Y(X):
pass
should append something (for sake of example, let's say the integer 1) to Y.items but not modify X.items, Z.items, or any other subclasses of X. Basically, Y.items should have items = [1]
but X.items and Z.items should both have items = []
. This is the decorator function definition I created that was supposed to achieve that goal:
def addItems(cls):
cls.items.append(1)
return cls
This does not work though - when I use the decorator, it ends up modifying the "items" list for X and all other subclasses of X. What gives? I know that class attributes in Python are shared among all instances of X, however, I did not know that subclasses were affected. Perhaps this behavior occurs only for mutable attributes?
What is the correct way of achieving my goal?
Upvotes: 1
Views: 184
Reputation: 251428
The issue is not with the decorator. You have a misunderstanding about where/how the items
attribute is stored. Y
and Z
do not have their own items
attribute. Rather, there is only one items
attribute. You defined it on X
, and if you do Y.items
(or Z.items
), since the attribute is not found on the subclass, it is dynamically looked up on the superclass. If you do X.items is Y.items
you will see that it evalutes to True --- there is only one items
list. That means that when you modify it, all classes see the modification.
If you want to give each class its own items
list, you have to, well, give each class its own items
list:
class X(object):
items = []
class Y(object):
items = []
class Z(object):
items = []
Python never copies anything unless you tell it to. Just because you define a subclass that inherits from a class with an attribute, that doesn't mean Python makes a new copy of the attribute for the subclass; it just re-uses the same object from the superclass.
If you want to "magically" give each class its own items attribute, you could do it inside the decorator:
def addItems(cls):
cls.items = []
cls.items.append(1)
return cls
This first sets the class's items
to an empty list. This actually sets the attribute on the particular class it operates on, not on any sub- or superclass. Now when you access that, it will work as you expect.
Upvotes: 2