mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	Close #18738: Route __format__ calls to mixed-in type for mixed Enums (such as IntEnum).
This commit is contained in:
		
							parent
							
								
									34567ec94b
								
							
						
					
					
						commit
						ec15a826ce
					
				
					 3 changed files with 122 additions and 4 deletions
				
			
		|  | @ -463,6 +463,12 @@ Some rules: | ||||||
| 3. When another data type is mixed in, the :attr:`value` attribute is *not the | 3. When another data type is mixed in, the :attr:`value` attribute is *not the | ||||||
|    same* as the enum member itself, although it is equivalant and will compare |    same* as the enum member itself, although it is equivalant and will compare | ||||||
|    equal. |    equal. | ||||||
|  | 4. %-style formatting:  `%s` and `%r` call :class:`Enum`'s :meth:`__str__` and | ||||||
|  |    :meth:`__repr__` respectively; other codes (such as `%i` or `%h` for | ||||||
|  |    IntEnum) treat the enum member as its mixed-in type. | ||||||
|  | 5. :class:`str`.:meth:`__format__` (or :func:`format`) will use the mixed-in | ||||||
|  |    type's :meth:`__format__`.  If the :class:`Enum`'s :func:`str` or | ||||||
|  |    :func:`repr` is desired use the `!s` or `!r` :class:`str` format codes. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Interesting examples | Interesting examples | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								Lib/enum.py
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								Lib/enum.py
									
										
									
									
									
								
							|  | @ -50,7 +50,6 @@ def _break_on_call_reduce(self): | ||||||
|     cls.__reduce__ = _break_on_call_reduce |     cls.__reduce__ = _break_on_call_reduce | ||||||
|     cls.__module__ = '<unknown>' |     cls.__module__ = '<unknown>' | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class _EnumDict(dict): | class _EnumDict(dict): | ||||||
|     """Keeps track of definition order of the enum items. |     """Keeps track of definition order of the enum items. | ||||||
| 
 | 
 | ||||||
|  | @ -182,7 +181,7 @@ def __new__(metacls, cls, bases, classdict): | ||||||
| 
 | 
 | ||||||
|         # double check that repr and friends are not the mixin's or various |         # double check that repr and friends are not the mixin's or various | ||||||
|         # things break (such as pickle) |         # things break (such as pickle) | ||||||
|         for name in ('__repr__', '__str__', '__getnewargs__'): |         for name in ('__repr__', '__str__', '__format__', '__getnewargs__'): | ||||||
|             class_method = getattr(enum_class, name) |             class_method = getattr(enum_class, name) | ||||||
|             obj_method = getattr(member_type, name, None) |             obj_method = getattr(member_type, name, None) | ||||||
|             enum_method = getattr(first_enum, name, None) |             enum_method = getattr(first_enum, name, None) | ||||||
|  | @ -441,6 +440,21 @@ def __eq__(self, other): | ||||||
|             return self is other |             return self is other | ||||||
|         return NotImplemented |         return NotImplemented | ||||||
| 
 | 
 | ||||||
|  |     def __format__(self, format_spec): | ||||||
|  |         # mixed-in Enums should use the mixed-in type's __format__, otherwise | ||||||
|  |         # we can get strange results with the Enum name showing up instead of | ||||||
|  |         # the value | ||||||
|  | 
 | ||||||
|  |         # pure Enum branch | ||||||
|  |         if self._member_type_ is object: | ||||||
|  |             cls = str | ||||||
|  |             val = str(self) | ||||||
|  |         # mix-in branch | ||||||
|  |         else: | ||||||
|  |             cls = self._member_type_ | ||||||
|  |             val = self.value | ||||||
|  |         return cls.__format__(val, format_spec) | ||||||
|  | 
 | ||||||
