mirror of
https://github.com/python/cpython.git
synced 2025-08-22 09:45:06 +00:00
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.
This commit is contained in:
parent
cb8f491f46
commit
b2afdc95cc
5 changed files with 386 additions and 53 deletions
69
Lib/enum.py
69
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):
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue