具有默认枚举值的枚举类方法失败
Posted
技术标签:
【中文标题】具有默认枚举值的枚举类方法失败【英文标题】:Enum Class method with default Enum value fails 【发布时间】:2020-03-03 17:39:31 【问题描述】:我很清楚,如果您有一个使用枚举的类名进行类型提示的类方法,那么有一个技巧可以让它在 Python 3.6 及更低版本中工作。
而不是...
class Release(Enum):
...
@classmethod
def get(cls, release: Release):
...
你需要像这样使用字符串值...
class Release(Enum):
...
@classmethod
def get(cls, release: "Release"):
...
我相信在 Python 3.7 及更高版本中,您不必使用引号就可以绕过这种“hack”。原因类似于“在首先完成所有方法和变量之前,该类还不存在”。由于该类尚不存在,因此我还不能使用类名,并且必须使用带引号的字符串作为破解。
但是,我试图更进一步并使用默认值。那是行不通的。 Python 3.6 是否有一种不是 hack 的 Pythonic 方法? 另外,python 3.7 及更高版本是否有修复?
代码
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())
使用此代码,我收到以下错误消息
AttributeError: 'tuple' object has no attribute 'current'
那是因为它返回的是 Canary 的元组,而不是实际的值。
【问题讨论】:
current
和cascade
有什么区别? current
究竟是如何设置的?
【参考方案1】:
虽然这绝对是一种解决方法,但这似乎对我来说效果很好:
@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
它适用于您分配给Canary
的任何值。因此,只要这是您的默认设置,我相信它会起作用。
为了更通用,您只需调整类定义中的默认值而不是每个函数,您可以按如下方式进行:
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
【讨论】:
据您所知,没有任何内置方法可以做到这一点,对吗? 我不知道有哪一种,也找不到任何复杂度较低的东西,但这不是我以前尝试过的东西,所以我不能 100% 确定。 我更新了答案以显示更通用的设置和使用默认值的方式。 ***.com/questions/44867597/… 这里也有一些其他的答案,但如果这个方法对你有用,它至少不需要太多的改变。 您在Stable
上缺少逗号。【参考方案2】:
从@ufoxDan 的回答中得到了提示,但试图使其不那么变通,更自然。
基本上,我首先在return
ing 之前检查type(release)
,并注意到我得到了...的结果。
<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<class 'tuple'>
我注意到,如果类型是 Release
,那么我可以执行代码,但是如果它是 anything else,比如 None
而不是未创建的 Canary
类型,那么我可以假设它要求Canary
。所以我做了以下...
@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))
这似乎是实现结果的最 Pythonic 方式。这似乎也是阅读代码且无需重复代码的最佳方式。似乎任何人都应该能够实现类似的东西。
【讨论】:
我喜欢。请注意,如果您需要更改哪个值是默认值,您将需要在每个使用此默认值的函数中重构它(尽管对于一个函数来说还不错)。所以你也可以在枚举定义中设置default = Canary
,然后做return Release.default.current
。不是一个大问题,但如果它是一个大项目,它可以缓解未来的发展:)
@ufoxDan 我明白你的意思。我的代码当前循环遍历其他地方的所有枚举,我认为我必须更改该代码以处理新的default
枚举。如果我需要更多更新课程,我会调查一下【参考方案3】:
您可以在Release
Enum
中做一些事情来让生活更轻松,第一个是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
第二种技术可以有两种方式——您的默认值始终是第一个Enum
成员:
@classmethod
def get_all_releases(cls):
return list(cls[0]).current
或者,如果默认值可以是任何成员,那么类似于 this answer 的东西应该可以工作:
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
你最终的Enum
看起来像(假设cascade
是所有以前的成员并使用装饰器方法):
@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
并在使用中:
>>> Release.DEFAULT
<Release.Canary: 1>
>>> Release.get_all_releases()
['Release']
>>> Release.get_all_releases(Release.RC)
['ReleaseReleaseRelease']
原答案
您的代码有问题:
class Release(Enum):
Canary = 1,
通过包含额外的逗号,您使Canary
的值变为(1, )
。删除该逗号以摆脱 tuple
异常。
【讨论】:
感谢您解释为什么值为(1, )
。直到现在我才完全理解。但是,我的真实代码 确实 使用了一个元组,其中第二个元素是一个列表。我试图做一个最小可行的例子
问题是它给出了一个AttributeError
和int
而不是tuple
@ChristopherRucinski:更新答案以匹配您更新的问题。 :)
@ChristopherRucinski:啊。我不应该在不先运行代码的情况下发布代码!现在可以了。 ://
这里有很多很好的信息可以解析。在这个问题中,我只是想找出一种在 classmethod 中使用默认 Enum 值的 Pythonic 方式,因此我遗漏了许多您必须假设的重要细节。例如current
实际上是一个dict
,其键为latest
,每个版本如1.0
、1.1
、1.2
、1.3
、2.0
、2.2
、@98764 @、3.0
等... 值是一些数据,如下载链接。此外,虽然cascade
通常是所有以前的版本,但有一个奇怪的案例我没有包括它不符合该逻辑的地方。以上是关于具有默认枚举值的枚举类方法失败的主要内容,如果未能解决你的问题,请参考以下文章