|     def __getnewargs__(self): |     def __getnewargs__(self): | ||||||
|         return (self._value_, ) |         return (self._value_, ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -67,6 +67,33 @@ class Season(Enum): | ||||||
|             WINTER = 4 |             WINTER = 4 | ||||||
|         self.Season = Season |         self.Season = Season | ||||||
| 
 | 
 | ||||||
|  |         class Konstants(float, Enum): | ||||||
|  |             E = 2.7182818 | ||||||
|  |             PI = 3.1415926 | ||||||
|  |             TAU = 2 * PI | ||||||
|  |         self.Konstants = Konstants | ||||||
|  | 
 | ||||||
|  |         class Grades(IntEnum): | ||||||
|  |             A = 5 | ||||||
|  |             B = 4 | ||||||
|  |             C = 3 | ||||||
|  |             D = 2 | ||||||
|  |             F = 0 | ||||||
|  |         self.Grades = Grades | ||||||
|  | 
 | ||||||
|  |         class Directional(str, Enum): | ||||||
|  |             EAST = 'east' | ||||||
|  |             WEST = 'west' | ||||||
|  |             NORTH = 'north' | ||||||
|  |             SOUTH = 'south' | ||||||
|  |         self.Directional = Directional | ||||||
|  | 
 | ||||||
|  |         from datetime import date | ||||||
|  |         class Holiday(date, Enum): | ||||||
|  |             NEW_YEAR = 2013, 1, 1 | ||||||
|  |             IDES_OF_MARCH = 2013, 3, 15 | ||||||
|  |         self.Holiday = Holiday | ||||||
|  | 
 | ||||||
|     def test_dir_on_class(self): |     def test_dir_on_class(self): | ||||||
|         Season = self.Season |         Season = self.Season | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|  | @ -207,6 +234,77 @@ class Huh(Enum): | ||||||
|         self.assertIs(type(Huh.name), Huh) |         self.assertIs(type(Huh.name), Huh) | ||||||
|         self.assertEqual(Huh.name.name, 'name') |         self.assertEqual(Huh.name.name, 'name') | ||||||
|         self.assertEqual(Huh.name.value, 1) |         self.assertEqual(Huh.name.value, 1) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum(self): | ||||||
|  |         Season = self.Season | ||||||
|  |         self.assertEqual('{}'.format(Season.SPRING), | ||||||
|  |                          '{}'.format(str(Season.SPRING))) | ||||||
|  |         self.assertEqual( '{:}'.format(Season.SPRING), | ||||||
|  |                           '{:}'.format(str(Season.SPRING))) | ||||||
|  |         self.assertEqual('{:20}'.format(Season.SPRING), | ||||||
|  |                          '{:20}'.format(str(Season.SPRING))) | ||||||
|  |         self.assertEqual('{:^20}'.format(Season.SPRING), | ||||||
|  |                          '{:^20}'.format(str(Season.SPRING))) | ||||||
|  |         self.assertEqual('{:>20}'.format(Season.SPRING), | ||||||
|  |                          '{:>20}'.format(str(Season.SPRING))) | ||||||
|  |         self.assertEqual('{:<20}'.format(Season.SPRING), | ||||||
|  |                          '{:<20}'.format(str(Season.SPRING))) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum_custom(self): | ||||||
|  |         class TestFloat(float, Enum): | ||||||
|  |             one = 1.0 | ||||||
|  |             two = 2.0 | ||||||
|  |             def __format__(self, spec): | ||||||
|  |                 return 'TestFloat success!' | ||||||
|  |         self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!') | ||||||
|  | 
 | ||||||
|  |     def assertFormatIsValue(self, spec, member): | ||||||
|  |         self.assertEqual(spec.format(member), spec.format(member.value)) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum_date(self): | ||||||
|  |         Holiday = self.Holiday | ||||||
|  |         self.assertFormatIsValue('{}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:20}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:^20}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:>20}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:<20}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:%Y %m}', Holiday.IDES_OF_MARCH) | ||||||
|  |         self.assertFormatIsValue('{:%Y %m %M:00}', Holiday.IDES_OF_MARCH) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum_float(self): | ||||||
|  |         Konstants = self.Konstants | ||||||
|  |         self.assertFormatIsValue('{}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:20}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:^20}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:>20}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:<20}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:n}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:5.2}', Konstants.TAU) | ||||||
|  |         self.assertFormatIsValue('{:f}', Konstants.TAU) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum_int(self): | ||||||
|  |         Grades = self.Grades | ||||||
|  |         self.assertFormatIsValue('{}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:20}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:^20}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:>20}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:<20}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:+}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:08X}', Grades.C) | ||||||
|  |         self.assertFormatIsValue('{:b}', Grades.C) | ||||||
|  | 
 | ||||||
|  |     def test_format_enum_str(self): | ||||||
|  |         Directional = self.Directional | ||||||
|  |         self.assertFormatIsValue('{}', Directional.WEST) | ||||||
|  |         self.assertFormatIsValue('{:}', Directional.WEST) | ||||||
|  |         self.assertFormatIsValue('{:20}', Directional.WEST) | ||||||
|  |         self.assertFormatIsValue('{:^20}', Directional.WEST) | ||||||
|  |         self.assertFormatIsValue('{:>20}', Directional.WEST) | ||||||
|  |         self.assertFormatIsValue('{:<20}', Directional.WEST) | ||||||
|  | 
 | ||||||
|     def test_hash(self): |     def test_hash(self): | ||||||
|         Season = self.Season |         Season = self.Season | ||||||
|         dates = {} |         dates = {} | ||||||
|  | @ -232,7 +330,7 @@ class phy(IntEnum): | ||||||
| 
 | 
 | ||||||
|     def test_floatenum_from_scratch(self): |     def test_floatenum_from_scratch(self): | ||||||
|         class phy(float, Enum): |         class phy(float, Enum): | ||||||
|             pi = 3.141596 |             pi = 3.1415926 | ||||||
|             tau = 2 * pi |             tau = 2 * pi | ||||||
|         self.assertTrue(phy.pi < phy.tau) |         self.assertTrue(phy.pi < phy.tau) | ||||||
| 
 | 
 | ||||||
|  | @ -240,7 +338,7 @@ def test_floatenum_inherited(self): | ||||||
|         class FloatEnum(float, Enum): |         class FloatEnum(float, Enum): | ||||||
|             pass |             pass | ||||||
|         class phy(FloatEnum): |         class phy(FloatEnum): | ||||||
|             pi = 3.141596 |             pi = 3.1415926 | ||||||
|             tau = 2 * pi |             tau = 2 * pi | ||||||
|         self.assertTrue(phy.pi < phy.tau) |         self.assertTrue(phy.pi < phy.tau) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ethan Furman
						Ethan Furman