mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	bpo-42851: [Enum] remove brittle __init_subclass__ support (GH-24154)
Solution to support calls to `__init_subclass__` with members defined is too brittle and breaks with certain mixins.
This commit is contained in:
		
							parent
							
								
									8643345bdb
								
							
						
					
					
						commit
						a581a868d9
					
				
					 3 changed files with 2 additions and 75 deletions
				
			
		
							
								
								
									
										31
									
								
								Lib/enum.py
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								Lib/enum.py
									
										
									
									
									
								
							|  | @ -9,14 +9,6 @@ | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class _NoInitSubclass: |  | ||||||
|     """ |  | ||||||
|     temporary base class to suppress calling __init_subclass__ |  | ||||||
|     """ |  | ||||||
|     @classmethod |  | ||||||
|     def __init_subclass__(cls, **kwds): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
| def _is_descriptor(obj): | def _is_descriptor(obj): | ||||||
|     """ |     """ | ||||||
|     Returns True if obj is a descriptor, False otherwise. |     Returns True if obj is a descriptor, False otherwise. | ||||||
|  | @ -227,22 +219,7 @@ def __new__(metacls, cls, bases, classdict, **kwds): | ||||||
|         if '__doc__' not in classdict: |         if '__doc__' not in classdict: | ||||||
|             classdict['__doc__'] = 'An enumeration.' |             classdict['__doc__'] = 'An enumeration.' | ||||||
| 
 | 
 | ||||||
|         # postpone calling __init_subclass__ |         enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) | ||||||
|         if '__init_subclass__' in classdict and classdict['__init_subclass__'] is None: |  | ||||||
|             raise TypeError('%s.__init_subclass__ cannot be None') |  | ||||||
|         # remove current __init_subclass__ so previous one can be found with getattr |  | ||||||
|         new_init_subclass = classdict.pop('__init_subclass__', None) |  | ||||||
|         # create our new Enum type |  | ||||||
|         if bases: |  | ||||||
|             bases = (_NoInitSubclass, ) + bases |  | ||||||
|             enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) |  | ||||||
|             enum_class.__bases__ = enum_class.__bases__[1:] #or (object, ) |  | ||||||
|         else: |  | ||||||
|             enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) |  | ||||||
|         old_init_subclass = getattr(enum_class, '__init_subclass__', None) |  | ||||||
|         # and restore the new one (if there was one) |  | ||||||
|         if new_init_subclass is not None: |  | ||||||
|             enum_class.__init_subclass__ = classmethod(new_init_subclass) |  | ||||||
|         enum_class._member_names_ = []               # names in definition order |         enum_class._member_names_ = []               # names in definition order | ||||||
|         enum_class._member_map_ = {}                 # name->value map |         enum_class._member_map_ = {}                 # name->value map | ||||||
|         enum_class._member_type_ = member_type |         enum_class._member_type_ = member_type | ||||||
|  | @ -354,9 +331,6 @@ def __new__(metacls, cls, bases, classdict, **kwds): | ||||||
|             if _order_ != enum_class._member_names_: |             if _order_ != enum_class._member_names_: | ||||||
|                 raise TypeError('member order does not match _order_') |                 raise TypeError('member order does not match _order_') | ||||||
| 
 | 
 | ||||||
|         # finally, call parents' __init_subclass__ |  | ||||||
|         if Enum is not None and old_init_subclass is not None: |  | ||||||
|             old_init_subclass(**kwds) |  | ||||||
|         return enum_class |         return enum_class | ||||||
| 
 | 
 | ||||||
|     def __bool__(self): |     def __bool__(self): | ||||||
|  | @ -734,9 +708,6 @@ def _generate_next_value_(name, start, count, last_values): | ||||||
|         else: |         else: | ||||||
|             return start |             return start | ||||||
| 
 | 
 | ||||||
