Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
03220fdb
Commit
03220fdb
authored
Dec 29, 2017
by
Eric V. Smith
Committed by
GitHub
Dec 29, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-32427: Expose dataclasses.MISSING object. (#5045)
parent
e3256087
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
70 additions
and
23 deletions
+70
-23
Lib/dataclasses.py
Lib/dataclasses.py
+21
-20
Lib/test/test_dataclasses.py
Lib/test/test_dataclasses.py
+49
-3
No files found.
Lib/dataclasses.py
View file @
03220fdb
...
@@ -8,6 +8,7 @@ __all__ = ['dataclass',
...
@@ -8,6 +8,7 @@ __all__ = ['dataclass',
'field'
,
'field'
,
'FrozenInstanceError'
,
'FrozenInstanceError'
,
'InitVar'
,
'InitVar'
,
'MISSING'
,
# Helper functions.
# Helper functions.
'fields'
,
'fields'
,
...
@@ -29,11 +30,11 @@ class _HAS_DEFAULT_FACTORY_CLASS:
...
@@ -29,11 +30,11 @@ class _HAS_DEFAULT_FACTORY_CLASS:
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 @@ class Field:
...
@@ -114,7 +115,7 @@ class Field:
# 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,
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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):
...
@@ -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
...
...
Lib/test/test_dataclasses.py
View file @
03220fdb
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 @@ class TestCase(unittest.TestCase):
...
@@ -917,12 +917,12 @@ class TestCase(unittest.TestCase):
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 TestCase(unittest.TestCase):
...
@@ -948,6 +948,52 @@ class TestCase(unittest.TestCase):
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
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment