Search code examples
pythoninheritanceclass-methodpython-decorators

Class method attribute inheritance


I would like to build a class which has a class method returning an instance of the class instantiated with any of a pre-determined set of default values, pulled from a private class attribute. I'm finding, however, that if I create a child class with the private class attribute overriden, the class method still seems to use the parent class's version! Why is this happening?

Here's a MWE demonstrating the issue:

class MyCoolBeansClass:

    __MyCoolBeansPrivateAttr = {'FizzBuzz': {'foo': 'Fizz', 'bar': 'Buzz'},
                                 'WimWam': {'foo': 'Wim', 'bar': 'Wam'}}

    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    def __str__(self):
        return 'foo: {foo}\tbar: {bar}'.format(foo=self.foo, bar=self.bar)

    @classmethod
    def specificBeanz(cls, cbtype, printclass=False):
        """ Create a specific instance from a pre-determined set of instances """
        if printclass:
            print("Class Name: " + cls.__name__)

        if cbtype not in cls.__MyCoolBeansPrivateAttr.keys():
            raise ValueError('Invalid pre-determined Beanz, valid values are: ' + 
                             ', '.join(cls.__MyCoolBeansPrivateAttr.keys()))

        kwargs = cls.__MyCoolBeansPrivateAttr[cbtype]
        return cls(**kwargs)

class MyCubeGleamer(MyCoolBeansClass):
    __MyCoolBeansPrivateAttr = {'GleamCube': {'foo': 'Gleam', 'bar': 'Cube'},
                                'FogSnarl': {'foo': 'Fog', 'bar': 'Snarl'}}

if __name__ == "__main__":
    FizzBuzz = MyCoolBeansClass.specificBeanz('FizzBuzz', printclass=True)
    print(FizzBuzz)

    WimWam = MyCoolBeansClass.specificBeanz('WimWam', printclass=True)
    print(WimWam)

    GleamCube = MyCubeGleamer.specificBeanz('GleamCube', printclass=True)
    print(GleamCube)

Running this gives:

> testinheritance.py
Class Name: MyCoolBeansClass
foo: Fizz       bar: Buzz
Class Name: MyCoolBeansClass
foo: Wim        bar: Wam
Class Name: MyCubeGleamer
-----------------------------------------------------------------------------
Traceback (most recent call last):
  File "testinheritance.py", line 37, in <module>
    GleamCube = MyCubeGleamer.specificBeanz('GleamCube', printclass=True)
  File "testinheritance.py", line 21, in specificBeanz
    ', '.join(cls.__MyCoolBeansPrivateAttr.keys()))
ValueError: Invalid pre-determined Beanz, valid values are: FizzBuzz, WimWam
-----------------------------------------------------------------------------

As you can see, in the MyCubeGleamer.specificBeanz() call, cls.__name__ seems to be working correctly, but cls.__MyCoolBeansPrivateAttr is retrieving MyCoolBeansClass.__MyCoolBeansPrivateAttr.


Solution

  • Correct. Double initial underscores mangle the attribute name in order to prevent children from being able to access them easily. They are not, however, actually private since there is no such thing as "private" in Python. Switch the attribute name to using a single initial underscore.