summaryrefslogtreecommitdiffstats
path: root/Lib/enum.py
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2022-01-17 12:58:40 (GMT)
committerGitHub <noreply@github.com>2022-01-17 12:58:40 (GMT)
commit42a64c03ec5c443f2a5c2ee4284622f5d1f5326c (patch)
treed5fffd97234c4a8481ee3f07a69107188f1faa7d /Lib/enum.py
parent7f4b69b9076bdbcea31f6ad16eb125ee99cf0175 (diff)
downloadcpython-42a64c03ec5c443f2a5c2ee4284622f5d1f5326c.zip
cpython-42a64c03ec5c443f2a5c2ee4284622f5d1f5326c.tar.gz
cpython-42a64c03ec5c443f2a5c2ee4284622f5d1f5326c.tar.bz2
Revert "bpo-40066: [Enum] update str() and format() output (GH-30582)" (GH-30632)
This reverts commit acf7403f9baea3ae1119fc6b4a3298522188bf96.
Diffstat (limited to 'Lib/enum.py')
-rw-r--r--Lib/enum.py603
1 files changed, 232 insertions, 371 deletions
diff --git a/Lib/enum.py b/Lib/enum.py
index 772e1ea..93ea1be 100644
--- a/Lib/enum.py
+++ b/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.
-
- 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>
+ Generic enumeration.
- - 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):
+class IntEnum(int, Enum):
"""
- Only changes the repr(), leaving str() and format() to the mixed-in type.
+ Enum where members are also (and must be) ints
"""
+ def __str__(self):
+ return "%s" % (self._name_, )
-class IntEnum(int, ReprEnum):
- """
- Enum where members are also (and must be) ints
- """
+ 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, ReprEnum):
+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