diff options
Diffstat (limited to 'Lib/enum.py')
-rw-r--r-- | Lib/enum.py | 69 |
1 files changed, 42 insertions, 27 deletions
diff --git a/Lib/enum.py b/Lib/enum.py index 82058ae924..86f94d905f 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -16,6 +16,8 @@ class _RouteClassAttributeToGetattr: """ def __init__(self, fget=None): self.fget = fget + if fget.__doc__ is not None: + self.__doc__ = fget.__doc__ def __get__(self, instance, ownerclass=None): if instance is None: @@ -29,6 +31,14 @@ class _RouteClassAttributeToGetattr: raise AttributeError("can't delete attribute") +def _is_descriptor(obj): + """Returns True if obj is a descriptor, False otherwise.""" + return ( + hasattr(obj, '__get__') or + hasattr(obj, '__set__') or + hasattr(obj, '__delete__')) + + def _is_dunder(name): """Returns True if a __dunder__ name, False otherwise.""" return (name[:2] == name[-2:] == '__' and @@ -50,8 +60,9 @@ def _make_class_unpicklable(cls): cls.__reduce__ = _break_on_call_reduce cls.__module__ = '<unknown>' + class _EnumDict(dict): - """Keeps track of definition order of the enum items. + """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. @@ -62,11 +73,7 @@ class _EnumDict(dict): self._member_names = [] def __setitem__(self, key, value): - """Changes anything not dundered or that doesn't have __get__. - - If a descriptor is added with the same name as an enum member, the name - is removed from _member_names (this may leave a hole in the numerical - sequence of values). + """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. @@ -76,19 +83,20 @@ class _EnumDict(dict): """ if _is_sunder(key): raise ValueError('_names_ are reserved for future Enum use') - elif _is_dunder(key) or hasattr(value, '__get__'): - if key in self._member_names: - # overwriting an enum with a method? then remove the name from - # _member_names or it will become an enum anyway when the class - # is created - self._member_names.remove(key) - else: - if key in self._member_names: - raise TypeError('Attempted to reuse key: %r' % key) + elif _is_dunder(key): + pass + elif key in self._member_names: + # descriptor overwriting an enum? + raise TypeError('Attempted to reuse key: %r' % key) + elif not _is_descriptor(value): + if key in self: + # enum overwriting a descriptor? + raise TypeError('Key already defined as: %r' % self[key]) self._member_names.append(key) super().__setitem__(key, value) + # Dummy value for Enum as EnumMeta explicitly checks for it, but of course # until EnumMeta finishes running the first time the Enum class doesn't exist. # This is also why there are checks in EnumMeta like `if Enum is not None` @@ -160,6 +168,7 @@ class EnumMeta(type): enum_member._value_ = member_type(*args) value = enum_member._value_ enum_member._name_ = member_name + enum_member.__objclass__ = enum_class enum_member.__init__(*args) # If another member with the same value was already defined, the # new member becomes an alias to the existing one. @@ -223,17 +232,7 @@ class EnumMeta(type): return isinstance(member, cls) and member.name in cls._member_map_ def __dir__(self): - return ['__class__', '__doc__', '__members__'] + self._member_names_ - - @property - def __members__(cls): - """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_) + return ['__class__', '__doc__', '__members__', '__module__'] + self._member_names_ def __getattr__(cls, name): """Return the enum member matching `name` @@ -260,9 +259,22 @@ class EnumMeta(type): def __len__(cls): return len(cls._member_names_) + @property + def __members__(cls): + """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_) + def __repr__(cls): return "<enum %r>" % cls.__name__ + def __reversed__(cls): + return (cls._member_map_[name] for name in reversed(cls._member_names_)) + def __setattr__(cls, name, value): """Block attempts to reassign Enum members. @@ -446,7 +458,8 @@ class Enum(metaclass=EnumMeta): return "%s.%s" % (self.__class__.__name__, self._name_) def __dir__(self): - return (['__class__', '__doc__', 'name', 'value']) + added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_'] + return ['__class__', '__doc__', '__module__', 'name', 'value'] + added_behavior def __eq__(self, other): if type(other) is self.__class__: @@ -483,10 +496,12 @@ class Enum(metaclass=EnumMeta): @_RouteClassAttributeToGetattr def name(self): + """The name of the Enum member.""" return self._name_ @_RouteClassAttributeToGetattr def value(self): + """The value of the Enum member.""" return self._value_ |