我正在尝试重载枚举的子类的__init__()
方法。奇怪的是,与普通类一起使用的模式在Enum中不再起作用。
以下显示了使用普通类的所需模式:
class Integer:
def __init__(self, a):
"""Accepts only int"""
assert isinstance(a, int)
self.a = a
def __repr__(self):
return str(self.a)
class RobustInteger(Integer):
def __init__(self, a):
"""Accepts int or str"""
if isinstance(a, str):
super().__init__(int(a))
else:
super().__init__(a)
print(Integer(1))
# 1
print(RobustInteger(1))
# 1
print(RobustInteger('1'))
# 1
如果与枚举一起使用,则相同的模式会中断:
from enum import Enum
from datetime import date
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
def __init__(self, value):
"""Accepts int or date"""
if isinstance(value, date):
super().__init__(date.weekday())
else:
super().__init__(value)
assert WeekDay(0) == WeekDay.MONDAY
assert WeekDay(date(2019, 4, 3)) == WeekDay.MONDAY
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# /path/to/my/test/file.py in <module>()
# 27
# 28
# ---> 29 class WeekDay(Enum):
# 30 MONDAY = 0
# 31 TUESDAY = 1
# /path/to/my/virtualenv/lib/python3.6/enum.py in __new__(metacls, cls, bases, classdict)
# 208 enum_member._name_ = member_name
# 209 enum_member.__objclass__ = enum_class
# --> 210 enum_member.__init__(*args)
# 211 # If another member with the same value was already defined, the
# 212 # new member becomes an alias to the existing one.
# /path/to/my/test/file.py in __init__(self, value)
# 40 super().__init__(date.weekday())
# 41 else:
# ---> 42 super().__init__(value)
# 43
# 44
# TypeError: object.__init__() takes no parameters
答案 0 :(得分:4)
您必须重载_missing_
钩子。 WeekDay
的所有实例都是在首次定义该类时创建的; WeekDay(date(...))
是索引操作,而不是创建操作,__new__
最初正在寻找绑定到整数0到6的预先存在的值。否则,它将调用_missing_
,其中您可以将date
对象转换成这样的整数。
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
@classmethod
def _missing_(cls, value):
if isinstance(value, date):
return cls(value.weekday())
return super()._missing_(value)
一些例子:
>>> WeekDay(date(2019,3,7))
<WeekDay.THURSDAY: 3>
>>> assert WeekDay(date(2019, 4, 1)) == WeekDay.MONDAY
>>> assert WeekDay(date(2019, 4, 3)) == WeekDay.MONDAY
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
(注意:_missing_
在Python 3.6之前不可用。)
在3.6之前,似乎您可以覆盖EnumMeta.__call__
进行相同的检查,但是我不确定这是否会产生意外的副作用。 (对__call__
的回想总是使我的头有些旋转。)
# Silently convert an instance of datatime.date to a day-of-week
# integer for lookup.
class WeekDayMeta(EnumMeta):
def __call__(cls, value, *args, **kwargs):
if isinstance(value, date):
value = value.weekday())
return super().__call__(value, *args, **kwargs)
class WeekDay(Enum, metaclass=WeekDayMeta):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
答案 1 :(得分:2)
有一个更好的答案,但是无论如何我都会发布此问题,因为这可能有助于理解该问题。
文档给出了以下提示:
EnumMeta会在创建Enum类本身时全部创建它们, 然后放置自定义 new ()以确保没有新的 只能通过返回现有成员实例来实例化。
因此,我们必须等待重新定义__new__
,直到创建该类。经过一些丑陋的修补,这可以通过测试:
from enum import Enum
from datetime import date
class WeekDay(Enum):
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
wnew = WeekDay.__new__
def _new(cls, value):
if isinstance(value, date):
return wnew(cls, value.weekday()) # not date.weekday()
else:
return wnew(cls, value)
WeekDay.__new__ = _new
assert WeekDay(0) == WeekDay.MONDAY
assert WeekDay(date(2019, 3, 4)) == WeekDay.MONDAY # not 2019,4,3