mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Revert "bpo-40066: [Enum] update str() and format() output (GH-30582)" (GH-30632)
This reverts commit acf7403f9b
.
This commit is contained in:
parent
7f4b69b907
commit
42a64c03ec
14 changed files with 2021 additions and 2087 deletions
605
Lib/enum.py
605
Lib/enum.py
|
@ -1,16 +1,16 @@
|
|||
import sys
|
||||
import builtins as bltns
|
||||
from types import MappingProxyType, DynamicClassAttribute
|
||||
from operator import or_ as _or_
|
||||
from functools import reduce
|
||||
from builtins import property as _bltin_property, bin as _bltin_bin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'EnumType', 'EnumMeta',
|
||||
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
|
||||
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag',
|
||||
'auto', 'unique', 'property', 'verify',
|
||||
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
|
||||
'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum',
|
||||
'global_flag_repr', 'global_enum_repr', 'global_enum',
|
||||
'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE',
|
||||
]
|
||||
|
||||
|
@ -18,7 +18,7 @@ __all__ = [
|
|||
# Dummy value for Enum and Flag as there are explicit checks for them
|
||||
# before they have been created.
|
||||
# This is also why there are checks in EnumType like `if Enum is not None`
|
||||
Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None
|
||||
Enum = Flag = EJECT = None
|
||||
|
||||
def _is_descriptor(obj):
|
||||
"""
|
||||
|
@ -116,9 +116,9 @@ def bin(num, max_bits=None):
|
|||
|
||||
ceiling = 2 ** (num).bit_length()
|
||||
if num >= 0:
|
||||
s = bltns.bin(num + ceiling).replace('1', '0', 1)
|
||||
s = _bltin_bin(num + ceiling).replace('1', '0', 1)
|
||||
else:
|
||||
s = bltns.bin(~num ^ (ceiling - 1) + ceiling)
|
||||
s = _bltin_bin(~num ^ (ceiling - 1) + ceiling)
|
||||
sign = s[:3]
|
||||
digits = s[3:]
|
||||
if max_bits is not None:
|
||||
|
@ -126,19 +126,6 @@ def bin(num, max_bits=None):
|
|||
digits = (sign[-1] * max_bits + digits)[-max_bits:]
|
||||
return "%s %s" % (sign, digits)
|
||||
|
||||
def _dedent(text):
|
||||
"""
|
||||
Like textwrap.dedent. Rewritten because we cannot import textwrap.
|
||||
"""
|
||||
lines = text.split('\n')
|
||||
blanks = 0
|
||||
for i, ch in enumerate(lines[0]):
|
||||
if ch != ' ':
|
||||
break
|
||||
for j, l in enumerate(lines):
|
||||
lines[j] = l[i:]
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
_auto_null = object()
|
||||
class auto:
|
||||
|
@ -162,12 +149,22 @@ class property(DynamicClassAttribute):
|
|||
return ownerclass._member_map_[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError(
|
||||
'%r has no attribute %r' % (ownerclass, self.name)
|
||||
'%s: no class attribute %r' % (ownerclass.__name__, self.name)
|
||||
)
|
||||
else:
|
||||
if self.fget is None:
|
||||
# check for member
|
||||
if self.name in ownerclass._member_map_:
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"accessing one member from another is not supported, "
|
||||
" and will be disabled in 3.12",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return ownerclass._member_map_[self.name]
|
||||
raise AttributeError(
|
||||
'%r member has no attribute %r' % (ownerclass, self.name)
|
||||
'%s: no instance attribute %r' % (ownerclass.__name__, self.name)
|
||||
)
|
||||
else:
|
||||
return self.fget(instance)
|
||||
|
@ -175,7 +172,7 @@ class property(DynamicClassAttribute):
|
|||
def __set__(self, instance, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError(
|
||||
"<enum %r> cannot set attribute %r" % (self.clsname, self.name)
|
||||
"%s: cannot set instance attribute %r" % (self.clsname, self.name)
|
||||
)
|
||||
else:
|
||||
return self.fset(instance, value)
|
||||
|
@ -183,7 +180,7 @@ class property(DynamicClassAttribute):
|
|||
def __delete__(self, instance):
|
||||
if self.fdel is None:
|
||||
raise AttributeError(
|
||||
"<enum %r> cannot delete attribute %r" % (self.clsname, self.name)
|
||||
"%s: cannot delete instance attribute %r" % (self.clsname, self.name)
|
||||
)
|
||||
else:
|
||||
return self.fdel(instance)
|
||||
|
@ -331,7 +328,7 @@ class _EnumDict(dict):
|
|||
elif _is_sunder(key):
|
||||
if key not in (
|
||||
'_order_',
|
||||
'_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_',
|
||||
'_generate_next_value_', '_missing_', '_ignore_',
|
||||
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
|
||||
):
|
||||
raise ValueError(
|
||||
|
@ -361,13 +358,13 @@ class _EnumDict(dict):
|
|||
key = '_order_'
|
||||
elif key in self._member_names:
|
||||
# descriptor overwriting an enum?
|
||||
raise TypeError('%r already defined as %r' % (key, self[key]))
|
||||
raise TypeError('%r already defined as: %r' % (key, self[key]))
|
||||
elif key in self._ignore:
|
||||
pass
|
||||
elif not _is_descriptor(value):
|
||||
if key in self:
|
||||
# enum overwriting a descriptor?
|
||||
raise TypeError('%r already defined as %r' % (key, self[key]))
|
||||
raise TypeError('%r already defined as: %r' % (key, self[key]))
|
||||
if isinstance(value, auto):
|
||||
if value.value == _auto_null:
|
||||
value.value = self._generate_next_value(
|
||||
|
@ -398,7 +395,7 @@ class EnumType(type):
|
|||
@classmethod
|
||||
def __prepare__(metacls, cls, bases, **kwds):
|
||||
# check that previous enum members do not exist
|
||||
metacls._check_for_existing_members_(cls, bases)
|
||||
metacls._check_for_existing_members(cls, bases)
|
||||
# create the namespace dict
|
||||
enum_dict = _EnumDict()
|
||||
enum_dict._cls_name = cls
|
||||
|
@ -416,10 +413,9 @@ class EnumType(type):
|
|||
# inherited __new__ unless a new __new__ is defined (or the resulting
|
||||
# class will fail).
|
||||
#
|
||||
# remove any keys listed in _ignore_
|
||||
if _simple:
|
||||
return super().__new__(metacls, cls, bases, classdict, **kwds)
|
||||
#
|
||||
# remove any keys listed in _ignore_
|
||||
classdict.setdefault('_ignore_', []).append('_ignore_')
|
||||
ignore = classdict['_ignore_']
|
||||
for key in ignore:
|
||||
|
@ -431,8 +427,8 @@ class EnumType(type):
|
|||
# check for illegal enum names (any others?)
|
||||
invalid_names = set(member_names) & {'mro', ''}
|
||||
if invalid_names:
|
||||
raise ValueError('invalid enum member name(s) '.format(
|
||||
','.join(repr(n) for n in invalid_names)))
|
||||
raise ValueError('Invalid enum member name: {0}'.format(
|
||||
','.join(invalid_names)))
|
||||
#
|
||||
# adjust the sunders
|
||||
_order_ = classdict.pop('_order_', None)
|
||||
|
@ -462,8 +458,6 @@ class EnumType(type):
|
|||
classdict['_value2member_map_'] = {}
|
||||
classdict['_unhashable_values_'] = []
|
||||
classdict['_member_type_'] = member_type
|
||||
# now set the __repr__ for the value
|
||||
classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases)
|
||||
#
|
||||
# Flag structures (will be removed if final class is not a Flag
|
||||
classdict['_boundary_'] = (
|
||||
|
@ -473,6 +467,10 @@ class EnumType(type):
|
|||
classdict['_flag_mask_'] = flag_mask
|
||||
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
|
||||
classdict['_inverted_'] = None
|
||||
#
|
||||
# create a default docstring if one has not been provided
|
||||
if '__doc__' not in classdict:
|
||||
classdict['__doc__'] = 'An enumeration.'
|
||||
try:
|
||||
exc = None
|
||||
enum_class = super().__new__(metacls, cls, bases, classdict, **kwds)
|
||||
|
@ -483,140 +481,18 @@ class EnumType(type):
|
|||
if exc is not None:
|
||||
raise exc
|
||||
#
|
||||
# update classdict with any changes made by __init_subclass__
|
||||
classdict.update(enum_class.__dict__)
|
||||
#
|
||||
# create a default docstring if one has not been provided
|
||||
if enum_class.__doc__ is None:
|
||||
if not member_names:
|
||||
enum_class.__doc__ = classdict['__doc__'] = _dedent("""\
|
||||
Create a collection of name/value pairs.
|
||||
|
||||
Example enumeration:
|
||||
|
||||
>>> class Color(Enum):
|
||||
... RED = 1
|
||||
... BLUE = 2
|
||||
... GREEN = 3
|
||||
|
||||
Access them by:
|
||||
|
||||
- attribute access::
|
||||
|
||||
>>> Color.RED
|
||||
<Color.RED: 1>
|
||||
|
||||
- value lookup:
|
||||
|
||||
>>> Color(1)
|
||||
<Color.RED: 1>
|
||||
|
||||
- name lookup:
|
||||
|
||||
>>> Color['RED']
|
||||
<Color.RED: 1>
|
||||
|
||||
Enumerations can be iterated over, and know how many members they have:
|
||||
|
||||
>>> len(Color)
|
||||
3
|
||||
|
||||
>>> list(Color)
|
||||
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
|
||||
|
||||
Methods can be added to enumerations, and members can have their own
|
||||
attributes -- see the documentation for details.
|
||||
""")
|
||||
else:
|
||||
member = list(enum_class)[0]
|
||||
enum_length = len(enum_class)
|
||||
cls_name = enum_class.__name__
|
||||
if enum_length == 1:
|
||||
list_line = 'list(%s)' % cls_name
|
||||
list_repr = '[<%s.%s: %r>]' % (cls_name, member.name, member.value)
|
||||
elif enum_length == 2:
|
||||
member2 = list(enum_class)[1]
|
||||
list_line = 'list(%s)' % cls_name
|
||||
list_repr = '[<%s.%s: %r>, <%s.%s: %r>]' % (
|
||||
cls_name, member.name, member.value,
|
||||
cls_name, member2.name, member2.value,
|
||||
)
|
||||
else:
|
||||
member2 = list(enum_class)[1]
|
||||
member3 = list(enum_class)[2]
|
||||
list_line = 'list(%s)%s' % (cls_name, ('','[:3]')[enum_length > 3])
|
||||
list_repr = '[<%s.%s: %r>, <%s.%s: %r>, <%s.%s: %r>]' % (
|
||||
cls_name, member.name, member.value,
|
||||
cls_name, member2.name, member2.value,
|
||||
cls_name, member3.name, member3.value,
|
||||
)
|
||||
enum_class.__doc__ = classdict['__doc__'] = _dedent("""\
|
||||
A collection of name/value pairs.
|
||||
|
||||
Access them by:
|
||||
|
||||
- attribute access::
|
||||
|
||||
>>> %s.%s
|
||||
<%s.%s: %r>
|
||||
|
||||
- value lookup:
|
||||
|
||||
>>> %s(%r)
|
||||
<%s.%s: %r>
|
||||
|
||||
- name lookup:
|
||||
|
||||
>>> %s[%r]
|
||||
<%s.%s: %r>
|
||||
|
||||
Enumerations can be iterated over, and know how many members they have:
|
||||
|
||||
>>> len(%s)
|
||||
%r
|
||||
|
||||
>>> %s
|
||||
%s
|
||||
|
||||
Methods can be added to enumerations, and members can have their own
|
||||
attributes -- see the documentation for details.
|
||||
"""
|
||||
% (cls_name, member.name,
|
||||
cls_name, member.name, member.value,
|
||||
cls_name, member.value,
|
||||
cls_name, member.name, member.value,
|
||||
cls_name, member.name,
|
||||
cls_name, member.name, member.value,
|
||||
cls_name, enum_length,
|
||||
list_line, list_repr,
|
||||
))
|
||||
#
|
||||
# double check that repr and friends are not the mixin's or various
|
||||
# things break (such as pickle)
|
||||
# however, if the method is defined in the Enum itself, don't replace
|
||||
# it
|
||||
#
|
||||
# Also, special handling for ReprEnum
|
||||
if ReprEnum is not None and ReprEnum in bases:
|
||||
if member_type is object:
|
||||
raise TypeError(
|
||||
'ReprEnum subclasses must be mixed with a data type (i.e.'
|
||||
' int, str, float, etc.)'
|
||||
)
|
||||
if '__format__' not in classdict:
|
||||
enum_class.__format__ = member_type.__format__
|
||||
classdict['__format__'] = enum_class.__format__
|
||||
if '__str__' not in classdict:
|
||||
method = member_type.__str__
|
||||
if method is object.__str__:
|
||||
# if member_type does not define __str__, object.__str__ will use
|
||||
# its __repr__ instead, so we'll also use its __repr__
|
||||
method = member_type.__repr__
|
||||
enum_class.__str__ = method
|
||||
classdict['__str__'] = enum_class.__str__
|
||||
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
|
||||
if name not in classdict:
|
||||
setattr(enum_class, name, getattr(first_enum, name))
|
||||
if name in classdict:
|
||||
continue
|
||||
class_method = getattr(enum_class, name)
|
||||
obj_method = getattr(member_type, name, None)
|
||||
enum_method = getattr(first_enum, name, None)
|
||||
if obj_method is not None and obj_method is class_method:
|
||||
setattr(enum_class, name, enum_method)
|
||||
#
|
||||
# replace any other __new__ with our own (as long as Enum is not None,
|
||||
# anyway) -- again, this is to support pickle
|
||||
|
@ -687,13 +563,13 @@ class EnumType(type):
|
|||
# _order_ step 4: verify that _order_ and _member_names_ match
|
||||
if _order_ != enum_class._member_names_:
|
||||
raise TypeError(
|
||||
'member order does not match _order_:\n %r\n %r'
|
||||
'member order does not match _order_:\n%r\n%r'
|
||||
% (enum_class._member_names_, _order_)
|
||||
)
|
||||
#
|
||||
return enum_class
|
||||
|
||||
def __bool__(cls):
|
||||
def __bool__(self):
|
||||
"""
|
||||
classes/types should always be True.
|
||||
"""
|
||||
|
@ -738,13 +614,6 @@ class EnumType(type):
|
|||
)
|
||||
|
||||
def __contains__(cls, member):
|
||||
"""
|
||||
Return True if member is a member of this enum
|
||||
raises TypeError if member is not an enum member
|
||||
|
||||
note: in 3.12 TypeError will no longer be raised, and True will also be
|
||||
returned if member is the value of a member in this enum
|
||||
"""
|
||||
if not isinstance(member, Enum):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
|
@ -762,33 +631,60 @@ class EnumType(type):
|
|||
# nicer error message when someone tries to delete an attribute
|
||||
# (see issue19025).
|
||||
if attr in cls._member_map_:
|
||||
raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr))
|
||||
raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr))
|
||||
super().__delattr__(attr)
|
||||
|
||||
def __dir__(cls):
|
||||
# TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__
|
||||
# on object-based enums
|
||||
if cls._member_type_ is object:
|
||||
interesting = set(cls._member_names_)
|
||||
if cls._new_member_ is not object.__new__:
|
||||
interesting.add('__new__')
|
||||
if cls.__init_subclass__ is not object.__init_subclass__:
|
||||
interesting.add('__init_subclass__')
|
||||
for method in ('__init__', '__format__', '__repr__', '__str__'):
|
||||
if getattr(cls, method) not in (getattr(Enum, method), getattr(Flag, method)):
|
||||
interesting.add(method)
|
||||
return sorted(set([
|
||||
'__class__', '__contains__', '__doc__', '__getitem__',
|
||||
'__iter__', '__len__', '__members__', '__module__',
|
||||
'__name__', '__qualname__',
|
||||
]) | interesting
|
||||
)
|
||||
else:
|
||||
# return whatever mixed-in data type has
|
||||
return sorted(set(
|
||||
dir(cls._member_type_)
|
||||
+ cls._member_names_
|
||||
))
|
||||
def __dir__(self):
|
||||
# Start off with the desired result for dir(Enum)
|
||||
cls_dir = {'__class__', '__doc__', '__members__', '__module__'}
|
||||
add_to_dir = cls_dir.add
|
||||
mro = self.__mro__
|
||||
this_module = globals().values()
|
||||
is_from_this_module = lambda cls: any(cls is thing for thing in this_module)
|
||||
first_enum_base = next(cls for cls in mro if is_from_this_module(cls))
|
||||
enum_dict = Enum.__dict__
|
||||
sentinel = object()
|
||||
# special-case __new__
|
||||
ignored = {'__new__', *filter(_is_sunder, enum_dict)}
|
||||
add_to_ignored = ignored.add
|
||||
|
||||
# We want these added to __dir__
|
||||
# if and only if they have been user-overridden
|
||||
enum_dunders = set(filter(_is_dunder, enum_dict))
|
||||
|
||||
for cls in mro:
|
||||
# Ignore any classes defined in this module
|
||||
if cls is object or is_from_this_module(cls):
|
||||
continue
|
||||
|
||||
cls_lookup = cls.__dict__
|
||||
|
||||
# If not an instance of EnumType,
|
||||
# ensure all attributes excluded from that class's `dir()` are ignored here.
|
||||
if not isinstance(cls, EnumType):
|
||||
cls_lookup = set(cls_lookup).intersection(dir(cls))
|
||||
|
||||
for attr_name in cls_lookup:
|
||||
# Already seen it? Carry on
|
||||
if attr_name in cls_dir or attr_name in ignored:
|
||||
continue
|
||||
# Sunders defined in Enum.__dict__ are already in `ignored`,
|
||||
# But sunders defined in a subclass won't be (we want all sunders excluded).
|
||||
elif _is_sunder(attr_name):
|
||||
add_to_ignored(attr_name)
|
||||
# Not an "enum dunder"? Add it to dir() output.
|
||||
elif attr_name not in enum_dunders:
|
||||
add_to_dir(attr_name)
|
||||
# Is an "enum dunder", and is defined by a class from enum.py? Ignore it.
|
||||
elif getattr(self, attr_name) is getattr(first_enum_base, attr_name, sentinel):
|
||||
add_to_ignored(attr_name)
|
||||
# Is an "enum dunder", and is either user-defined or defined by a mixin class?
|
||||
# Add it to dir() output.
|
||||
else:
|
||||
add_to_dir(attr_name)
|
||||
|
||||
# sort the output before returning it, so that the result is deterministic.
|
||||
return sorted(cls_dir)
|
||||
|
||||
def __getattr__(cls, name):
|
||||
"""
|
||||
|
@ -807,24 +703,18 @@ class EnumType(type):
|
|||
raise AttributeError(name) from None
|
||||
|
||||
def __getitem__(cls, name):
|
||||
"""
|
||||
Return the member matching `name`.
|
||||
"""
|
||||
return cls._member_map_[name]
|
||||
|
||||
def __iter__(cls):
|
||||
"""
|
||||
Return members in definition order.
|
||||
Returns members in definition order.
|
||||
"""
|
||||
return (cls._member_map_[name] for name in cls._member_names_)
|
||||
|
||||
def __len__(cls):
|
||||
"""
|
||||
Return the number of members (no aliases)
|
||||
"""
|
||||
return len(cls._member_names_)
|
||||
|
||||
@bltns.property
|
||||
@_bltin_property
|
||||
def __members__(cls):
|
||||
"""
|
||||
Returns a mapping of member name->value.
|
||||
|
@ -842,7 +732,7 @@ class EnumType(type):
|
|||
|
||||
def __reversed__(cls):
|
||||
"""
|
||||
Return members in reverse definition order.
|
||||
Returns members in reverse definition order.
|
||||
"""
|
||||
return (cls._member_map_[name] for name in reversed(cls._member_names_))
|
||||
|
||||
|
@ -856,7 +746,7 @@ class EnumType(type):
|
|||
"""
|
||||
member_map = cls.__dict__.get('_member_map_', {})
|
||||
if name in member_map:
|
||||
raise AttributeError('cannot reassign member %r' % (name, ))
|
||||
raise AttributeError('Cannot reassign member %r.' % (name, ))
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None):
|
||||
|
@ -911,7 +801,8 @@ class EnumType(type):
|
|||
|
||||
return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary)
|
||||
|
||||
def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_global=False):
|
||||
def _convert_(cls, name, module, filter, source=None, *, boundary=None):
|
||||
|
||||
"""
|
||||
Create a new Enum subclass that replaces a collection of global constants
|
||||
"""
|
||||
|
@ -943,25 +834,22 @@ class EnumType(type):
|
|||
tmp_cls = type(name, (object, ), body)
|
||||
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
|
||||
cls.__reduce_ex__ = _reduce_ex_by_global_name
|
||||
if as_global:
|
||||
global_enum(cls)
|
||||
else:
|
||||
sys.modules[cls.__module__].__dict__.update(cls.__members__)
|
||||
global_enum(cls)
|
||||
module_globals[name] = cls
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
def _check_for_existing_members_(mcls, class_name, bases):
|
||||
@staticmethod
|
||||
def _check_for_existing_members(class_name, bases):
|
||||
for chain in bases:
|
||||
for base in chain.__mro__:
|
||||
if issubclass(base, Enum) and base._member_names_:
|
||||
raise TypeError(
|
||||
"<enum %r> cannot extend %r"
|
||||
% (class_name, base)
|
||||
"%s: cannot extend enumeration %r"
|
||||
% (class_name, base.__name__)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_mixins_(mcls, class_name, bases):
|
||||
def _get_mixins_(cls, class_name, bases):
|
||||
"""
|
||||
Returns the type for creating enum members, and the first inherited
|
||||
enum class.
|
||||
|
@ -971,7 +859,30 @@ class EnumType(type):
|
|||
if not bases:
|
||||
return object, Enum
|
||||
|
||||
mcls._check_for_existing_members_(class_name, bases)
|
||||
def _find_data_type(bases):
|
||||
data_types = set()
|
||||
for chain in bases:
|
||||
candidate = None
|
||||
for base in chain.__mro__:
|
||||
if base is object:
|
||||
continue
|
||||
elif issubclass(base, Enum):
|
||||
if base._member_type_ is not object:
|
||||
data_types.add(base._member_type_)
|
||||
break
|
||||
elif '__new__' in base.__dict__:
|
||||
if issubclass(base, Enum):
|
||||
continue
|
||||
data_types.add(candidate or base)
|
||||
break
|
||||
else:
|
||||
candidate = candidate or base
|
||||
if len(data_types) > 1:
|
||||
raise TypeError('%r: too many data types: %r' % (class_name, data_types))
|
||||
elif data_types:
|
||||
return data_types.pop()
|
||||
else:
|
||||
return None
|
||||
|
||||
# ensure final parent class is an Enum derivative, find any concrete
|
||||
# data type, and check that Enum has no members
|
||||
|
@ -979,51 +890,12 @@ class EnumType(type):
|
|||
if not issubclass(first_enum, Enum):
|
||||
raise TypeError("new enumerations should be created as "
|
||||
"`EnumName([mixin_type, ...] [data_type,] enum_type)`")
|
||||
member_type = mcls._find_data_type_(class_name, bases) or object
|
||||
cls._check_for_existing_members(class_name, bases)
|
||||
member_type = _find_data_type(bases) or object
|
||||
return member_type, first_enum
|
||||
|
||||
@classmethod
|
||||
def _find_data_repr_(mcls, class_name, bases):
|
||||
for chain in bases:
|
||||
for base in chain.__mro__:
|
||||
if base is object:
|
||||
continue
|
||||
elif issubclass(base, Enum):
|
||||
# if we hit an Enum, use it's _value_repr_
|
||||
return base._value_repr_
|
||||
elif '__repr__' in base.__dict__:
|
||||
# this is our data repr
|
||||
return base.__dict__['__repr__']
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _find_data_type_(mcls, class_name, bases):
|
||||
data_types = set()
|
||||
for chain in bases:
|
||||
candidate = None
|
||||
for base in chain.__mro__:
|
||||
if base is object:
|
||||
continue
|
||||
elif issubclass(base, Enum):
|
||||
if base._member_type_ is not object:
|
||||
data_types.add(base._member_type_)
|
||||
break
|
||||
elif '__new__' in base.__dict__:
|
||||
if issubclass(base, Enum):
|
||||
continue
|
||||
data_types.add(candidate or base)
|
||||
break
|
||||
else:
|
||||
candidate = candidate or base
|
||||
if len(data_types) > 1:
|
||||
raise TypeError('too many data types for %r: %r' % (class_name, data_types))
|
||||
elif data_types:
|
||||
return data_types.pop()
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _find_new_(mcls, classdict, member_type, first_enum):
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""
|
||||
Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
|
@ -1071,42 +943,9 @@ EnumMeta = EnumType
|
|||
|
||||
class Enum(metaclass=EnumType):
|
||||
"""
|
||||
Create a collection of name/value pairs.
|
||||
Generic enumeration.
|
||||
|
||||
Example enumeration:
|
||||
|
||||
>>> class Color(Enum):
|
||||
... RED = 1
|
||||
... BLUE = 2
|
||||
... GREEN = 3
|
||||
|
||||
Access them by:
|
||||
|
||||
- attribute access::
|
||||
|
||||
>>> Color.RED
|
||||
<Color.RED: 1>
|
||||
|
||||
- value lookup:
|
||||
|
||||
>>> Color(1)
|
||||
<Color.RED: 1>
|
||||
|
||||
- name lookup:
|
||||
|
||||
>>> Color['RED']
|
||||
<Color.RED: 1>
|
||||
|
||||
Enumerations can be iterated over, and know how many members they have:
|
||||
|
||||
>>> len(Color)
|
||||
3
|
||||
|
||||
>>> list(Color)
|
||||
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
|
||||
|
||||
Methods can be added to enumerations, and members can have their own
|
||||
attributes -- see the documentation for details.
|
||||
Derive from this class to define new enumerations.
|
||||
"""
|
||||
|
||||
def __new__(cls, value):
|
||||
|
@ -1160,9 +999,6 @@ class Enum(metaclass=EnumType):
|
|||
exc = None
|
||||
ve_exc = None
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
pass
|
||||
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
"""
|
||||
Generate the next value when not given.
|
||||
|
@ -1185,44 +1021,47 @@ class Enum(metaclass=EnumType):
|
|||
return None
|
||||
|
||||
def __repr__(self):
|
||||
v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
|
||||
return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_))
|
||||
return "%s.%s" % ( self.__class__.__name__, self._name_)
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_, )
|
||||
return "%s" % (self._name_, )
|
||||
|
||||
def __dir__(self):
|
||||
"""
|
||||
Returns all members and all public methods
|
||||
"""
|
||||
if self.__class__._member_type_ is object:
|
||||
interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value'])
|
||||
else:
|
||||
interesting = set(object.__dir__(self))
|
||||
for name in getattr(self, '__dict__', []):
|
||||
if name[0] != '_':
|
||||
interesting.add(name)
|
||||
for cls in self.__class__.mro():
|
||||
for name, obj in cls.__dict__.items():
|
||||
if name[0] == '_':
|
||||
continue
|
||||
if isinstance(obj, property):
|
||||
# that's an enum.property
|
||||
if obj.fget is not None or name not in self._member_map_:
|
||||
interesting.add(name)
|
||||
else:
|
||||
# in case it was added by `dir(self)`
|
||||
interesting.discard(name)
|
||||
else:
|
||||
interesting.add(name)
|
||||
names = sorted(
|
||||
set(['__class__', '__doc__', '__eq__', '__hash__', '__module__'])
|
||||
| interesting
|
||||
)
|
||||
return names
|
||||
cls = type(self)
|
||||
to_exclude = {'__members__', '__init__', '__new__', *cls._member_names_}
|
||||
filtered_self_dict = (name for name in self.__dict__ if not name.startswith('_'))
|
||||
return sorted({'name', 'value', *dir(cls), *filtered_self_dict} - to_exclude)
|
||||
|
||||
def __format__(self, format_spec):
|
||||
return str.__format__(str(self), format_spec)
|
||||
"""
|
||||
Returns format using actual value type unless __str__ has been overridden.
|
||||
"""
|
||||
# mixed-in Enums should use the mixed-in type's __format__, otherwise
|
||||
# we can get strange results with the Enum name showing up instead of
|
||||
# the value
|
||||
#
|
||||
# pure Enum branch, or branch with __str__ explicitly overridden
|
||||
str_overridden = type(self).__str__ not in (Enum.__str__, IntEnum.__str__, Flag.__str__)
|
||||
if self._member_type_ is object or str_overridden:
|
||||
cls = str
|
||||
val = str(self)
|
||||
# mix-in branch
|
||||
else:
|
||||
if not format_spec or format_spec in ('{}','{:}'):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"in 3.12 format() will use the enum member, not the enum member's value;\n"
|
||||
"use a format specifier, such as :d for an integer-based Enum, to maintain "
|
||||
"the current display",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
cls = self._member_type_
|
||||
val = self._value_
|
||||
return cls.__format__(val, format_spec)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._name_)
|
||||
|
@ -1249,25 +1088,34 @@ class Enum(metaclass=EnumType):
|
|||
return self._value_
|
||||
|
||||
|
||||
class ReprEnum(Enum):
|
||||
"""
|
||||
Only changes the repr(), leaving str() and format() to the mixed-in type.
|
||||
"""
|
||||
|
||||
|
||||
class IntEnum(int, ReprEnum):
|
||||
class IntEnum(int, Enum):
|
||||
"""
|
||||
Enum where members are also (and must be) ints
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return "%s" % (self._name_, )
|
||||
|
||||
class StrEnum(str, ReprEnum):
|
||||
def __format__(self, format_spec):
|
||||
"""
|
||||
Returns format using actual value unless __str__ has been overridden.
|
||||
"""
|
||||
str_overridden = type(self).__str__ != IntEnum.__str__
|
||||
if str_overridden:
|
||||
cls = str
|
||||
val = str(self)
|
||||
else:
|
||||
cls = self._member_type_
|
||||
val = self._value_
|
||||
return cls.__format__(val, format_spec)
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
"""
|
||||
Enum where members are also (and must be) strings
|
||||
"""
|
||||
|
||||
def __new__(cls, *values):
|
||||
"values must already be of type `str`"
|
||||
if len(values) > 3:
|
||||
raise TypeError('too many arguments for str(): %r' % (values, ))
|
||||
if len(values) == 1:
|
||||
|
@ -1287,6 +1135,10 @@ class StrEnum(str, ReprEnum):
|
|||
member._value_ = value
|
||||
return member
|
||||
|
||||
__str__ = str.__str__
|
||||
|
||||
__format__ = str.__format__
|
||||
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
"""
|
||||
Return the lower-cased version of the member name.
|
||||
|
@ -1317,8 +1169,6 @@ class Flag(Enum, boundary=STRICT):
|
|||
Support for flags
|
||||
"""
|
||||
|
||||
_numeric_repr_ = repr
|
||||
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
"""
|
||||
Generate the next value when not given.
|
||||
|
@ -1334,7 +1184,7 @@ class Flag(Enum, boundary=STRICT):
|
|||
try:
|
||||
high_bit = _high_bit(last_value)
|
||||
except Exception:
|
||||
raise TypeError('invalid flag value %r' % last_value) from None
|
||||
raise TypeError('Invalid Flag value: %r' % last_value) from None
|
||||
return 2 ** (high_bit+1)
|
||||
|
||||
@classmethod
|
||||
|
@ -1382,8 +1232,8 @@ class Flag(Enum, boundary=STRICT):
|
|||
if cls._boundary_ is STRICT:
|
||||
max_bits = max(value.bit_length(), flag_mask.bit_length())
|
||||
raise ValueError(
|
||||
"%r invalid value %r\n given %s\n allowed %s" % (
|
||||
cls, value, bin(value, max_bits), bin(flag_mask, max_bits),
|
||||
"%s: invalid value: %r\n given %s\n allowed %s" % (
|
||||
cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits),
|
||||
))
|
||||
elif cls._boundary_ is CONFORM:
|
||||
value = value & flag_mask
|
||||
|
@ -1397,7 +1247,7 @@ class Flag(Enum, boundary=STRICT):
|
|||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
'%r unknown flag boundary %r' % (cls, cls._boundary_, )
|
||||
'unknown flag boundary: %r' % (cls._boundary_, )
|
||||
)
|
||||
if value < 0:
|
||||
neg_value = value
|
||||
|
@ -1424,7 +1274,7 @@ class Flag(Enum, boundary=STRICT):
|
|||
m._name_ for m in cls._iter_member_(member_value)
|
||||
])
|
||||
if unknown:
|
||||
pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown)
|
||||
pseudo_member._name_ += '|0x%x' % unknown
|
||||
else:
|
||||
pseudo_member._name_ = None
|
||||
# use setdefault in case another thread already created a composite
|
||||
|
@ -1442,8 +1292,10 @@ class Flag(Enum, boundary=STRICT):
|
|||
"""
|
||||
if not isinstance(other, self.__class__):
|
||||
raise TypeError(
|
||||
"unsupported operand type(s) for 'in': %r and %r" % (
|
||||
"unsupported operand type(s) for 'in': '%s' and '%s'" % (
|
||||
type(other).__qualname__, self.__class__.__qualname__))
|
||||
if other._value_ == 0 or self._value_ == 0:
|
||||
return False
|
||||
return other._value_ & self._value_ == other._value_
|
||||
|
||||
def __iter__(self):
|
||||
|
@ -1457,18 +1309,27 @@ class Flag(Enum, boundary=STRICT):
|
|||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
|
||||
if self._name_ is None:
|
||||
return "<%s: %s>" % (cls_name, v_repr(self._value_))
|
||||
return "0x%x" % (self._value_, )
|
||||
if _is_single_bit(self._value_):
|
||||
return '%s.%s' % (cls_name, self._name_)
|
||||
if self._boundary_ is not FlagBoundary.KEEP:
|
||||
return '%s.' % cls_name + ('|%s.' % cls_name).join(self.name.split('|'))
|
||||
else:
|
||||
return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_))
|
||||
name = []
|
||||
for n in self._name_.split('|'):
|
||||
if n.startswith('0'):
|
||||
name.append(n)
|
||||
else:
|
||||
name.append('%s.%s' % (cls_name, n))
|
||||
return '|'.join(name)
|
||||
|
||||
def __str__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
cls = self.__class__
|
||||
if self._name_ is None:
|
||||
return '%s(%r)' % (cls_name, self._value_)
|
||||
return '%s(%x)' % (cls.__name__, self._value_)
|
||||
else:
|
||||
return "%s.%s" % (cls_name, self._name_)
|
||||
return self._name_
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._value_)
|
||||
|
@ -1501,11 +1362,20 @@ class Flag(Enum, boundary=STRICT):
|
|||
return self._inverted_
|
||||
|
||||
|
||||
class IntFlag(int, ReprEnum, Flag, boundary=EJECT):
|
||||
class IntFlag(int, Flag, boundary=EJECT):
|
||||
"""
|
||||
Support for integer-based Flags
|
||||
"""
|
||||
|
||||
def __format__(self, format_spec):
|
||||
"""
|
||||
Returns format using actual value unless __str__ has been overridden.
|
||||
"""
|
||||
str_overridden = type(self).__str__ != Flag.__str__
|
||||
value = self
|
||||
if not str_overridden:
|
||||
value = self._value_
|
||||
return int.__format__(value, format_spec)
|
||||
|
||||
def __or__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
|
@ -1542,7 +1412,6 @@ class IntFlag(int, ReprEnum, Flag, boundary=EJECT):
|
|||
__rxor__ = __xor__
|
||||
__invert__ = Flag.__invert__
|
||||
|
||||
|
||||
def _high_bit(value):
|
||||
"""
|
||||
returns index of highest bit, or -1 if value is zero or negative
|
||||
|
@ -1587,7 +1456,7 @@ def global_flag_repr(self):
|
|||
module = self.__class__.__module__.split('.')[-1]
|
||||
cls_name = self.__class__.__name__
|
||||
if self._name_ is None:
|
||||
return "%s.%s(%r)" % (module, cls_name, self._value_)
|
||||
return "%s.%s(0x%x)" % (module, cls_name, self._value_)
|
||||
if _is_single_bit(self):
|
||||
return '%s.%s' % (module, self._name_)
|
||||
if self._boundary_ is not FlagBoundary.KEEP:
|
||||
|
@ -1595,22 +1464,14 @@ def global_flag_repr(self):
|
|||
else:
|
||||
name = []
|
||||
for n in self._name_.split('|'):
|
||||
if n[0].isdigit():
|
||||
if n.startswith('0'):
|
||||
name.append(n)
|
||||
else:
|
||||
name.append('%s.%s' % (module, n))
|
||||
return '|'.join(name)
|
||||
|
||||
def global_str(self):
|
||||
"""
|
||||
use enum_name instead of class.enum_name
|
||||
"""
|
||||
if self._name_ is None:
|
||||
return "%s(%r)" % (cls_name, self._value_)
|
||||
else:
|
||||
return self._name_
|
||||
|
||||
def global_enum(cls, update_str=False):
|
||||
def global_enum(cls):
|
||||
"""
|
||||
decorator that makes the repr() of an enum member reference its module
|
||||
instead of its class; also exports all members to the enum's module's
|
||||
|
@ -1620,8 +1481,6 @@ def global_enum(cls, update_str=False):
|
|||
cls.__repr__ = global_flag_repr
|
||||
else:
|
||||
cls.__repr__ = global_enum_repr
|
||||
if not issubclass(cls, ReprEnum) or update_str:
|
||||
cls.__str__ = global_str
|
||||
sys.modules[cls.__module__].__dict__.update(cls.__members__)
|
||||
return cls
|
||||
|
||||
|
@ -1663,7 +1522,6 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
body['_value2member_map_'] = value2member_map = {}
|
||||
body['_unhashable_values_'] = []
|
||||
body['_member_type_'] = member_type = etype._member_type_
|
||||
body['_value_repr_'] = etype._value_repr_
|
||||
if issubclass(etype, Flag):
|
||||
body['_boundary_'] = boundary or etype._boundary_
|
||||
body['_flag_mask_'] = None
|
||||
|
@ -1685,8 +1543,13 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
# it
|
||||
enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True)
|
||||
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
|
||||
if name not in body:
|
||||
setattr(enum_class, name, getattr(etype, name))
|
||||
if name in body:
|
||||
continue
|
||||
class_method = getattr(enum_class, name)
|
||||
obj_method = getattr(member_type, name, None)
|
||||
enum_method = getattr(etype, name, None)
|
||||
if obj_method is not None and obj_method is class_method:
|
||||
setattr(enum_class, name, enum_method)
|
||||
gnv_last_values = []
|
||||
if issubclass(enum_class, Flag):
|
||||
# Flag / IntFlag
|
||||
|
@ -1897,8 +1760,8 @@ def _test_simple_enum(checked_enum, simple_enum):
|
|||
+ list(simple_enum._member_map_.keys())
|
||||
)
|
||||
for key in set(checked_keys + simple_keys):
|
||||
if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'):
|
||||
# keys known to be different, or very long
|
||||
if key in ('__module__', '_member_map_', '_value2member_map_'):
|
||||
# keys known to be different
|
||||
continue
|
||||
elif key in member_names:
|
||||
# members are checked below
|
||||
|
@ -2019,5 +1882,3 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
|
|||
cls.__reduce_ex__ = _reduce_ex_by_global_name
|
||||
cls.__repr__ = global_enum_repr
|
||||
return cls
|
||||
|
||||
_stdlib_enums = IntEnum, StrEnum, IntFlag
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue