Search code examples
pythoninheritancepython-3.4class-variables

Class variable V3 dependent on V1 and V2. How to define V3 in children classes


Parent class is inherited by multiple other classes.

class Parent(object):

    V_1 = set()
    V_2 = set()

    ALL_V_ELEMENTS = V_1 | V_2

class Child1(Parent):
    V_1 = {1, }
    V_2 = {4, 7, 10}

class Child2(Parent):
    V_1 = {'a', 'b'}
    V_2 = {'a', 'c'}

V_1 and V_2 are different in each child (also they don't change once the class is created).

Using the code below I get the same value for ALL_V_ELEMENTS:

print(Parent.ALL_V_ELEMENTS)  # prints: set()
print(Child1.ALL_V_ELEMENTS)  # prints: set()
print(Child2.ALL_V_ELEMENTS)  # prints: set()

Which is something I don't want. What I need is this:

print(Parent.ALL_V_ELEMENTS)  # prints: set()
print(Child1.ALL_V_ELEMENTS)  # prints: {1, 10, 4, 7}
print(Child2.ALL_V_ELEMENTS)  # prints: {'a', 'c', 'b'}

To achieve my goal I can define the classes as follows:

class Child1(Parent):
    V_1 = {1, }
    V_2 = {4, 7, 10}
    ALL_V_ELEMENTS = V_1 | V_2

class Child2(Parent):
    V_1 = {'a', 'b'}
    V_2 = {'a', 'c'}
    ALL_V_ELEMENTS = V_1 | V_2

However, copy-pasting ALL_V_ELEMENTS = V_1 | V_2 on every child of Parent doesn't seem like a good idea.

Another alternative would be defining Parent in a different way:

class Parent(object):

    V_1 = set()
    V_2 = set()

    def __init__(self):
        self.ALL_V_ELEMENTS = self.V_1 | self.V_2

This would do the | operation on every instance which is redundant.


Is there a better way to achieve my goal?


Solution

  • You could define it as a property:

    class Parent(object):
    
        V_1 = set()
        V_2 = set()
    
        @property
        def ALL_V_ELEMENTS(self):
           return V_1 | V_2
    

    This would, however, re-calculate the set each time. Having the __init__ create the set means it'll be created for every instance.

    You could calculate the set in a metaclass, so it is only produced when the class object is produced:

    class AllVMeta(type):
        def __new__(typ, name, bases, attrs):
            cls = super(AllVMeta, typ).__new__(typ, name, bases, attrs)
            cls.ALL_V_ELEMENTS = cls.V_1 | cls.V_2
            return cls
    

    This metaclass adds a ALL_V_ELEMENTS union to any subclass; use it like this:

    class Parent(object, metaclass=AllVMeta):
        V_1 = set()
        V_2 = set()
    
    class Child1(Parent):
        V_1 = {1, }
        V_2 = {4, 7, 10}
    
    class Child2(Parent):
        V_1 = {'a', 'b'}
        V_2 = {'a', 'c'}
    

    Demo:

    >>> class AllVMeta(type):
    ...     def __new__(typ, name, bases, attrs):
    ...         cls = super(AllVMeta, typ).__new__(typ, name, bases, attrs)
    ...         cls.ALL_V_ELEMENTS = cls.V_1 | cls.V_2
    ...         return cls
    ...
    >>> class Parent(object, metaclass=AllVMeta):
    ...     V_1 = set()
    ...     V_2 = set()
    ...
    >>> class Child1(Parent):
    ...     V_1 = {1, }
    ...     V_2 = {4, 7, 10}
    ...
    >>> class Child2(Parent):
    ...     V_1 = {'a', 'b'}
    ...     V_2 = {'a', 'c'}
    ...
    >>> Child1.ALL_V_ELEMENTS
    {1, 10, 4, 7}
    >>> Child2.ALL_V_ELEMENTS
    {'a', 'c', 'b'}