问题
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 return
ing 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