From 6d3dfee271b6e4afbfb060c269b034b871e2d1b3 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 8 Dec 2020 12:26:56 -0800 Subject: [Enum] reformat and add doc strings (GH-23705) --- Lib/enum.py | 187 ++++++++++++++++++++++++++++++++++++-------------- Lib/test/test_enum.py | 2 +- 2 files changed, 135 insertions(+), 54 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 484db2c..f6c7e8b 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -10,31 +10,41 @@ __all__ = [ def _is_descriptor(obj): - """Returns True if obj is a descriptor, False otherwise.""" + """ + Returns True if obj is a descriptor, False otherwise. + """ return ( hasattr(obj, '__get__') or hasattr(obj, '__set__') or - hasattr(obj, '__delete__')) - + hasattr(obj, '__delete__') + ) def _is_dunder(name): - """Returns True if a __dunder__ name, False otherwise.""" - return (len(name) > 4 and + """ + Returns True if a __dunder__ name, False otherwise. + """ + return ( + len(name) > 4 and name[:2] == name[-2:] == '__' and name[2] != '_' and - name[-3] != '_') - + name[-3] != '_' + ) def _is_sunder(name): - """Returns True if a _sunder_ name, False otherwise.""" - return (len(name) > 2 and + """ + Returns True if a _sunder_ name, False otherwise. + """ + return ( + len(name) > 2 and name[0] == name[-1] == '_' and name[1:2] != '_' and - name[-2:-1] != '_') - + name[-2:-1] != '_' + ) def _make_class_unpicklable(cls): - """Make the given class un-picklable.""" + """ + Make the given class un-picklable. + """ def _break_on_call_reduce(self, proto): raise TypeError('%r cannot be pickled' % self) cls.__reduce_ex__ = _break_on_call_reduce @@ -49,11 +59,11 @@ class auto: class _EnumDict(dict): - """Track enum member order and ensure member names are not reused. + """ + Track enum member order and ensure member names are not reused. EnumMeta will use the names found in self._member_names as the enumeration member names. - """ def __init__(self): super().__init__() @@ -63,21 +73,23 @@ class _EnumDict(dict): self._auto_called = False def __setitem__(self, key, value): - """Changes anything not dundered or not a descriptor. + """ + Changes anything not dundered or not a descriptor. If an enum member name is used twice, an error is raised; duplicate values are not checked for. Single underscore (sunder) names are reserved. - """ if _is_sunder(key): if key not in ( '_order_', '_create_pseudo_member_', '_generate_next_value_', '_missing_', '_ignore_', ): - raise ValueError(f'_sunder_ names, such as "{key}", are ' - 'reserved for future Enum use') + raise ValueError( + '_sunder_ names, such as %r, are reserved for future Enum use' + % (key, ) + ) if key == '_generate_next_value_': # check if members already defined as auto() if self._auto_called: @@ -91,7 +103,10 @@ class _EnumDict(dict): self._ignore = value already = set(value) & set(self._member_names) if already: - raise ValueError('_ignore_ cannot specify already set names: %r' % (already, )) + raise ValueError( + '_ignore_ cannot specify already set names: %r' + % (already, ) + ) elif _is_dunder(key): if key == '__order__': key = '_order_' @@ -106,7 +121,12 @@ class _EnumDict(dict): 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(key, 1, len(self._member_names), self._last_values[:]) + value.value = self._generate_next_value( + key, + 1, + len(self._member_names), + self._last_values[:], + ) self._auto_called = True value = value.value self._member_names.append(key) @@ -119,9 +139,10 @@ class _EnumDict(dict): # This is also why there are checks in EnumMeta like `if Enum is not None` Enum = None - class EnumMeta(type): - """Metaclass for Enum""" + """ + Metaclass for Enum + """ @classmethod def __prepare__(metacls, cls, bases): # check that previous enum members do not exist @@ -131,7 +152,9 @@ class EnumMeta(type): # inherit previous flags and _generate_next_value_ function member_type, first_enum = metacls._get_mixins_(cls, bases) if first_enum is not None: - enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None) + enum_dict['_generate_next_value_'] = getattr( + first_enum, '_generate_next_value_', None, + ) return enum_dict def __new__(metacls, cls, bases, classdict): @@ -177,9 +200,11 @@ class EnumMeta(type): # save DynamicClassAttribute attributes from super classes so we know # if we can take the shortcut of storing members in the class dict - dynamic_attributes = {k for c in enum_class.mro() - for k, v in c.__dict__.items() - if isinstance(v, DynamicClassAttribute)} + dynamic_attributes = { + k for c in enum_class.mro() + for k, v in c.__dict__.items() + if isinstance(v, DynamicClassAttribute) + } # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} @@ -289,7 +314,8 @@ class EnumMeta(type): return True def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): - """Either returns an existing member, or creates a new enum class. + """ + Either returns an existing member, or creates a new enum class. This method is used both when an enum class is given a value to match to an enumeration member (i.e. Color(3)) and for the functional API @@ -311,12 +337,18 @@ class EnumMeta(type): not correct, unpickling will fail in some circumstances. `type`, if set, will be mixed in as the first base class. - """ if names is None: # simple value lookup return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start) + return cls._create_( + value, + names, + module=module, + qualname=qualname, + type=type, + start=start, + ) def __contains__(cls, member): if not isinstance(member, Enum): @@ -329,22 +361,23 @@ class EnumMeta(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." % cls.__name__) + raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr)) super().__delattr__(attr) def __dir__(self): - return (['__class__', '__doc__', '__members__', '__module__'] + - self._member_names_) + return ( + ['__class__', '__doc__', '__members__', '__module__'] + + self._member_names_ + ) def __getattr__(cls, name): - """Return the enum member matching `name` + """ + Return the enum member matching `name` We use __getattr__ instead of descriptors or inserting into the enum class' __dict__ in order to support `name` and `value` being both properties for enum members (which live in the class' __dict__) and enum members themselves. - """ if _is_dunder(name): raise AttributeError(name) @@ -357,6 +390,9 @@ class EnumMeta(type): return cls._member_map_[name] def __iter__(cls): + """ + Returns members in definition order. + """ return (cls._member_map_[name] for name in cls._member_names_) def __len__(cls): @@ -364,11 +400,11 @@ class EnumMeta(type): @property def __members__(cls): - """Returns a mapping of member name->value. + """ + Returns a mapping of member name->value. This mapping lists all enum members, including aliases. Note that this is a read-only view of the internal mapping. - """ return MappingProxyType(cls._member_map_) @@ -376,15 +412,18 @@ class EnumMeta(type): return "" % cls.__name__ def __reversed__(cls): + """ + Returns members in reverse definition order. + """ return (cls._member_map_[name] for name in reversed(cls._member_names_)) def __setattr__(cls, name, value): - """Block attempts to reassign Enum members. + """ + Block attempts to reassign Enum members. A simple assignment to the class namespace only changes one of the several possible ways to get an Enum member from the Enum class, resulting in an inconsistent Enumeration. - """ member_map = cls.__dict__.get('_member_map_', {}) if name in member_map: @@ -392,7 +431,8 @@ class EnumMeta(type): super().__setattr__(name, value) def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1): - """Convenience method to create a new Enum class. + """ + Convenience method to create a new Enum class. `names` can be: @@ -401,7 +441,6 @@ class EnumMeta(type): * An iterable of member names. Values are incremented by 1 from `start`. * An iterable of (member name, value) pairs. * A mapping of member name -> value pairs. - """ metacls = cls.__class__ bases = (cls, ) if type is None else (type, cls) @@ -482,15 +521,18 @@ class EnumMeta(type): 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__)) + raise TypeError( + "%s: cannot extend enumeration %r" + % (class_name, base.__name__) + ) @staticmethod def _get_mixins_(class_name, bases): - """Returns the type for creating enum members, and the first inherited + """ + Returns the type for creating enum members, and the first inherited enum class. bases: the tuple of bases that was given to __new__ - """ if not bases: return object, Enum @@ -533,12 +575,12 @@ class EnumMeta(type): @staticmethod def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. + """ + Returns the __new__ to be used for creating the enum members. classdict: the class dictionary given to __new__ member_type: the data type whose __new__ will be used by default first_enum: enumeration to check for an overriding __new__ - """ # now find the correct __new__, checking to see of one was defined # by the user; also check earlier enum classes in case a __new__ was @@ -578,10 +620,10 @@ class EnumMeta(type): class Enum(metaclass=EnumMeta): - """Generic enumeration. + """ + Generic enumeration. Derive from this class to define new enumerations. - """ def __new__(cls, value): # all enum instances are actually created during class construction @@ -624,6 +666,14 @@ class Enum(metaclass=EnumMeta): raise exc def _generate_next_value_(name, start, count, last_values): + """ + Generate the next value when not given. + + name: the name of the member + start: the initial start value or None + count: the number of existing members + last_value: the last value assigned or None + """ for last_value in reversed(last_values): try: return last_value + 1 @@ -644,6 +694,9 @@ class Enum(metaclass=EnumMeta): return "%s.%s" % (self.__class__.__name__, self._name_) def __dir__(self): + """ + Returns all members and all public methods + """ added_behavior = [ m for cls in self.__class__.mro() @@ -653,6 +706,9 @@ class Enum(metaclass=EnumMeta): return (['__class__', '__doc__', '__module__'] + added_behavior) 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 @@ -730,7 +786,9 @@ def _reduce_ex_by_name(self, proto): return self.name class Flag(Enum): - """Support for flags""" + """ + Support for flags + """ def _generate_next_value_(name, start, count, last_values): """ @@ -753,6 +811,9 @@ class Flag(Enum): @classmethod def _missing_(cls, value): + """ + Returns member (possibly creating it) if one can be found for value. + """ original_value = value if value < 0: value = ~value @@ -782,6 +843,9 @@ class Flag(Enum): return pseudo_member def __contains__(self, other): + """ + Returns True if self has at least the same flags set as other. + """ if not isinstance(other, self.__class__): raise TypeError( "unsupported operand type(s) for 'in': '%s' and '%s'" % ( @@ -789,6 +853,9 @@ class Flag(Enum): return other._value_ & self._value_ == other._value_ def __iter__(self): + """ + Returns flags in decreasing value order. + """ members, extra_flags = _decompose(self.__class__, self.value) return (m for m in members if m._value_ != 0) @@ -844,10 +911,15 @@ class Flag(Enum): class IntFlag(int, Flag): - """Support for integer-based Flags""" + """ + Support for integer-based Flags + """ @classmethod def _missing_(cls, value): + """ + Returns member (possibly creating it) if one can be found for value. + """ if not isinstance(value, int): raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) new_member = cls._create_pseudo_member_(value) @@ -855,6 +927,9 @@ class IntFlag(int, Flag): @classmethod def _create_pseudo_member_(cls, value): + """ + Create a composite member iff value contains only members. + """ pseudo_member = cls._value2member_map_.get(value, None) if pseudo_member is None: need_to_create = [value] @@ -909,11 +984,15 @@ class IntFlag(int, Flag): def _high_bit(value): - """returns index of highest bit, or -1 if value is zero or negative""" + """ + returns index of highest bit, or -1 if value is zero or negative + """ return value.bit_length() - 1 def unique(enumeration): - """Class decorator for enumerations ensuring unique member values.""" + """ + Class decorator for enumerations ensuring unique member values. + """ duplicates = [] for name, member in enumeration.__members__.items(): if name != member.name: @@ -926,7 +1005,9 @@ def unique(enumeration): return enumeration def _decompose(flag, value): - """Extract all members from the value.""" + """ + Extract all members from the value. + """ # _decompose is only called if the value is not named not_covered = value negative = value < 0 diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index f2171b5..ab4b52f 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -429,7 +429,7 @@ class TestEnum(unittest.TestCase): def test_reserved__sunder_(self): with self.assertRaisesRegex( ValueError, - '_sunder_ names, such as "_bad_", are reserved', + "_sunder_ names, such as '_bad_', are reserved", ): class Bad(Enum): _bad_ = 1 -- cgit v0.12