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
9b9d97dd
Commit
9b9d97dd
authored
Sep 14, 2018
by
Eric V. Smith
Committed by
GitHub
Sep 14, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-34363: dataclasses.asdict() and .astuple() now handle fields which are namedtuples. (GH-9151)
parent
73820a60
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
118 additions
and
2 deletions
+118
-2
Lib/dataclasses.py
Lib/dataclasses.py
+38
-2
Lib/test/test_dataclasses.py
Lib/test/test_dataclasses.py
+79
-0
Misc/NEWS.d/next/Library/2018-09-10-21-09-34.bpo-34363.YuSb0T.rst
...S.d/next/Library/2018-09-10-21-09-34.bpo-34363.YuSb0T.rst
+1
-0
No files found.
Lib/dataclasses.py
View file @
9b9d97dd
...
@@ -1026,10 +1026,35 @@ def _asdict_inner(obj, dict_factory):
...
@@ -1026,10 +1026,35 @@ def _asdict_inner(obj, dict_factory):
value
=
_asdict_inner
(
getattr
(
obj
,
f
.
name
),
dict_factory
)
value
=
_asdict_inner
(
getattr
(
obj
,
f
.
name
),
dict_factory
)
result
.
append
((
f
.
name
,
value
))
result
.
append
((
f
.
name
,
value
))
return
dict_factory
(
result
)
return
dict_factory
(
result
)
elif
isinstance
(
obj
,
tuple
)
and
hasattr
(
obj
,
'_fields'
):
# obj is a namedtuple. Recurse into it, but the returned
# object is another namedtuple of the same type. This is
# similar to how other list- or tuple-derived classes are
# treated (see below), but we just need to create them
# differently because a namedtuple's __init__ needs to be
# called differently (see bpo-34363).
# I'm not using namedtuple's _asdict()
# method, because:
# - it does not recurse in to the namedtuple fields and
# convert them to dicts (using dict_factory).
# - I don't actually want to return a dict here. The the main
# use case here is json.dumps, and it handles converting
# namedtuples to lists. Admittedly we're losing some
# information here when we produce a json list instead of a
# dict. Note that if we returned dicts here instead of
# namedtuples, we could no longer call asdict() on a data
# structure where a namedtuple was used as a dict key.
return
type
(
obj
)(
*
[
_asdict_inner
(
v
,
dict_factory
)
for
v
in
obj
])
elif
isinstance
(
obj
,
(
list
,
tuple
)):
elif
isinstance
(
obj
,
(
list
,
tuple
)):
# Assume we can create an object of this type by passing in a
# generator (which is not true for namedtuples, handled
# above).
return
type
(
obj
)(
_asdict_inner
(
v
,
dict_factory
)
for
v
in
obj
)
return
type
(
obj
)(
_asdict_inner
(
v
,
dict_factory
)
for
v
in
obj
)
elif
isinstance
(
obj
,
dict
):
elif
isinstance
(
obj
,
dict
):
return
type
(
obj
)((
_asdict_inner
(
k
,
dict_factory
),
_asdict_inner
(
v
,
dict_factory
))
return
type
(
obj
)((
_asdict_inner
(
k
,
dict_factory
),
_asdict_inner
(
v
,
dict_factory
))
for
k
,
v
in
obj
.
items
())
for
k
,
v
in
obj
.
items
())
else
:
else
:
return
copy
.
deepcopy
(
obj
)
return
copy
.
deepcopy
(
obj
)
...
@@ -1066,7 +1091,18 @@ def _astuple_inner(obj, tuple_factory):
...
@@ -1066,7 +1091,18 @@ def _astuple_inner(obj, tuple_factory):
value
=
_astuple_inner
(
getattr
(
obj
,
f
.
name
),
tuple_factory
)
value
=
_astuple_inner
(
getattr
(
obj
,
f
.
name
),
tuple_factory
)
result
.
append
(
value
)
result
.
append
(
value
)
return
tuple_factory
(
result
)
return
tuple_factory
(
result
)
elif
isinstance
(
obj
,
tuple
)
and
hasattr
(
obj
,
'_fields'
):
# obj is a namedtuple. Recurse into it, but the returned
# object is another namedtuple of the same type. This is
# similar to how other list- or tuple-derived classes are
# treated (see below), but we just need to create them
# differently because a namedtuple's __init__ needs to be
# called differently (see bpo-34363).
return
type
(
obj
)(
*
[
_astuple_inner
(
v
,
tuple_factory
)
for
v
in
obj
])
elif
isinstance
(
obj
,
(
list
,
tuple
)):
elif
isinstance
(
obj
,
(
list
,
tuple
)):
# Assume we can create an object of this type by passing in a
# generator (which is not true for namedtuples, handled
# above).
return
type
(
obj
)(
_astuple_inner
(
v
,
tuple_factory
)
for
v
in
obj
)
return
type
(
obj
)(
_astuple_inner
(
v
,
tuple_factory
)
for
v
in
obj
)
elif
isinstance
(
obj
,
dict
):
elif
isinstance
(
obj
,
dict
):
return
type
(
obj
)((
_astuple_inner
(
k
,
tuple_factory
),
_astuple_inner
(
v
,
tuple_factory
))
return
type
(
obj
)((
_astuple_inner
(
k
,
tuple_factory
),
_astuple_inner
(
v
,
tuple_factory
))
...
...
Lib/test/test_dataclasses.py
View file @
9b9d97dd
...
@@ -1429,6 +1429,70 @@ class TestCase(unittest.TestCase):
...
@@ -1429,6 +1429,70 @@ class TestCase(unittest.TestCase):
self
.
assertEqual
(
d
,
OrderedDict
([(
'x'
,
42
),
(
'y'
,
2
)]))
self
.
assertEqual
(
d
,
OrderedDict
([(
'x'
,
42
),
(
'y'
,
2
)]))
self
.
assertIs
(
type
(
d
),
OrderedDict
)
self
.
assertIs
(
type
(
d
),
OrderedDict
)
def
test_helper_asdict_namedtuple
(
self
):
T
=
namedtuple
(
'T'
,
'a b c'
)
@
dataclass
class
C
:
x
:
str
y
:
T
c
=
C
(
'outer'
,
T
(
1
,
C
(
'inner'
,
T
(
11
,
12
,
13
)),
2
))
d
=
asdict
(
c
)
self
.
assertEqual
(
d
,
{
'x'
:
'outer'
,
'y'
:
T
(
1
,
{
'x'
:
'inner'
,
'y'
:
T
(
11
,
12
,
13
)},
2
),
}
)
# Now with a dict_factory. OrderedDict is convenient, but
# since it compares to dicts, we also need to have separate
# assertIs tests.
d
=
asdict
(
c
,
dict_factory
=
OrderedDict
)
self
.
assertEqual
(
d
,
{
'x'
:
'outer'
,
'y'
:
T
(
1
,
{
'x'
:
'inner'
,
'y'
:
T
(
11
,
12
,
13
)},
2
),
}
)
# Make sure that the returned dicts are actuall OrderedDicts.
self
.
assertIs
(
type
(
d
),
OrderedDict
)
self
.
assertIs
(
type
(
d
[
'y'
][
1
]),
OrderedDict
)
def
test_helper_asdict_namedtuple_key
(
self
):
# Ensure that a field that contains a dict which has a
# namedtuple as a key works with asdict().
@
dataclass
class
C
:
f
:
dict
T
=
namedtuple
(
'T'
,
'a'
)
c
=
C
({
T
(
'an a'
):
0
})
self
.
assertEqual
(
asdict
(
c
),
{
'f'
:
{
T
(
a
=
'an a'
):
0
}})
def
test_helper_asdict_namedtuple_derived
(
self
):
class
T
(
namedtuple
(
'Tbase'
,
'a'
)):
def
my_a
(
self
):
return
self
.
a
@
dataclass
class
C
:
f
:
T
t
=
T
(
6
)
c
=
C
(
t
)
d
=
asdict
(
c
)
self
.
assertEqual
(
d
,
{
'f'
:
T
(
a
=
6
)})
# Make sure that t has been copied, not used directly.
self
.
assertIsNot
(
d
[
'f'
],
t
)
self
.
assertEqual
(
d
[
'f'
].
my_a
(),
6
)
def
test_helper_astuple
(
self
):
def
test_helper_astuple
(
self
):
# Basic tests for astuple(), it should return a new tuple.
# Basic tests for astuple(), it should return a new tuple.
@
dataclass
@
dataclass
...
@@ -1541,6 +1605,21 @@ class TestCase(unittest.TestCase):
...
@@ -1541,6 +1605,21 @@ class TestCase(unittest.TestCase):
self
.
assertEqual
(
t
,
NT
(
42
,
2
))
self
.
assertEqual
(
t
,
NT
(
42
,
2
))
self
.
assertIs
(
type
(
t
),
NT
)
self
.
assertIs
(
type
(
t
),
NT
)
def
test_helper_astuple_namedtuple
(
self
):
T
=
namedtuple
(
'T'
,
'a b c'
)
@
dataclass
class
C
:
x
:
str
y
:
T
c
=
C
(
'outer'
,
T
(
1
,
C
(
'inner'
,
T
(
11
,
12
,
13
)),
2
))
t
=
astuple
(
c
)
self
.
assertEqual
(
t
,
(
'outer'
,
T
(
1
,
(
'inner'
,
(
11
,
12
,
13
)),
2
)))
# Now, using a tuple_factory. list is convenient here.
t
=
astuple
(
c
,
tuple_factory
=
list
)
self
.
assertEqual
(
t
,
[
'outer'
,
T
(
1
,
[
'inner'
,
T
(
11
,
12
,
13
)],
2
)])
def
test_dynamic_class_creation
(
self
):
def
test_dynamic_class_creation
(
self
):
cls_dict
=
{
'__annotations__'
:
{
'x'
:
int
,
'y'
:
int
},
cls_dict
=
{
'__annotations__'
:
{
'x'
:
int
,
'y'
:
int
},
}
}
...
...
Misc/NEWS.d/next/Library/2018-09-10-21-09-34.bpo-34363.YuSb0T.rst
0 → 100644
View file @
9b9d97dd
dataclasses.asdict() and .astuple() now handle namedtuples correctly.
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