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

Include the `__dunders__` in `dir()` that make `Enum` special:

- `__contains__`
- `__getitem__`
- `__iter__`
- `__len__`
- `__members__`
This commit is contained in:
Ethan Furman 2022-01-18 15:13:13 -08:00 committed by GitHub
parent 3852269b91
commit 7c0914d35e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 44 deletions

View file

@ -766,29 +766,22 @@ class EnumType(type):
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):
"""

View file

@ -883,14 +883,15 @@ class TestSpecial(unittest.TestCase):
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):
@ -904,8 +905,8 @@ class TestSpecial(unittest.TestCase):
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):
@ -4322,13 +4323,18 @@ class TestConvert(unittest.TestCase):
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',
@ -4362,10 +4368,12 @@ class TestConvert(unittest.TestCase):
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')
@ -4392,25 +4400,22 @@ class TestConvert(unittest.TestCase):
# 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: