ericvsmith / dataclasses

Apache License 2.0
584 stars 53 forks source link

dataclasses.astuple breaks NamedTuple attribute of dataclass instance #151

Open isvoboda opened 4 years ago

isvoboda commented 4 years ago

It seems the fix of https://bugs.python.org/issue34363 is missing in dataclasses version 0.7 backported to python 3.6

# 3.6.8 (default, Feb 28 2019, 22:12:13)
# [GCC 8.2.1 20181127](6, 0, 1)
# dataclasses 0.7

from dataclasses import InitVar, astuple, dataclass, field
from typing import NamedTuple, Tuple
import numpy as np

class A(NamedTuple):
    x: float = np.nan
    y: float = np.nan

@dataclass
class B:
    tuple_a: InitVar[Tuple] = None
    named_tuple_a: A = field(init=False, default=A())

    def __post_init__(self, tuple_a):
        if tuple_a is not None:
            self.named_tuple_a = A(*tuple_a)

b = B()
astuple(b)

>> (A(x=<generator object _astuple_inner.<locals>.<genexpr> at 0x7f7dec0902b0>, y=nan),)

While the same code in Python 3.7.4 gives:

# 3.7.4 (default, Oct  4 2019, 06:57:26)
# [GCC 9.2.0] (6, 0, 1)

from dataclasses import InitVar, astuple, dataclass, field
from typing import NamedTuple, Tuple
import numpy as np

class A(NamedTuple):
    x: float = np.nan
    y: float = np.nan

@dataclass
class B:
    tuple_a: InitVar[Tuple] = None
    named_tuple_a: A = field(init=False, default=A())

    def __post_init__(self, tuple_a):
        if tuple_a is not None:
            self.named_tuple_a = A(*tuple_a)

b = B()
astuple(b)

>> (A(x=nan, y=nan),)
synap5e commented 4 years ago

This is the monkey patch I am using to resolve this issue while we wait for a fix here

    orig_asdict_inner = dataclasses._asdict_inner
    def _asdict_inner(obj, dict_factory):
        if isinstance(obj, tuple) and hasattr(obj, '_fields'):
            return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
        else:
            return orig_asdict_inner(obj, dict_factory)
    dataclasses._asdict_inner = _asdict_inner

    orig_astuple_inner = dataclasses._astuple_inner
    def _astuple_inner(obj, tuple_factory):
        if isinstance(obj, tuple) and hasattr(obj, '_fields'):
            return type(obj)(*[_astuple_inner(v, tuple_factory) for v in obj])
        else:
            return orig_astuple_inner(obj, tuple_factory)
    dataclasses._astuple_inner = _astuple_inner