|     def __init_subclass__(cls, **kwds): |  | ||||||
|         super().__init_subclass__(**kwds) |  | ||||||
| 
 |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _missing_(cls, value): |     def _missing_(cls, value): | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  | @ -2119,52 +2119,7 @@ class ThirdFailedStrEnum(StrEnum): | ||||||
|                 one = '1' |                 one = '1' | ||||||
|                 two = b'2', 'ascii', 9 |                 two = b'2', 'ascii', 9 | ||||||
|                  |                  | ||||||
|     def test_init_subclass_calling(self): |  | ||||||
|         class MyEnum(Enum): |  | ||||||
|             def __init_subclass__(cls, **kwds): |  | ||||||
|                 super(MyEnum, cls).__init_subclass__(**kwds) |  | ||||||
|                 self.assertFalse(cls.__dict__.get('_test', False)) |  | ||||||
|                 cls._test1 = 'MyEnum' |  | ||||||
|         # |  | ||||||
|         class TheirEnum(MyEnum): |  | ||||||
|             def __init_subclass__(cls, **kwds): |  | ||||||
|                 super().__init_subclass__(**kwds) |  | ||||||
|                 cls._test2 = 'TheirEnum' |  | ||||||
|         class WhoseEnum(TheirEnum): |  | ||||||
|             def __init_subclass__(cls, **kwds): |  | ||||||
|                 pass |  | ||||||
|         class NoEnum(WhoseEnum): |  | ||||||
|             ONE = 1 |  | ||||||
|         self.assertEqual(TheirEnum.__dict__['_test1'], 'MyEnum') |  | ||||||
|         self.assertEqual(WhoseEnum.__dict__['_test1'], 'MyEnum') |  | ||||||
|         self.assertEqual(WhoseEnum.__dict__['_test2'], 'TheirEnum') |  | ||||||
|         self.assertFalse(NoEnum.__dict__.get('_test1', False)) |  | ||||||
|         self.assertFalse(NoEnum.__dict__.get('_test2', False)) |  | ||||||
|         # |  | ||||||
|         class OurEnum(MyEnum): |  | ||||||
|             def __init_subclass__(cls, **kwds): |  | ||||||
|                 cls._test2 = 'OurEnum' |  | ||||||
|         class WhereEnum(OurEnum): |  | ||||||
|             def __init_subclass__(cls, **kwds): |  | ||||||
|                 pass |  | ||||||
|         class NeverEnum(WhereEnum): |  | ||||||
|             ONE = 'one' |  | ||||||
|         self.assertEqual(OurEnum.__dict__['_test1'], 'MyEnum') |  | ||||||
|         self.assertFalse(WhereEnum.__dict__.get('_test1', False)) |  | ||||||
|         self.assertEqual(WhereEnum.__dict__['_test2'], 'OurEnum') |  | ||||||
|         self.assertFalse(NeverEnum.__dict__.get('_test1', False)) |  | ||||||
|         self.assertFalse(NeverEnum.__dict__.get('_test2', False)) |  | ||||||
| 
 | 
 | ||||||
|     def test_init_subclass_parameter(self): |  | ||||||
|         class multiEnum(Enum): |  | ||||||
|             def __init_subclass__(cls, multi): |  | ||||||
|                 for member in cls: |  | ||||||
|                     member._as_parameter_ = multi * member.value |  | ||||||
|         class E(multiEnum, multi=3): |  | ||||||
|             A = 1 |  | ||||||
|             B = 2 |  | ||||||
|         self.assertEqual(E.A._as_parameter_, 3) |  | ||||||
|         self.assertEqual(E.B._as_parameter_, 6) |  | ||||||
| 
 | 
 | ||||||
|     @unittest.skipUnless( |     @unittest.skipUnless( | ||||||
|             sys.version_info[:2] == (3, 9), |             sys.version_info[:2] == (3, 9), | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | remove __init_subclass__ support for Enum members | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ethan Furman
						Ethan Furman