mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	bpo-32427: Expose dataclasses.MISSING object. (#5045)
This commit is contained in:
		
							parent
							
								
									e325608740
								
							
						
					
					
						commit
						03220fdb26
					
				
					 2 changed files with 70 additions and 23 deletions
				
			
		|  | @ -8,6 +8,7 @@ | ||||||
|            'field', |            'field', | ||||||
|            'FrozenInstanceError', |            'FrozenInstanceError', | ||||||
|            'InitVar', |            'InitVar', | ||||||
|  |            'MISSING', | ||||||
| 
 | 
 | ||||||
|            # Helper functions. |            # Helper functions. | ||||||
|            'fields', |            'fields', | ||||||
|  | @ -29,11 +30,11 @@ def __repr__(self): | ||||||
|         return '<factory>' |         return '<factory>' | ||||||
| _HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() | _HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() | ||||||
| 
 | 
 | ||||||
| # A sentinel object to detect if a parameter is supplied or not. | # A sentinel object to detect if a parameter is supplied or not.  Use | ||||||
| class _MISSING_FACTORY: | #  a class to give it a better repr. | ||||||
|     def __repr__(self): | class _MISSING_TYPE: | ||||||
|         return '<missing>' |     pass | ||||||
| _MISSING = _MISSING_FACTORY() | MISSING = _MISSING_TYPE() | ||||||
| 
 | 
 | ||||||
| # Since most per-field metadata will be unused, create an empty | # Since most per-field metadata will be unused, create an empty | ||||||
| #  read-only proxy that can be shared among all fields. | #  read-only proxy that can be shared among all fields. | ||||||
|  | @ -114,7 +115,7 @@ def __repr__(self): | ||||||
| # This function is used instead of exposing Field creation directly, | # This function is used instead of exposing Field creation directly, | ||||||
| #  so that a type checker can be told (via overloads) that this is a | #  so that a type checker can be told (via overloads) that this is a | ||||||
| #  function whose type depends on its parameters. | #  function whose type depends on its parameters. | ||||||
| def field(*, default=_MISSING, default_factory=_MISSING, init=True, repr=True, | def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, | ||||||
|           hash=None, compare=True, metadata=None): |           hash=None, compare=True, metadata=None): | ||||||
|     """Return an object to identify dataclass fields. |     """Return an object to identify dataclass fields. | ||||||
| 
 | 
 | ||||||
