summaryrefslogtreecommitdiffstats
path: root/Lib/enum.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/enum.py')
-rw-r--r--Lib/enum.py603
1 files changed, 371 insertions, 232 deletions
diff --git a/Lib/enum.py b/Lib/enum.py
index 93ea1be..772e1ea 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',
+ 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
'auto', 'unique', 'property', 'verify',
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
- 'global_flag_repr', 'global_enum_repr', 'global_enum',
+ 'global_flag_repr', 'global_enum_repr', 'global_str', '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 = None
+Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None
def _is_descriptor(obj):
"""
@@ -116,9 +116,9 @@ def bin(num, max_bits=None):
ceiling = 2 ** (num).bit_length()
if num >= 0:
- s = _bltin_bin(num + ceiling).replace('1', '0', 1)
+ s = bltns.bin(num + ceiling).replace('1', '0', 1)
else:
- s = _bltin_bin(~num ^ (ceiling - 1) + ceiling)
+ s = bltns.bin(~num ^ (ceiling - 1) + ceiling)
sign = s[:3]
digits = s[3:]
if max_bits is not None:
@@ -126,6 +126,19 @@ 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:
@@ -149,22 +162,12 @@ class property(DynamicClassAttribute):
return ownerclass._member_map_[self.name]
except KeyError:
raise AttributeError(
- '%s: no class attribute %r' % (ownerclass.__name__, self.name)
+ '%r has no attribute %r' % (ownerclass, 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(
- '%s: no instance attribute %r' % (ownerclass.__name__, self.name)
+ '%r member has no attribute %r' % (ownerclass, self.name)
)
else:
return self.fget(instance)
@@ -172,7 +175,7 @@ class property(DynamicClassAttribute):
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError(
- "%s: cannot set instance attribute %r" % (self.clsname, self.name)
+ "<enum %r> cannot set attribute %r" % (self.clsname, self.name)
)
else:
return self.fset(instance, value)
@@ -180,7 +183,7 @@ class property(DynamicClassAttribute):
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError(
- "%s: cannot delete instance attribute %r" % (self.clsname, self.name)
+ "<enum %r> cannot delete attribute %r" % (self.clsname, self.name)
)
else:
return self.fdel(instance)
@@ -328,7 +331,7 @@ class _EnumDict(dict):
elif _is_sunder(key):
if key not in (
'_order_',
- '_generate_next_value_', '_missing_', '_ignore_',
+ '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_',
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
):
raise ValueError(
@@ -358,13 +361,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(
@@ -395,7 +398,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
@@ -413,9 +416,10 @@ 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:
@@ -427,8 +431,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: {0}'.format(
- ','.join(invalid_names)))
+ raise ValueError('invalid enum member name(s) '.format(
+ ','.join(repr(n) for n in invalid_names)))
#
# adjust the sunders
_order_ = classdict.pop('_order_', None)
@@ -458,6 +462,8 @@ 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_'] = (
@@ -467,10 +473,6 @@ 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)
@@ -481,18 +483,140 @@ 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 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)
+ if name not in classdict:
+ setattr(enum_class, name, getattr(first_enum, name))
#
# replace any other __new__ with our own (as long as Enum is not None,
# anyway) -- again, this is to support pickle
@@ -563,13 +687,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__(self):
+ def __bool__(cls):
"""
classes/types should always be True.
"""
@@ -614,6 +738,13 @@ 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(
@@ -631,60 +762,33 @@ class EnumType(type):
# nicer error message when someone tries to delete an attribute
# (see issue19025).
if attr in cls._member_map_:
- raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr))
+ raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr))
super().__delattr__(attr)
- 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 __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 __getattr__(cls, name):
"""
@@ -703,18 +807,24 @@ 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):
"""
- Returns members in definition order.
+ Return 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_)
- @_bltin_property
+ @bltns.property
def __members__(cls):
"""
Returns a mapping of member name->value.
@@ -732,7 +842,7 @@ class EnumType(type):
def __reversed__(cls):
"""
- Returns members in reverse definition order.
+ Return members in reverse definition order.
"""
return (cls._member_map_[name] for name in reversed(cls._member_names_))
@@ -746,7 +856,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):
@@ -801,8 +911,7 @@ class EnumType(type):
return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary)
- def _convert_(cls, name, module, filter, source=None, *, boundary=None):
-
+ def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_global=False):
"""
Create a new Enum subclass that replaces a collection of global constants
"""
@@ -834,22 +943,25 @@ 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
- global_enum(cls)
+ if as_global:
+ global_enum(cls)
+ else:
+ sys.modules[cls.__module__].__dict__.update(cls.__members__)
module_globals[name] = cls
return cls
- @staticmethod
- def _check_for_existing_members(class_name, bases):
+ @classmethod
+ def _check_for_existing_members_(mcls, class_name, bases):
for chain in bases:
for base in chain.__mro__:
if issubclass(base, Enum) and base._member_names_:
raise TypeError(
- "%s: cannot extend enumeration %r"
- % (class_name, base.__name__)
+ "<enum %r> cannot extend %r"
+ % (class_name, base)
)
@classmethod
- def _get_mixins_(cls, class_name, bases):
+ def _get_mixins_(mcls, class_name, bases):
"""
Returns the type for creating enum members, and the first inherited
enum class.
@@ -859,30 +971,7 @@ class EnumType(type):
if not bases:
return object, Enum
- 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
+ mcls._check_for_existing_members_(class_name, bases)
# ensure final parent class is an Enum derivative, find any concrete
# data type, and check that Enum has no members
@@ -890,12 +979,51 @@ class EnumType(type):
if not issubclass(first_enum, Enum):
raise TypeError("new enumerations should be created as "
"`EnumName([mixin_type, ...] [data_type,] enum_type)`")
- cls._check_for_existing_members(class_name, bases)
- member_type = _find_data_type(bases) or object
+ member_type = mcls._find_data_type_(class_name, bases) or object
return member_type, first_enum
- @staticmethod
- def _find_new_(classdict, 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):
"""
Returns the __new__ to be used for creating the enum members.
@@ -943,9 +1071,42 @@ EnumMeta = EnumType
class Enum(metaclass=EnumType):
"""
- Generic enumeration.
+ 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>
- Derive from this class to define new enumerations.
+ - 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.
"""
def __new__(cls, value):
@@ -999,6 +1160,9 @@ 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.
@@ -1021,47 +1185,44 @@ class Enum(metaclass=EnumType):
return None
def __repr__(self):
- return "%s.%s" % ( self.__class__.__name__, self._name_)
+ v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
+ return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_))
def __str__(self):
- return "%s" % (self._name_, )
+ return "%s.%s" % (self.__class__.__name__, self._name_, )
def __dir__(self):
"""
Returns all members and all public methods
"""
- 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)
+ 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
def __format__(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)
+ return str.__format__(str(self), format_spec)
def __hash__(self):
return hash(self._name_)
@@ -1088,34 +1249,25 @@ class Enum(metaclass=EnumType):
return self._value_
-class IntEnum(int, Enum):
+class ReprEnum(Enum):
"""
- Enum where members are also (and must be) ints
+ Only changes the repr(), leaving str() and format() to the mixed-in type.
"""
- def __str__(self):
- return "%s" % (self._name_, )
- 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 IntEnum(int, ReprEnum):
+ """
+ Enum where members are also (and must be) ints
+ """
-class StrEnum(str, Enum):
+class StrEnum(str, ReprEnum):
"""
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:
@@ -1135,10 +1287,6 @@ class StrEnum(str, Enum):
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.
@@ -1169,6 +1317,8 @@ 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.
@@ -1184,7 +1334,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
@@ -1232,8 +1382,8 @@ class Flag(Enum, boundary=STRICT):
if cls._boundary_ is STRICT:
max_bits = max(value.bit_length(), flag_mask.bit_length())
raise ValueError(
- "%s: invalid value: %r\n given %s\n allowed %s" % (
- cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits),
+ "%r invalid value %r\n given %s\n allowed %s" % (
+ cls, value, bin(value, max_bits), bin(flag_mask, max_bits),
))
elif cls._boundary_ is CONFORM:
value = value & flag_mask
@@ -1247,7 +1397,7 @@ class Flag(Enum, boundary=STRICT):
)
else:
raise ValueError(
- 'unknown flag boundary: %r' % (cls._boundary_, )
+ '%r unknown flag boundary %r' % (cls, cls._boundary_, )
)
if value < 0:
neg_value = value
@@ -1274,7 +1424,7 @@ class Flag(Enum, boundary=STRICT):
m._name_ for m in cls._iter_member_(member_value)
])
if unknown:
- pseudo_member._name_ += '|0x%x' % unknown
+ pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown)
else:
pseudo_member._name_ = None
# use setdefault in case another thread already created a composite
@@ -1292,10 +1442,8 @@ class Flag(Enum, boundary=STRICT):
"""
if not isinstance(other, self.__class__):
raise TypeError(
- "unsupported operand type(s) for 'in': '%s' and '%s'" % (
+ "unsupported operand type(s) for 'in': %r and %r" % (
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):
@@ -1309,27 +1457,18 @@ 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 "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('|'))
+ return "<%s: %s>" % (cls_name, v_repr(self._value_))
else:
- 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)
+ return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_))
def __str__(self):
- cls = self.__class__
+ cls_name = self.__class__.__name__
if self._name_ is None:
- return '%s(%x)' % (cls.__name__, self._value_)
+ return '%s(%r)' % (cls_name, self._value_)
else:
- return self._name_
+ return "%s.%s" % (cls_name, self._name_)
def __bool__(self):
return bool(self._value_)
@@ -1362,20 +1501,11 @@ class Flag(Enum, boundary=STRICT):
return self._inverted_
-class IntFlag(int, Flag, boundary=EJECT):
+class IntFlag(int, ReprEnum, 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__):
@@ -1412,6 +1542,7 @@ class IntFlag(int, 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
@@ -1456,7 +1587,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(0x%x)" % (module, cls_name, self._value_)
+ return "%s.%s(%r)" % (module, cls_name, self._value_)
if _is_single_bit(self):
return '%s.%s' % (module, self._name_)
if self._boundary_ is not FlagBoundary.KEEP:
@@ -1464,14 +1595,22 @@ def global_flag_repr(self):
else:
name = []
for n in self._name_.split('|'):
- if n.startswith('0'):
+ if n[0].isdigit():
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):
+def global_enum(cls, update_str=False):
"""
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
@@ -1481,6 +1620,8 @@ def global_enum(cls):
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
@@ -1522,6 +1663,7 @@ 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
@@ -1543,13 +1685,8 @@ 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 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)
+ if name not in body:
+ setattr(enum_class, name, getattr(etype, name))
gnv_last_values = []
if issubclass(enum_class, Flag):
# Flag / IntFlag
@@ -1760,8 +1897,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_'):
- # keys known to be different
+ if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'):
+ # keys known to be different, or very long
continue
elif key in member_names:
# members are checked below
@@ -1882,3 +2019,5 @@ 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