Enum Class method with default Enum value fails

大城市里の小女人 提交于 2020-01-24 15:29:28

问题


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.


回答1:


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



回答2:


Took a hint from @ufoxDan with his answer, but tried to make it less workaround-y and more natural.

Basically, I started by checking the type(release) before returning and noticed that I got the results of..

<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<class 'tuple'>

I noticed that if the type was Release then I can just execute the code, however if it was anything else, like None instead of the uncreated Canary type, then I could assume it was asking for Canary. So I did the following...

@classmethod
def get_all_releases(cls, release: "Release" = None):
   if type(release) is Release:
       return release.current
   return Release.Canary.current

# Now these all work
print(Release.get_all_releases())
print(Release.get_all_releases(Release.Canary))
print(Release.get_all_releases(Release.Stable))

This appears to be the most pythonic way of achieving the results. This seems to also be the best way while reading the code and without repeating code. Anyone should be able to implement something similar it seems.




回答3:


There are a couple things you can do in your Release Enum to make life easier, the first being a technique shown here:

    def __new__(cls, value, cascade):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value]      # not sure what this should actually be

        # if always the previous versions (don't need cascade defined)
        obj.cascade = sorted(list(cls), reverse=True)

        # if some already defined subset (need cascade defined)
        obj.cascade = [cls._value2member_map_(c) for c in cascade]

        return obj

The second technique can go two ways -- your default is always the first Enum member:

    @classmethod
    def get_all_releases(cls):
        return list(cls[0]).current

or, if the default could be any member, then something similar to this answer should work:

class add_default:
    """
    add DEFAULT psuedo-member to enumeration; use first member if none specified
    (default should be name of member)
    """
    def __init__(self, default=''):
        self._default = default
    def __call__(self, enumeration):
        if self._default:
            member = enumeration[self._default]
        else:
            member = enumeration[enumeration._member_names_[0]]
        enumeration._member_map_['DEFAULT'] = member
        return enumeration

Your final Enum would then look like (assuming cascade is all previous members and using the decorator approach):

@add_default('Canary')
class Release(Enum):
    Canary = 1
    Beta = 2
    RC = 3
    Stable = 4
    def __new__(cls, value):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value]      # not sure what this should actually be or how it's calculated
        obj.cascade = list(cls)[::-1]
        return obj
    @classmethod
    def get_all_releases(cls, release: "Release" = None):
        if release is None:
            release = cls.DEFAULT
        return release.current

and in use:

>>> Release.DEFAULT
<Release.Canary: 1>

>>> Release.get_all_releases()
['Release']

>>> Release.get_all_releases(Release.RC)
['ReleaseReleaseRelease']

Original Answer

The problem you are having with your code is here:

class Release(Enum):
    Canary = 1,

By including that extra comma you have made the value for Canary be (1, ). Remove that comma to get rid of the tuple exception.



来源:https://stackoverflow.com/questions/58743679/enum-class-method-with-default-enum-value-fails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!