summaryrefslogtreecommitdiffstats
path: root/Lib/enum.py
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2021-12-02 16:49:52 (GMT)
committerGitHub <noreply@github.com>2021-12-02 16:49:52 (GMT)
commitb2afdc95cc8f4e9228148730949a43cef0323f15 (patch)
tree00f99a2e959b803ee11434f6866215ac63dc90e6 /Lib/enum.py
parentcb8f491f46e262549f6c447b31625cab7c20a60a (diff)
downloadcpython-b2afdc95cc8f4e9228148730949a43cef0323f15.zip
cpython-b2afdc95cc8f4e9228148730949a43cef0323f15.tar.gz
cpython-b2afdc95cc8f4e9228148730949a43cef0323f15.tar.bz2
bpo-45535: Improve output of Enum ``dir()`` (GH-29316)
Modify the ``EnumType.__dir__()`` and ``Enum.__dir__()`` to ensure that user-defined methods and methods inherited from mixin classes always show up in the output of `help()`. This change also makes it easier for IDEs to provide auto-completion.
Diffstat (limited to 'Lib/enum.py')
-rw-r--r--Lib/enum.py69
1 files changed, 58 insertions, 11 deletions
diff --git a/Lib/enum.py b/Lib/enum.py
index 461d276..8efc38c 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -635,10 +635,60 @@ class EnumType(type):
super().__delattr__(attr)
def __dir__(self):
- return (
- ['__class__', '__doc__', '__members__', '__module__']
- + self._member_names_
- )
+ # 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))
+
+ # special-case __new__
+ if self.__new__ is not first_enum_base.__new__:
+ add_to_dir('__new__')
+
+ 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):
"""
@@ -985,13 +1035,10 @@ class Enum(metaclass=EnumType):
"""
Returns all members and all public methods
"""
- added_behavior = [
- m
- for cls in self.__class__.mro()
- for m in cls.__dict__
- if m[0] != '_' and m not in self._member_map_
- ] + [m for m in self.__dict__ if m[0] != '_']
- return (['__class__', '__doc__', '__module__'] + added_behavior)
+ 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):
"""