Search code examples
pythoninheritancestaticpython-decorators

Force decorator to extend static field on inherited class only even if not explicitly overridden


I'm working on writing a decorator for use on an inherited class, and that decorator should add in some values to a list that is a field on a base class. The problem I've discovered, is that unless that class definition explicitly lists that field, the decorator ends up modifying the base class instead, which in turn adds the values to all of the other classes that inherit from that class as well. It seems as though the fields are not getting defined on the class but instead manipulate the fields on the Base class. Is there a way to force the inherited classes to have their own mutable fields (that start with the same value as the Base fields) that don't affect the value of the Base class fields?

To be sure, I could find ways around this by not doing what I'm doing, but I am wondering if there is a way to make what I want to do work, preferably by modifying the decorator. In addition to a solution, an explanation of why this is and what I'm not understanding about python, static field inheritance, and decorators would be great!

I've given basic cases below. Note that the this prints lines are if the Extension# classes are used individually, not with them side by side like they are shown.

def add_values(cls):
    cls.my_list.append(1)
    return cls

class Base(object):
    my_list = []

### in the following case, the definition of Base actually changes
@add_values
class Extension1(Base):

    def append_1(self):
        self.my_list.append(1)
        print(self.my_list) ### this prints [1, 1]
        print(Base.my_list) ### this prints [1, 1]


### in this case, the definition of Base is fine and it only modifies
### the field on this class definition, which is what I'd expect.
@add_values
class Extension2(Base):
    my_list = []

    def append_1(self):
        self.my_list.append(1)
        print(self.my_list) ### this prints [1, 1]
        print(Base.my_list) ### this prints []

Solution

  • Classes don't make copies of values they inherit from their base classes. It's just the attribute look up system that searches to find them where they were previously defined. What you should probably do is change your mutation of the my_list class variable into a non-mutating version:

    def add_values(cls):
        cls.my_list = cls.my_list + [1]  # like cls.my_list.append(1), but makes a new list
        return cls
    

    This creates a new list with the 1 added and binds it to add_values, whether you're decorating the base class or a derived one.