class AddressSegment(AutoEnum):
misc = "not currently tracked"
ordinal = "N S E W NE NW SE SW"
secondary = "apt bldg floor etc"
street = "st ave blvd etc"
您可能会问,为什么不将"N S E W NE NW SE SW"作为ordinal的值呢?因为当我得到它的repr时,看到<AddressSegment.ordinal: 'N S E W NE NW SE SW'>会有点笨拙,但在文档字符串中随时提供这些信息是一个很好的折衷方案。 下面是Enum的配方:
class AutoEnum(enum.Enum):
"""
Automatically numbers enum members starting from 1.
Includes support for a custom docstring per member.
"""
#
def __new__(cls, *args):
"""Ignores arguments (will be handled in __init__."""
value = len(cls) + 1
obj = object.__new__(cls)
obj._value_ = value
return obj
#
def __init__(self, *args):
"""Can handle 0 or 1 argument; more requires a custom __init__.
0 = auto-number w/o docstring
1 = auto-number w/ docstring
2+ = needs custom __init__
"""
if len(args) == 1 and isinstance(args[0], (str, unicode)):
self.__doc__ = args[0]
elif args:
raise TypeError('%s not dealt with -- need custom __init__' % (args,))
class MyEnum(Enum):
"""
MyEnum purpose and general doc string
"""
VALUE = "Value"
"""
This is the Value selection. Use this for Values
"""
BUILD = "Build"
"""
This is the Build selection. Use this for Buildings
"""
from typing import *
from enum import Enum, auto
class AutoEnum(str, Enum):
"""
Utility class which can be subclassed to create enums using auto().
Also provides utility methods for common enum operations.
"""
@classmethod
def _missing_(cls, enum_value: Any):
## Ref: https://stackoverflow.com/a/60174274/4900327
## This is needed to allow Pydantic to perform case-insensitive conversion to AutoEnum.
return cls.from_str(enum_value=enum_value, raise_error=True)
def _generate_next_value_(name, start, count, last_values):
return name
@property
def str(self) -> str:
return self.__str__()
def __repr__(self):
return self.__str__()
def __str__(self):
return self.name
def __hash__(self):
return hash(self.__class__.__name__ + '.' + self.name)
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def matches(self, enum_value: str) -> bool:
return self is self.from_str(enum_value, raise_error=False)
@classmethod
def matches_any(cls, enum_value: str) -> bool:
return cls.from_str(enum_value, raise_error=False) is not None
@classmethod
def does_not_match_any(cls, enum_value: str) -> bool:
return not cls.matches_any(enum_value)
@classmethod
def _initialize_lookup(cls):
if '_value2member_map_normalized_' not in cls.__dict__: ## Caching values for fast retrieval.
cls._value2member_map_normalized_ = {}
for e in list(cls):
normalized_e_name: str = cls._normalize(e.value)
if normalized_e_name in cls._value2member_map_normalized_:
raise ValueError(
f'Cannot register enum "{e.value}"; '
f'another enum with the same normalized name "{normalized_e_name}" already exists.'
)
cls._value2member_map_normalized_[normalized_e_name] = e
@classmethod
def from_str(cls, enum_value: str, raise_error: bool = True) -> Optional:
"""
Performs a case-insensitive lookup of the enum value string among the members of the current AutoEnum subclass.
:param enum_value: enum value string
:param raise_error: whether to raise an error if the string is not found in the enum
:return: an enum value which matches the string
:raises: ValueError if raise_error is True and no enum value matches the string
"""
if isinstance(enum_value, cls):
return enum_value
if enum_value is None and raise_error is False:
return None
if not isinstance(enum_value, str) and raise_error is True:
raise ValueError(f'Input should be a string; found type {type(enum_value)}')
cls._initialize_lookup()
enum_obj: Optional[AutoEnum] = cls._value2member_map_normalized_.get(cls._normalize(enum_value))
if enum_obj is None and raise_error is True:
raise ValueError(f'Could not find enum with value {enum_value}; available values are: {list(cls)}.')
return enum_obj
@classmethod
def _normalize(cls, x: str) -> str:
## Found to be faster than .translate() and re.sub() on Python 3.10.6
return str(x).replace(' ', '').replace('-', '').replace('_', '').lower()
@classmethod
def convert_keys(cls, d: Dict) -> Dict:
"""
Converts string dict keys to the matching members of the current AutoEnum subclass.
Leaves non-string keys untouched.
:param d: dict to transform
:return: dict with matching string keys transformed to enum values
"""
out_dict = {}
for k, v in d.items():
if isinstance(k, str) and cls.from_str(k, raise_error=False) is not None:
out_dict[cls.from_str(k, raise_error=False)] = v
else:
out_dict[k] = v
return out_dict
@classmethod
def convert_keys_to_str(cls, d: Dict) -> Dict:
"""
Converts dict keys of the current AutoEnum subclass to the matching string key.
Leaves other keys untouched.
:param d: dict to transform
:return: dict with matching keys of the current AutoEnum transformed to strings.
"""
out_dict = {}
for k, v in d.items():
if isinstance(k, cls):
out_dict[str(k)] = v
else:
out_dict[k] = v
return out_dict
@classmethod
def convert_values(
cls,
d: Union[Dict, Set, List, Tuple],
raise_error: bool = False
) -> Union[Dict, Set, List, Tuple]:
"""
Converts string values to the matching members of the current AutoEnum subclass.
Leaves non-string values untouched.
:param d: dict, set, list or tuple to transform.
:param raise_error: raise an error if unsupported type.
:return: data structure with matching string values transformed to enum values.
"""
if isinstance(d, dict):
return cls.convert_dict_values(d)
if isinstance(d, list):
return cls.convert_list(d)
if isinstance(d, tuple):
return tuple(cls.convert_list(d))
if isinstance(d, set):
return cls.convert_set(d)
if raise_error:
raise ValueError(f'Unrecognized data structure of type {type(d)}')
return d
@classmethod
def convert_dict_values(cls, d: Dict) -> Dict:
"""
Converts string dict values to the matching members of the current AutoEnum subclass.
Leaves non-string values untouched.
:param d: dict to transform
:return: dict with matching string values transformed to enum values
"""
out_dict = {}
for k, v in d.items():
if isinstance(v, str) and cls.from_str(v, raise_error=False) is not None:
out_dict[k] = cls.from_str(v, raise_error=False)
else:
out_dict[k] = v
return out_dict
@classmethod
def convert_list(cls, l: List) -> List:
"""
Converts string list itmes to the matching members of the current AutoEnum subclass.
Leaves non-string items untouched.
:param l: list to transform
:return: list with matching string items transformed to enum values
"""
out_list = []
for item in l:
if isinstance(item, str) and cls.matches_any(item):
out_list.append(cls.from_str(item))
else:
out_list.append(item)
return out_list
@classmethod
def convert_set(cls, s: Set) -> Set:
"""
Converts string list itmes to the matching members of the current AutoEnum subclass.
Leaves non-string items untouched.
:param s: set to transform
:return: set with matching string items transformed to enum values
"""
out_set = set()
for item in s:
if isinstance(item, str) and cls.matches_any(item):
out_set.add(cls.from_str(item))
else:
out_set.add(item)
return out_set
@classmethod
def convert_values_to_str(cls, d: Dict) -> Dict:
"""
Converts dict values of the current AutoEnum subclass to the matching string value.
Leaves other values untouched.
:param d: dict to transform
:return: dict with matching values of the current AutoEnum transformed to strings.
"""
out_dict = {}
for k, v in d.items():
if isinstance(v, cls):
out_dict[k] = str(v)
else:
out_dict[k] = v
return out_dict
4条答案
按热度按时间t9aqgxwy1#
是的,这是目前为止我最喜欢的食谱。作为奖励,你也不必指定整数值。下面是一个例子:
您可能会问,为什么不将
"N S E W NE NW SE SW"
作为ordinal
的值呢?因为当我得到它的repr时,看到<AddressSegment.ordinal: 'N S E W NE NW SE SW'>
会有点笨拙,但在文档字符串中随时提供这些信息是一个很好的折衷方案。下面是Enum的配方:
在使用中:
我在
__init__
中而不是在__new__
中处理参数的原因是,如果我想进一步扩展AutoEnum
,可以使它的子类化更容易。3z6pesqy2#
任何人到达这里作为谷歌搜索:
对于2022年的许多IDE来说,以下内容将填充intellisense:
VSCode中的示例:
2j4z5cfb3#
下面是@Ethan Furman的AutoEnum类的一个更健壮的版本,它使用
auto
枚举函数。下面的实现使用Pydantic,并将值模糊匹配到相应的枚举类型。
用法:
AutoEnum
的代码可以在下面找到。如果你想改变模糊匹配逻辑,那么重写类方法
_normalize
(例如,返回_normalize
中的输入不变,将执行精确匹配)。50pmv0ei4#
函数和类都有文档字符串,但大多数对象没有,甚至根本不需要它们。示例属性没有原生的文档字符串语法,因为它们可以在类的文档字符串中详尽地描述,这也是我建议你做的。类的示例通常也没有自己的文档字符串,枚举成员也不过如此。
毫无疑问,你几乎可以在任何东西上添加docstring。事实上,你确实可以在任何东西上添加任何东西,因为这是python的设计方式。但是它既不有用也不干净,甚至@Ethan Furman发布的内容看起来也像是仅仅为了在静态属性上添加docstring而花费了大量的开销。
长话短说,即使你一开始可能不喜欢它:不要这样做,使用枚举的文档字符串就足够了,它足以解释其成员的含义。