diff options
Diffstat (limited to 'Lib/enum.py')
-rw-r--r-- | Lib/enum.py | 603 |
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 |