|  | @ -130,7 +131,7 @@ def field(*, default=_MISSING, default_factory=_MISSING, init=True, repr=True, | ||||||
|     It is an error to specify both default and default_factory. |     It is an error to specify both default and default_factory. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     if default is not _MISSING and default_factory is not _MISSING: |     if default is not MISSING and default_factory is not MISSING: | ||||||
|         raise ValueError('cannot specify both default and default_factory') |         raise ValueError('cannot specify both default and default_factory') | ||||||
|     return Field(default, default_factory, init, repr, hash, compare, |     return Field(default, default_factory, init, repr, hash, compare, | ||||||
|                  metadata) |                  metadata) | ||||||
|  | @ -149,12 +150,12 @@ def _tuple_str(obj_name, fields): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _create_fn(name, args, body, globals=None, locals=None, | def _create_fn(name, args, body, globals=None, locals=None, | ||||||
|                return_type=_MISSING): |                return_type=MISSING): | ||||||
|     # Note that we mutate locals when exec() is called. Caller beware! |     # Note that we mutate locals when exec() is called. Caller beware! | ||||||
|     if locals is None: |     if locals is None: | ||||||
|         locals = {} |         locals = {} | ||||||
|     return_annotation = '' |     return_annotation = '' | ||||||
|     if return_type is not _MISSING: |     if return_type is not MISSING: | ||||||
|         locals['_return_type'] = return_type |         locals['_return_type'] = return_type | ||||||
|         return_annotation = '->_return_type' |         return_annotation = '->_return_type' | ||||||
|     args = ','.join(args) |     args = ','.join(args) | ||||||
|  | @ -182,7 +183,7 @@ def _field_init(f, frozen, globals, self_name): | ||||||
|     #  initialize this field. |     #  initialize this field. | ||||||
| 
 | 
 | ||||||
|     default_name = f'_dflt_{f.name}' |     default_name = f'_dflt_{f.name}' | ||||||
|     if f.default_factory is not _MISSING: |     if f.default_factory is not MISSING: | ||||||
|         if f.init: |         if f.init: | ||||||
|             # This field has a default factory.  If a parameter is |             # This field has a default factory.  If a parameter is | ||||||
|             #  given, use it.  If not, call the factory. |             #  given, use it.  If not, call the factory. | ||||||
|  | @ -210,10 +211,10 @@ def _field_init(f, frozen, globals, self_name): | ||||||
|     else: |     else: | ||||||
|         # No default factory. |         # No default factory. | ||||||
|         if f.init: |         if f.init: | ||||||
|             if f.default is _MISSING: |             if f.default is MISSING: | ||||||
|                 # There's no default, just do an assignment. |                 # There's no default, just do an assignment. | ||||||
|                 value = f.name |                 value = f.name | ||||||
|             elif f.default is not _MISSING: |             elif f.default is not MISSING: | ||||||
|                 globals[default_name] = f.default |                 globals[default_name] = f.default | ||||||
|                 value = f.name |                 value = f.name | ||||||
|         else: |         else: | ||||||
|  | @ -236,14 +237,14 @@ def _init_param(f): | ||||||
|     #  For example, the equivalent of 'x:int=3' (except instead of 'int', |     #  For example, the equivalent of 'x:int=3' (except instead of 'int', | ||||||
|     #  reference a variable set to int, and instead of '3', reference a |     #  reference a variable set to int, and instead of '3', reference a | ||||||
|     #  variable set to 3). |     #  variable set to 3). | ||||||
|     if f.default is _MISSING and f.default_factory is _MISSING: |     if f.default is MISSING and f.default_factory is MISSING: | ||||||
|         # There's no default, and no default_factory, just |         # There's no default, and no default_factory, just | ||||||
|         #  output the variable name and type. |         #  output the variable name and type. | ||||||
|         default = '' |         default = '' | ||||||
|     elif f.default is not _MISSING: |     elif f.default is not MISSING: | ||||||
|         # There's a default, this will be the name that's used to look it up. |         # There's a default, this will be the name that's used to look it up. | ||||||
|         default = f'=_dflt_{f.name}' |         default = f'=_dflt_{f.name}' | ||||||
|     elif f.default_factory is not _MISSING: |     elif f.default_factory is not MISSING: | ||||||
|         # There's a factory function. Set a marker. |         # There's a factory function. Set a marker. | ||||||
|         default = '=_HAS_DEFAULT_FACTORY' |         default = '=_HAS_DEFAULT_FACTORY' | ||||||
|     return f'{f.name}:_type_{f.name}{default}' |     return f'{f.name}:_type_{f.name}{default}' | ||||||
|  | @ -261,13 +262,13 @@ def _init_fn(fields, frozen, has_post_init, self_name): | ||||||
|     for f in fields: |     for f in fields: | ||||||
|         # Only consider fields in the __init__ call. |         # Only consider fields in the __init__ call. | ||||||
|         if f.init: |         if f.init: | ||||||
|             if not (f.default is _MISSING and f.default_factory is _MISSING): |             if not (f.default is MISSING and f.default_factory is MISSING): | ||||||
|                 seen_default = True |                 seen_default = True | ||||||
|             elif seen_default: |             elif seen_default: | ||||||
|                 raise TypeError(f'non-default argument {f.name!r} ' |                 raise TypeError(f'non-default argument {f.name!r} ' | ||||||
|                                 'follows default argument') |                                 'follows default argument') | ||||||
| 
 | 
 | ||||||
|     globals = {'_MISSING': _MISSING, |     globals = {'MISSING': MISSING, | ||||||
|                '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY} |                '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY} | ||||||
| 
 | 
 | ||||||
|     body_lines = [] |     body_lines = [] | ||||||
|  | @ -368,7 +369,7 @@ def _get_field(cls, a_name, a_type): | ||||||
| 
 | 
 | ||||||
|     # If the default value isn't derived from field, then it's |     # If the default value isn't derived from field, then it's | ||||||
|     #  only a normal default value.  Convert it to a Field(). |     #  only a normal default value.  Convert it to a Field(). | ||||||
|     default = getattr(cls, a_name, _MISSING) |     default = getattr(cls, a_name, MISSING) | ||||||
|     if isinstance(default, Field): |     if isinstance(default, Field): | ||||||
|         f = default |         f = default | ||||||
|     else: |     else: | ||||||
|  | @ -404,7 +405,7 @@ def _get_field(cls, a_name, a_type): | ||||||
| 
 | 
 | ||||||
|     # Special restrictions for ClassVar and InitVar. |     # Special restrictions for ClassVar and InitVar. | ||||||
|     if f._field_type in (_FIELD_CLASSVAR, _FIELD_INITVAR): |     if f._field_type in (_FIELD_CLASSVAR, _FIELD_INITVAR): | ||||||
|         if f.default_factory is not _MISSING: |         if f.default_factory is not MISSING: | ||||||
|             raise TypeError(f'field {f.name} cannot have a ' |             raise TypeError(f'field {f.name} cannot have a ' | ||||||
|                             'default factory') |                             'default factory') | ||||||
|         # Should I check for other field settings? default_factory |         # Should I check for other field settings? default_factory | ||||||
|  | @ -474,7 +475,7 @@ def _process_class(cls, repr, eq, order, hash, init, frozen): | ||||||
|         #  with the real default.  This is so that normal class |         #  with the real default.  This is so that normal class | ||||||
|         #  introspection sees a real default value, not a Field. |         #  introspection sees a real default value, not a Field. | ||||||
|         if isinstance(getattr(cls, f.name, None), Field): |         if isinstance(getattr(cls, f.name, None), Field): | ||||||
|             if f.default is _MISSING: |             if f.default is MISSING: | ||||||
|                 # If there's no default, delete the class attribute. |                 # If there's no default, delete the class attribute. | ||||||
|                 #  This happens if we specify field(repr=False), for |                 #  This happens if we specify field(repr=False), for | ||||||
|                 #  example (that is, we specified a field object, but |                 #  example (that is, we specified a field object, but | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from dataclasses import ( | from dataclasses import ( | ||||||
|     dataclass, field, FrozenInstanceError, fields, asdict, astuple, |     dataclass, field, FrozenInstanceError, fields, asdict, astuple, | ||||||
|     make_dataclass, replace, InitVar, Field |     make_dataclass, replace, InitVar, Field, MISSING | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| import pickle | import pickle | ||||||
|  | @ -917,12 +917,12 @@ def validate_class(cls): | ||||||
|             param = next(params) |             param = next(params) | ||||||
|             self.assertEqual(param.name, 'k') |             self.assertEqual(param.name, 'k') | ||||||
|             self.assertIs   (param.annotation, F) |             self.assertIs   (param.annotation, F) | ||||||
|             # Don't test for the default, since it's set to _MISSING |             # Don't test for the default, since it's set to MISSING | ||||||
|             self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) |             self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) | ||||||
|             param = next(params) |             param = next(params) | ||||||
|             self.assertEqual(param.name, 'l') |             self.assertEqual(param.name, 'l') | ||||||
|             self.assertIs   (param.annotation, float) |             self.assertIs   (param.annotation, float) | ||||||
|             # Don't test for the default, since it's set to _MISSING |             # Don't test for the default, since it's set to MISSING | ||||||
|             self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) |             self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) | ||||||
|             self.assertRaises(StopIteration, next, params) |             self.assertRaises(StopIteration, next, params) | ||||||
| 
 | 
 | ||||||
