diff options
author | Victor Stinner <vstinner@python.org> | 2022-01-17 12:58:40 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-17 12:58:40 (GMT) |
commit | 42a64c03ec5c443f2a5c2ee4284622f5d1f5326c (patch) | |
tree | d5fffd97234c4a8481ee3f07a69107188f1faa7d /Lib/enum.py | |
parent | 7f4b69b9076bdbcea31f6ad16eb125ee99cf0175 (diff) | |
download | cpython-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.py | 603 |
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 |