1

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?

1
  • Since Y doesn't have an items attribute it searches the superclass and finds X's. What are you trying to achieve, overall? Commented Mar 7, 2013 at 6:13

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Your explanation makes perfect sense. Your solution works well but is there any magical way to achieve this effect without having to re-specify the attribute for every subclass? The reason is because in my application, the subclasses are defined by users of the applications, and I'm trying to minimize the amount of boilerplate code they need to write.
@trinth: See my edited answer. Since you're already writing a decorator, you can just have your decorator create the new attribute on the class.
I think this is as elegant as it gets!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.