|  | @ -948,6 +948,52 @@ class C: | ||||||
| 
 | 
 | ||||||
|         validate_class(C) |         validate_class(C) | ||||||
| 
 | 
 | ||||||
|  |     def test_missing_default(self): | ||||||
|  |         # Test that MISSING works the same as a default not being | ||||||
|  |         #  specified. | ||||||
|  |         @dataclass | ||||||
|  |         class C: | ||||||
|  |             x: int=field(default=MISSING) | ||||||
|  |         with self.assertRaisesRegex(TypeError, | ||||||
|  |                                     r'__init__\(\) missing 1 required ' | ||||||
|  |                                     'positional argument'): | ||||||
|  |             C() | ||||||
|  |         self.assertNotIn('x', C.__dict__) | ||||||
|  | 
 | ||||||
|  |         @dataclass | ||||||
|  |         class D: | ||||||
|  |             x: int | ||||||
|  |         with self.assertRaisesRegex(TypeError, | ||||||
|  |                                     r'__init__\(\) missing 1 required ' | ||||||
|  |                                     'positional argument'): | ||||||
|  |             D() | ||||||
|  |         self.assertNotIn('x', D.__dict__) | ||||||
|  | 
 | ||||||
|  |     def test_missing_default_factory(self): | ||||||
|  |         # Test that MISSING works the same as a default factory not | ||||||
|  |         #  being specified (which is really the same as a default not | ||||||
|  |         #  being specified, too). | ||||||
|  |         @dataclass | ||||||
|  |         class C: | ||||||
|  |             x: int=field(default_factory=MISSING) | ||||||
|  |         with self.assertRaisesRegex(TypeError, | ||||||
|  |                                     r'__init__\(\) missing 1 required ' | ||||||
|  |                                     'positional argument'): | ||||||
|  |             C() | ||||||
|  |         self.assertNotIn('x', C.__dict__) | ||||||
|  | 
 | ||||||
|  |         @dataclass | ||||||
|  |         class D: | ||||||
|  |             x: int=field(default=MISSING, default_factory=MISSING) | ||||||
|  |         with self.assertRaisesRegex(TypeError, | ||||||
|  |                                     r'__init__\(\) missing 1 required ' | ||||||
|  |                                     'positional argument'): | ||||||
|  |             D() | ||||||
|  |         self.assertNotIn('x', D.__dict__) | ||||||
|  | 
 | ||||||
|  |     def test_missing_repr(self): | ||||||
|  |         self.assertIn('MISSING_TYPE object', repr(MISSING)) | ||||||
|  | 
 | ||||||
|     def test_dont_include_other_annotations(self): |     def test_dont_include_other_annotations(self): | ||||||
|         @dataclass |         @dataclass | ||||||
|         class C: |         class C: | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric V. Smith
						Eric V. Smith