mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +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 | ||||
|    same* as the enum member itself, although it is equivalant and will compare | ||||
|    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 | ||||
|  |  | |||
							
								
								
									
										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.__module__ = '<unknown>' | ||||
| 
 | ||||
| 
 | ||||
| class _EnumDict(dict): | ||||
|     """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 | ||||
|         # things break (such as pickle) | ||||
|         for name in ('__repr__', '__str__', '__getnewargs__'): | ||||
|         for name in ('__repr__', '__str__', '__format__', '__getnewargs__'): | ||||
|             class_method = getattr(enum_class, name) | ||||
|             obj_method = getattr(member_type, name, None) | ||||
|             enum_method = getattr(first_enum, name, None) | ||||
|  | @ -441,6 +440,21 @@ def __eq__(self, other): | |||
|             return self is other | ||||
|         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): | ||||
|         return (self._value_, ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -67,6 +67,33 @@ class Season(Enum): | |||
|             WINTER = 4 | ||||
|         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): | ||||
|         Season = self.Season | ||||
|         self.assertEqual( | ||||
|  | @ -207,6 +234,77 @@ class Huh(Enum): | |||
|         self.assertIs(type(Huh.name), Huh) | ||||
|         self.assertEqual(Huh.name.name, 'name') | ||||
|         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): | ||||
|         Season = self.Season | ||||
|         dates = {} | ||||
|  | @ -232,7 +330,7 @@ class phy(IntEnum): | |||
| 
 | ||||
|     def test_floatenum_from_scratch(self): | ||||
|         class phy(float, Enum): | ||||
|             pi = 3.141596 | ||||
|             pi = 3.1415926 | ||||
|             tau = 2 * pi | ||||
|         self.assertTrue(phy.pi < phy.tau) | ||||
| 
 | ||||
|  | @ -240,7 +338,7 @@ def test_floatenum_inherited(self): | |||
|         class FloatEnum(float, Enum): | ||||
|             pass | ||||
|         class phy(FloatEnum): | ||||
|             pi = 3.141596 | ||||
|             pi = 3.1415926 | ||||
|             tau = 2 * pi | ||||
|         self.assertTrue(phy.pi < phy.tau) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ethan Furman
						Ethan Furman