pythonpython-3.xinheritancesubclassingclass-instance-variables

Set Subclass Variables/Attributes from Within Base Class


Is it possible to dynamically create/set variables within a subclass within the base class across multiple subclasses without affecting the other subclasses?

For example, take this code here:

class Base:
    @classmethod
    def __init__(cls,v):
        cls.v=v

class sub1(Base):
    Base.__init__(1)

class sub2(Base):
    Base.__init__(5)

In that code, when sub1 is created, its v attribute is equal to 1. But when sub2 is created, the v attribute of both sub1 and sub2 becomes 5. I think I might know why this is. I assume the @classmethod of the base class is not actually setting the attribute of the subclass, but instead its own attribute. Then that attribute is inherited in the subclasses. My question is: how can I use this kind of inheritance to set attributes of subclasses, not attributes of the base class which are inherited.

In other words, I would like to be able to use a similar structure, if possible (or at least a simple one) in order to accomplish setting attributes in subclasses that are specific to the subclasses and are not simply globally inherited from the base class.

Is this even possible?


Solution

  • I don't know if it can be easily accomplished any other way, but I've come up with a solution myself using a metaclass.

    Solution:

    class MetaBase(type):
        def __init__(cls, name, bases, namespace):
            super(MetaBase, cls).__init__(name, bases, namespace)
            if '_superclass' in namespace:  # A special attribute to distinguish between superclasses and subclasses
                if '_attrnames' not in namespace:
                    raise AttributeError('"_attrnames" needs to be defined as a class attribute in a superclass.')
            else:
                if 'classvars' in namespace:  # Allow for define all required class attributes in one iterable attribute
                    for attrname, attr in zip(getattr(cls.__mro__[1], '_attrnames'), getattr(cls, 'classvars')):  # Get all the varnames in the superclass's "varnames", as well as the values
                        setattr(cls, attrname, attr)
                        namespace[attrname] = attr
                    delattr(cls, 'classvars')
                else:
                    for attrname in getattr(cls.mro()[1], '_attrnames'):
                        if attrname not in namespace:
                            raise AttributeError('"%s" not defined, but is required.' % attrname)
    
    class Base(metaclass=MetaBase):
        _superclass = True  # The value of this attribute doesn't matter
        _attrnames = ('a','b','c')
    
    class Sub1(Base):
        a = 1
        b = 2
        c = 3
    
    class Sub2(Base):
        classvars = (1, 2, 3)