Search code examples
pythonenumspython-class

How to correctly define a classmethod that accesses a value of a mangled child attribute?


In Python, how do I correctly define a classmethod of a parent class that references an attribute of a child class?

from enum import Enum


class LabelledEnum(Enum):
    @classmethod
    def list_labels(cls):
        return list(l for c, l in cls.__labels.items())


class Test(LabelledEnum):
    A = 1
    B = 2
    C = 3

    __labels = {
        1: "Label A",
        2: "Custom B",
        3: "Custom label for value C + another string",
    }


print(Test.list_labels())
# expected output
# ["Label A", "Custom B", "Custom label for value C + another string"]

In the code above I expect that Test.list_labels() will correctly print out the labels, however because the __labels dictionary is defined with the double underscore, I cannot access it correctly.

The reason I wanted to have double underscore is to make sure that the labels would not show up when iterating over the enumerator, e.g. list(Test) should not show the dictionary containing labels.


Solution

  • Note: This answer was originally a comment to the question.

    I strongly advise taking a different approach, like:


    Python 3.11+

    I do not suggest using private names. That being said, if for some reason you must use private names and you can't use the @enum.nonmember decorator, which is a much better approach. Then the following will work in Python 3.11+.

    The _Private__names section in Enum HOWTO states:

    Private names are not converted to enum members, but remain normal attributes.

    You could do something really ugly like:

    getattr(cls, f"_{cls.__name__}__labels", {})
    
    from enum import Enum
    
    class LabelledEnum(Enum):
        @classmethod
        def list_labels(cls):
            # account for private name mangling
            labels = getattr(cls, f"_{cls.__name__}__labels", {})
            return list(l for c, l in labels.items())
    
    class Test(LabelledEnum):
        A = 1
        __labels = { 1: "Label A" }
    
    
    print(Test.list_labels())
    # ['Label A']
    

    Python < 3.11

    In Python versions less than 3.11, __labels will become the _Test__labels enum member of Test. And the above code will raise an error, due to getattr returning the enum rather than a dict.

    print(Test.__members__)
    #{'A': <Test.A: 1>, '_Test__labels': <Test._Test__labels: {1: 'Label A'}>}
    
    print(type(Test._Test__labels))
    #<enum 'Test'>
    

    Also, in Python 3.9 and 3.10, using private names in an enum class will cause a DeprecationWarning, similar to the following:

    DeprecationWarning: private variables, such as '_Test__labels', will be normal attributes in 3.10