Search code examples
pythonenumspython-3.6default-valuetype-hinting

Enum Class method with default Enum value fails


I am well aware that if you have a class method that uses the enum's class name for type hinting, there is a hack to get it to work for Python 3.6 and below.

Instead of...

class Release(Enum):
   ...
   @classmethod
   def get(cls, release: Release):
      ...

You need to use the string value like so...

class Release(Enum):
   ...
   @classmethod
   def get(cls, release: "Release"):
      ...

I believe in Python 3.7 and above there is a pythonic way around this "hack" where you don't have to use quotes. The reason is something along the lines of "the class doesn't exist yet until all the methods and varibles are done first". Since the class doesn't exist yet, I can't use the class name yet, and have to use the quoted string as a hack.

However, I am trying to go one step further and use a default value. And that doesn't work. Is there a pythonic approach for Python 3.6 that isn't a hack? Also, is there a fix in python 3.7 and above?

Code

from enum import Enum

class Release(Enum):
    Canary = (1, [])
    Beta = (2, [1])
    RC = (3, [2, 1])
    Stable = (4, [3, 2, 1])

    def __new__(cls, value, cascade):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value] # This would technically be a list of all releasese in this enum. This is just to emulate different values
        obj.cascade = cascade
        return obj

    @classmethod
    def get_all_releases(cls, release: "Release" = Canary):  # Default Value = Release.Canary
        return release.current


print(Release.get_all_releases(Release.Canary))
print(Release.get_all_releases(Release.Beta))
print(Release.get_all_releases(Release.RC))
print(Release.get_all_releases(Release.Stable))

# Error. Even with default value
# print(Release.get_all_releases())

With this code I get the following error message

AttributeError: 'tuple' object has no attribute 'current'

That is because it returns the tuple of Canary instead of the actual value.


Solution

  • While it's definitely a workaround, this seemed to work well for me:

    @classmethod
    def get_all_releases(cls, release: "Release" = Canary):  # Default Value = Release.Canary
        if release == (Release.Canary.value,):
            return Release.Canary.current
        return release.current
    

    It does work for whatever value you assign to Canary. So as long as that is your default I believe it will work.


    To be more general so that you'd only have to adjust the default in the class definition instead of each function, you could do it as follows:

    class Release(Enum):
        Canary = 6,
        Beta = 2,
        RC = 3,
        Stable = 4
        default = Canary
    
        ...
    
        @classmethod
        def get_all_releases(cls, release: "Release" = default):
            if release == (Release.Canary.value,):
                return Release.Canary.current
            return release.current