Skip to content

bpo-45535: [Enum] include special dunders in dir() #30677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 13 additions & 20 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,29 +766,22 @@ def __delattr__(cls, attr):
super().__delattr__(attr)

def __dir__(cls):
# TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__
# on object-based enums
interesting = set([
'__class__', '__contains__', '__doc__', '__getitem__',
'__iter__', '__len__', '__members__', '__module__',
'__name__', '__qualname__',
]
+ 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__')
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
)
return sorted(interesting)
else:
# return whatever mixed-in data type has
return sorted(set(
dir(cls._member_type_)
+ cls._member_names_
))
return sorted(set(dir(cls._member_type_)) | interesting)

def __getattr__(cls, name):
"""
Expand Down
53 changes: 29 additions & 24 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,14 +883,15 @@ class Part(Enum):
with self.assertRaises(TypeError):
Season.SPRING < Part.CLIP

@unittest.skip('to-do list')
def test_dir_with_custom_dunders(self):
class PlainEnum(Enum):
pass
cls_dir = dir(PlainEnum)
self.assertNotIn('__repr__', cls_dir)
self.assertNotIn('__str__', cls_dir)
self.assertNotIn('__repr__', cls_dir)
self.assertNotIn('__repr__', cls_dir)
self.assertNotIn('__format__', cls_dir)
self.assertNotIn('__init__', cls_dir)
#
class MyEnum(Enum):
def __repr__(self):
Expand All @@ -904,8 +905,8 @@ def __init__(self):
cls_dir = dir(MyEnum)
self.assertIn('__repr__', cls_dir)
self.assertIn('__str__', cls_dir)
self.assertIn('__repr__', cls_dir)
self.assertIn('__repr__', cls_dir)
self.assertIn('__format__', cls_dir)
self.assertIn('__init__', cls_dir)

def test_duplicate_name_error(self):
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -4322,13 +4323,18 @@ def test_convert_int(self):
int_dir = dir(int) + [
'CONVERT_TEST_NAME_A', 'CONVERT_TEST_NAME_B', 'CONVERT_TEST_NAME_C',
'CONVERT_TEST_NAME_D', 'CONVERT_TEST_NAME_E', 'CONVERT_TEST_NAME_F',
'CONVERT_TEST_SIGABRT', 'CONVERT_TEST_SIGIOT',
'CONVERT_TEST_EIO', 'CONVERT_TEST_EBUS',
]
extra = [name for name in dir(test_type) if name not in enum_dir(test_type)]
missing = [name for name in enum_dir(test_type) if name not in dir(test_type)]
self.assertEqual(
[name for name in dir(test_type) if name not in int_dir],
extra + missing,
[],
msg='Names other than CONVERT_TEST_* found.',
msg='extra names: %r; missing names: %r' % (extra, missing),
)


def test_convert_uncomparable(self):
uncomp = enum.Enum._convert_(
'Uncomparable',
Expand Down Expand Up @@ -4362,10 +4368,12 @@ def test_convert_str(self):
self.assertEqual(test_type.CONVERT_STR_TEST_2, 'goodbye')
# Ensure that test_type only picked up names matching the filter.
str_dir = dir(str) + ['CONVERT_STR_TEST_1', 'CONVERT_STR_TEST_2']
extra = [name for name in dir(test_type) if name not in enum_dir(test_type)]
missing = [name for name in enum_dir(test_type) if name not in dir(test_type)]
self.assertEqual(
[name for name in dir(test_type) if name not in str_dir],
extra + missing,
[],
msg='Names other than CONVERT_STR_* found.',
msg='extra names: %r; missing names: %r' % (extra, missing),
)
self.assertEqual(repr(test_type.CONVERT_STR_TEST_1), '%s.CONVERT_STR_TEST_1' % SHORT_MODULE)
self.assertEqual(str(test_type.CONVERT_STR_TEST_2), 'goodbye')
Expand All @@ -4392,25 +4400,22 @@ def test_convert_repr_and_str(self):
# helpers

def enum_dir(cls):
# TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__
interesting = set([
'__class__', '__contains__', '__doc__', '__getitem__',
'__iter__', '__len__', '__members__', '__module__',
'__name__', '__qualname__',
]
+ 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__')
if cls._member_type_ is object:
interesting = set()
if cls.__init_subclass__ is not object.__init_subclass__:
interesting.add('__init_subclass__')
return sorted(set([
'__class__', '__contains__', '__doc__', '__getitem__',
'__iter__', '__len__', '__members__', '__module__',
'__name__', '__qualname__',
]
+ cls._member_names_
) | interesting
)
return sorted(interesting)
else:
# return whatever mixed-in data type has
return sorted(set(
dir(cls._member_type_)
+ cls._member_names_
))
return sorted(set(dir(cls._member_type_)) | interesting)

def member_dir(member):
if member.__class__._member_type_ is object:
Expand Down