bpo-40066: Enum: modify repr() and str() (GH-22392)

* Enum: streamline repr() and str(); improve docs

- repr() is now ``enum_class.member_name``
- stdlib global enums are ``module_name.member_name``
- str() is now ``member_name``
- add HOW-TO section for ``Enum``
- change main documentation to be an API reference
This commit is contained in:
Ethan Furman 2021-03-30 21:17:26 -07:00 committed by GitHub
parent 51a85ddce8
commit b775106d94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 2225 additions and 1491 deletions

View file

@ -7,7 +7,7 @@ import sys
import unittest
import threading
from collections import OrderedDict
from enum import Enum, IntEnum, StrEnum, EnumMeta, Flag, IntFlag, unique, auto
from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
from enum import STRICT, CONFORM, EJECT, KEEP
from io import StringIO
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
@ -262,11 +262,8 @@ class TestEnum(unittest.TestCase):
self.assertIn(e, Season)
self.assertIs(type(e), Season)
self.assertIsInstance(e, Season)
self.assertEqual(str(e), 'Season.' + season)
self.assertEqual(
repr(e),
'<Season.{0}: {1}>'.format(season, i),
)
self.assertEqual(str(e), season)
self.assertEqual(repr(e), 'Season.{0}'.format(season))
def test_value_name(self):
Season = self.Season
@ -440,7 +437,7 @@ class TestEnum(unittest.TestCase):
def test_reserved__sunder_(self):
with self.assertRaisesRegex(
ValueError,
"_sunder_ names, such as '_bad_', are reserved",
'_sunder_ names, such as ._bad_., are reserved',
):
class Bad(Enum):
_bad_ = 1
@ -488,7 +485,7 @@ class TestEnum(unittest.TestCase):
two = 2.0
def __format__(self, spec):
return 'Format!!'
self.assertEqual(str(EnumWithFormatOverride.one), 'EnumWithFormatOverride.one')
self.assertEqual(str(EnumWithFormatOverride.one), 'one')
self.assertEqual('{}'.format(EnumWithFormatOverride.one), 'Format!!')
def test_str_and_format_override_enum(self):
@ -528,7 +525,7 @@ class TestEnum(unittest.TestCase):
two = 2.0
def __format__(self, spec):
return 'TestFloat success!'
self.assertEqual(str(TestFloat.one), 'TestFloat.one')
self.assertEqual(str(TestFloat.one), 'one')
self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!')
def assertFormatIsValue(self, spec, member):
@ -614,6 +611,8 @@ class TestEnum(unittest.TestCase):
A = 1
B = 2
C = 3
def __repr__(self):
return '<%s.%s: %r>' % (self.__class__.__name__, self._name_, self._value_)
self.assertEqual(repr(MyEnum.A), '<MyEnum.A: 0x1>')
def test_too_many_data_types(self):
@ -1959,7 +1958,7 @@ class TestEnum(unittest.TestCase):
self.assertEqual(Color.GREEN.value, 2)
self.assertEqual(Color.BLUE.value, 3)
self.assertEqual(Color.MAX, 3)
self.assertEqual(str(Color.BLUE), 'Color.BLUE')
self.assertEqual(str(Color.BLUE), 'BLUE')
class Color(MaxMixin, StrMixin, Enum):
RED = auto()
GREEN = auto()
@ -2330,64 +2329,62 @@ class TestFlag(unittest.TestCase):
def test_str(self):
Perm = self.Perm
self.assertEqual(str(Perm.R), 'Perm.R')
self.assertEqual(str(Perm.W), 'Perm.W')
self.assertEqual(str(Perm.X), 'Perm.X')
self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X')
self.assertEqual(str(Perm.R), 'R')
self.assertEqual(str(Perm.W), 'W')
self.assertEqual(str(Perm.X), 'X')
self.assertEqual(str(Perm.R | Perm.W), 'R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'R|W|X')
self.assertEqual(str(Perm(0)), 'Perm(0)')
self.assertEqual(str(~Perm.R), 'Perm.W|X')
self.assertEqual(str(~Perm.W), 'Perm.R|X')
self.assertEqual(str(~Perm.X), 'Perm.R|W')
self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X')
self.assertEqual(str(~Perm.R), 'W|X')
self.assertEqual(str(~Perm.W), 'R|X')
self.assertEqual(str(~Perm.X), 'R|W')
self.assertEqual(str(~(Perm.R | Perm.W)), 'X')
self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)')
self.assertEqual(str(Perm(~0)), 'Perm.R|W|X')
self.assertEqual(str(Perm(~0)), 'R|W|X')
Open = self.Open
self.assertEqual(str(Open.RO), 'Open.RO')
self.assertEqual(str(Open.WO), 'Open.WO')
self.assertEqual(str(Open.AC), 'Open.AC')
self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
self.assertEqual(str(~Open.WO), 'Open.RW|CE')
self.assertEqual(str(~Open.AC), 'Open.CE')
self.assertEqual(str(~Open.CE), 'Open.AC')
self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC')
self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW')
self.assertEqual(str(Open.RO), 'RO')
self.assertEqual(str(Open.WO), 'WO')
self.assertEqual(str(Open.AC), 'AC')
self.assertEqual(str(Open.RO | Open.CE), 'CE')
self.assertEqual(str(Open.WO | Open.CE), 'WO|CE')
self.assertEqual(str(~Open.RO), 'WO|RW|CE')
self.assertEqual(str(~Open.WO), 'RW|CE')
self.assertEqual(str(~Open.AC), 'CE')
self.assertEqual(str(~(Open.RO | Open.CE)), 'AC')
self.assertEqual(str(~(Open.WO | Open.CE)), 'RW')
def test_repr(self):
Perm = self.Perm
self.assertEqual(repr(Perm.R), '<Perm.R: 4>')
self.assertEqual(repr(Perm.W), '<Perm.W: 2>')
self.assertEqual(repr(Perm.X), '<Perm.X: 1>')
self.assertEqual(repr(Perm.R | Perm.W), '<Perm.R|W: 6>')
self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '<Perm.R|W|X: 7>')
self.assertEqual(repr(Perm(0)), '<Perm: 0>')
self.assertEqual(repr(~Perm.R), '<Perm.W|X: 3>')
self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>')
self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: 1>')
self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm: 0>')
self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
self.assertEqual(repr(Perm.R), 'Perm.R')
self.assertEqual(repr(Perm.W), 'Perm.W')
self.assertEqual(repr(Perm.X), 'Perm.X')
self.assertEqual(repr(Perm.R | Perm.W), 'Perm.R|Perm.W')
self.assertEqual(repr(Perm.R | Perm.W | Perm.X), 'Perm.R|Perm.W|Perm.X')
self.assertEqual(repr(Perm(0)), '0x0')
self.assertEqual(repr(~Perm.R), 'Perm.W|Perm.X')
self.assertEqual(repr(~Perm.W), 'Perm.R|Perm.X')
self.assertEqual(repr(~Perm.X), 'Perm.R|Perm.W')
self.assertEqual(repr(~(Perm.R | Perm.W)), 'Perm.X')
self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '0x0')
self.assertEqual(repr(Perm(~0)), 'Perm.R|Perm.W|Perm.X')
Open = self.Open
self.assertEqual(repr(Open.RO), '<Open.RO: 0>')
self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>')
self.assertEqual(repr(Open.WO | Open.CE), '<Open.WO|CE: 524289>')
self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>')
self.assertEqual(repr(~Open.CE), '<Open.AC: 3>')
self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>')
self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
self.assertEqual(repr(Open.RO), 'Open.RO')
self.assertEqual(repr(Open.WO), 'Open.WO')
self.assertEqual(repr(Open.AC), 'Open.AC')
self.assertEqual(repr(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(repr(Open.WO | Open.CE), 'Open.WO|Open.CE')
self.assertEqual(repr(~Open.RO), 'Open.WO|Open.RW|Open.CE')
self.assertEqual(repr(~Open.WO), 'Open.RW|Open.CE')
self.assertEqual(repr(~Open.AC), 'Open.CE')
self.assertEqual(repr(~(Open.RO | Open.CE)), 'Open.AC')
self.assertEqual(repr(~(Open.WO | Open.CE)), 'Open.RW')
def test_format(self):
Perm = self.Perm
self.assertEqual(format(Perm.R, ''), 'Perm.R')
self.assertEqual(format(Perm.R | Perm.X, ''), 'Perm.R|X')
self.assertEqual(format(Perm.R, ''), 'R')
self.assertEqual(format(Perm.R | Perm.X, ''), 'R|X')
def test_or(self):
Perm = self.Perm
@ -2707,7 +2704,7 @@ class TestFlag(unittest.TestCase):
self.assertEqual(Color.GREEN.value, 2)
self.assertEqual(Color.BLUE.value, 4)
self.assertEqual(Color.ALL.value, 7)
self.assertEqual(str(Color.BLUE), 'Color.BLUE')
self.assertEqual(str(Color.BLUE), 'BLUE')
class Color(AllMixin, StrMixin, Flag):
RED = auto()
GREEN = auto()
@ -2850,77 +2847,70 @@ class TestIntFlag(unittest.TestCase):
def test_str(self):
Perm = self.Perm
self.assertEqual(str(Perm.R), 'Perm.R')
self.assertEqual(str(Perm.W), 'Perm.W')
self.assertEqual(str(Perm.X), 'Perm.X')
self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X')
self.assertEqual(str(Perm.R), 'R')
self.assertEqual(str(Perm.W), 'W')
self.assertEqual(str(Perm.X), 'X')
self.assertEqual(str(Perm.R | Perm.W), 'R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'R|W|X')
self.assertEqual(str(Perm.R | 8), '12')
self.assertEqual(str(Perm(0)), 'Perm(0)')
self.assertEqual(str(Perm(8)), '8')
self.assertEqual(str(~Perm.R), 'Perm.W|X')
self.assertEqual(str(~Perm.W), 'Perm.R|X')
self.assertEqual(str(~Perm.X), 'Perm.R|W')
self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X')
self.assertEqual(str(~Perm.R), 'W|X')
self.assertEqual(str(~Perm.W), 'R|X')
self.assertEqual(str(~Perm.X), 'R|W')
self.assertEqual(str(~(Perm.R | Perm.W)), 'X')
self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)')
self.assertEqual(str(~(Perm.R | 8)), '-13')
self.assertEqual(str(Perm(~0)), 'Perm.R|W|X')
self.assertEqual(str(Perm(~0)), 'R|W|X')
self.assertEqual(str(Perm(~8)), '-9')
Open = self.Open
self.assertEqual(str(Open.RO), 'Open.RO')
self.assertEqual(str(Open.WO), 'Open.WO')
self.assertEqual(str(Open.AC), 'Open.AC')
self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
self.assertEqual(str(Open.RO), 'RO')
self.assertEqual(str(Open.WO), 'WO')
self.assertEqual(str(Open.AC), 'AC')
self.assertEqual(str(Open.RO | Open.CE), 'CE')
self.assertEqual(str(Open.WO | Open.CE), 'WO|CE')
self.assertEqual(str(Open(4)), '4')
self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
self.assertEqual(str(~Open.WO), 'Open.RW|CE')
self.assertEqual(str(~Open.AC), 'Open.CE')
self.assertEqual(str(~Open.CE), 'Open.AC')
self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC')
self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW')
self.assertEqual(str(~Open.RO), 'WO|RW|CE')
self.assertEqual(str(~Open.WO), 'RW|CE')
self.assertEqual(str(~Open.AC), 'CE')
self.assertEqual(str(~(Open.RO | Open.CE)), 'AC')
self.assertEqual(str(~(Open.WO | Open.CE)), 'RW')
self.assertEqual(str(Open(~4)), '-5')
Skip = self.Skip
self.assertEqual(str(Skip(~4)), 'Skip.FIRST|SECOND|EIGHTH')
def test_repr(self):
Perm = self.Perm
self.assertEqual(repr(Perm.R), '<Perm.R: 4>')
self.assertEqual(repr(Perm.W), '<Perm.W: 2>')
self.assertEqual(repr(Perm.X), '<Perm.X: 1>')
self.assertEqual(repr(Perm.R | Perm.W), '<Perm.R|W: 6>')
self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '<Perm.R|W|X: 7>')
self.assertEqual(repr(Perm.R), 'Perm.R')
self.assertEqual(repr(Perm.W), 'Perm.W')
self.assertEqual(repr(Perm.X), 'Perm.X')
self.assertEqual(repr(Perm.R | Perm.W), 'Perm.R|Perm.W')
self.assertEqual(repr(Perm.R | Perm.W | Perm.X), 'Perm.R|Perm.W|Perm.X')
self.assertEqual(repr(Perm.R | 8), '12')
self.assertEqual(repr(Perm(0)), '<Perm: 0>')
self.assertEqual(repr(Perm(0)), '0x0')
self.assertEqual(repr(Perm(8)), '8')
self.assertEqual(repr(~Perm.R), '<Perm.W|X: 3>')
self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>')
self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: 1>')
self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm: 0>')
self.assertEqual(repr(~Perm.R), 'Perm.W|Perm.X')
self.assertEqual(repr(~Perm.W), 'Perm.R|Perm.X')
self.assertEqual(repr(~Perm.X), 'Perm.R|Perm.W')
self.assertEqual(repr(~(Perm.R | Perm.W)), 'Perm.X')
self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '0x0')
self.assertEqual(repr(~(Perm.R | 8)), '-13')
self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
self.assertEqual(repr(Perm(~0)), 'Perm.R|Perm.W|Perm.X')
self.assertEqual(repr(Perm(~8)), '-9')
Open = self.Open
self.assertEqual(repr(Open.RO), '<Open.RO: 0>')
self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>')
self.assertEqual(repr(Open.WO | Open.CE), '<Open.WO|CE: 524289>')
self.assertEqual(repr(Open.RO), 'Open.RO')
self.assertEqual(repr(Open.WO), 'Open.WO')
self.assertEqual(repr(Open.AC), 'Open.AC')
self.assertEqual(repr(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(repr(Open.WO | Open.CE), 'Open.WO|Open.CE')
self.assertEqual(repr(Open(4)), '4')
self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>')
self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>')
self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
self.assertEqual(repr(~Open.RO), 'Open.WO|Open.RW|Open.CE')
self.assertEqual(repr(~Open.WO), 'Open.RW|Open.CE')
self.assertEqual(repr(~Open.AC), 'Open.CE')
self.assertEqual(repr(~(Open.RO | Open.CE)), 'Open.AC')
self.assertEqual(repr(~(Open.WO | Open.CE)), 'Open.RW')
self.assertEqual(repr(Open(~4)), '-5')
Skip = self.Skip
self.assertEqual(repr(Skip(~4)), '<Skip.FIRST|SECOND|EIGHTH: 11>')
def test_format(self):
Perm = self.Perm
self.assertEqual(format(Perm.R, ''), '4')
@ -3252,7 +3242,7 @@ class TestIntFlag(unittest.TestCase):
self.assertEqual(Color.GREEN.value, 2)
self.assertEqual(Color.BLUE.value, 4)
self.assertEqual(Color.ALL.value, 7)
self.assertEqual(str(Color.BLUE), 'Color.BLUE')
self.assertEqual(str(Color.BLUE), 'BLUE')
class Color(AllMixin, StrMixin, IntFlag):
RED = auto()
GREEN = auto()
@ -3374,6 +3364,8 @@ class TestUnique(unittest.TestCase):
value = 4
class TestEnumTypeSubclassing(unittest.TestCase):
pass
expected_help_output_with_docs = """\
Help on class Color in module %s:
@ -3390,11 +3382,11 @@ class Color(enum.Enum)
|\x20\x20
| Data and other attributes defined here:
|\x20\x20
| blue = <Color.blue: 3>
| blue = Color.blue
|\x20\x20
| green = <Color.green: 2>
| green = Color.green
|\x20\x20
| red = <Color.red: 1>
| red = Color.red
|\x20\x20
| ----------------------------------------------------------------------
| Data descriptors inherited from enum.Enum:
@ -3406,7 +3398,7 @@ class Color(enum.Enum)
| The value of the Enum member.
|\x20\x20
| ----------------------------------------------------------------------
| Readonly properties inherited from enum.EnumMeta:
| Readonly properties inherited from enum.EnumType:
|\x20\x20
| __members__
| Returns a mapping of member name->value.
@ -3427,11 +3419,11 @@ class Color(enum.Enum)
|\x20\x20
| Data and other attributes defined here:
|\x20\x20
| blue = <Color.blue: 3>
| blue = Color.blue
|\x20\x20
| green = <Color.green: 2>
| green = Color.green
|\x20\x20
| red = <Color.red: 1>
| red = Color.red
|\x20\x20
| ----------------------------------------------------------------------
| Data descriptors inherited from enum.Enum:
@ -3441,7 +3433,7 @@ class Color(enum.Enum)
| value
|\x20\x20
| ----------------------------------------------------------------------
| Data descriptors inherited from enum.EnumMeta:
| Data descriptors inherited from enum.EnumType:
|\x20\x20
| __members__"""
@ -3468,7 +3460,7 @@ class TestStdLib(unittest.TestCase):
def test_inspect_getmembers(self):
values = dict((
('__class__', EnumMeta),
('__class__', EnumType),
('__doc__', 'An enumeration.'),
('__members__', self.Color.__members__),
('__module__', __name__),
@ -3495,11 +3487,11 @@ class TestStdLib(unittest.TestCase):
from inspect import Attribute
values = [
Attribute(name='__class__', kind='data',
defining_class=object, object=EnumMeta),
defining_class=object, object=EnumType),
Attribute(name='__doc__', kind='data',
defining_class=self.Color, object='An enumeration.'),
Attribute(name='__members__', kind='property',
defining_class=EnumMeta, object=EnumMeta.__members__),
defining_class=EnumType, object=EnumType.__members__),
Attribute(name='__module__', kind='data',
defining_class=self.Color, object=__name__),
Attribute(name='blue', kind='data',
@ -3589,6 +3581,45 @@ class TestIntEnumConvert(unittest.TestCase):
('test.test_enum', '__main__')[__name__=='__main__'],
filter=lambda x: x.startswith('CONVERT_TEST_'))
def test_convert_repr_and_str(self):
module = ('test.test_enum', '__main__')[__name__=='__main__']
test_type = enum.IntEnum._convert_(
'UnittestConvert',
module,
filter=lambda x: x.startswith('CONVERT_TEST_'))
self.assertEqual(repr(test_type.CONVERT_TEST_NAME_A), '%s.CONVERT_TEST_NAME_A' % module)
self.assertEqual(str(test_type.CONVERT_TEST_NAME_A), 'CONVERT_TEST_NAME_A')
self.assertEqual(format(test_type.CONVERT_TEST_NAME_A), '5')
# global names for StrEnum._convert_ test
CONVERT_STR_TEST_2 = 'goodbye'
CONVERT_STR_TEST_1 = 'hello'
class TestStrEnumConvert(unittest.TestCase):
def test_convert(self):
test_type = enum.StrEnum._convert_(
'UnittestConvert',
('test.test_enum', '__main__')[__name__=='__main__'],
filter=lambda x: x.startswith('CONVERT_STR_'))
# Ensure that test_type has all of the desired names and values.
self.assertEqual(test_type.CONVERT_STR_TEST_1, 'hello')
self.assertEqual(test_type.CONVERT_STR_TEST_2, 'goodbye')
# Ensure that test_type only picked up names matching the filter.
self.assertEqual([name for name in dir(test_type)
if name[0:2] not in ('CO', '__')],
[], msg='Names other than CONVERT_STR_* found.')
def test_convert_repr_and_str(self):
module = ('test.test_enum', '__main__')[__name__=='__main__']
test_type = enum.StrEnum._convert_(
'UnittestConvert',
module,
filter=lambda x: x.startswith('CONVERT_STR_'))
self.assertEqual(repr(test_type.CONVERT_STR_TEST_1), '%s.CONVERT_STR_TEST_1' % module)
self.assertEqual(str(test_type.CONVERT_STR_TEST_2), 'goodbye')
self.assertEqual(format(test_type.CONVERT_STR_TEST_1), 'hello')
if __name__ == '__main__':
unittest.main()