In the code below the astuple
function is carrying out a deep copy of a class attribute of the dataclass. Why is it not producing the same result as the function my_tuple
?
import copy
import dataclasses@dataclasses.dataclass
class Demo:a_number: inta_bool: boolclassy: 'YOhY'def my_tuple(self):return self.a_number, self.a_bool, self.classyclass YOhY:def __repr__(self):return (self.__class__.__qualname__ + f" id={id(self)}")why = YOhY()
print(why) # YOhY id=4369078368demo = Demo(1, True, why)
print(demo) # Demo(a_number=1, a_bool=True, classy=YOhY id=4369078368)untrupled = demo.my_tuple()
print(untrupled) # YOhY id=4369078368trupled = dataclasses.astuple(demo)
print(trupled) # YOhY id=4374460064trupled2 = trupled
print(trupled2) # YOhY id=4374460064trupled3 = copy.copy(trupled)
print(trupled3) # YOhY id=4374460064trupled4 = copy.deepcopy(trupled)
print(trupled4) # YOhY id=4374460176
Footnote
As Anthony Sottile's excellent response makes clear this is the behavior coded into Python 3.7. Anyone expecting astuple to unpack the same way as collections.namedtuple will need to replace it with a method similar to Demo.my_tuple
. The following code is less fragile than my_tuple because it will not need modification if the fields of the dataclass are changed. On the other hand it won't work if __slots__
are in use.
Both versions of the code pose a threat whenever a __hash__
method is present in the class or its superclasses. See the Python 3.7 documentation for unsafe_hash
in particular the two paragraphs beginning 'Here are the rules governing implicit creation of a __hash__()
method'.
def unsafe_astuple(self):return tuple([self.__dict__[field.name] for field in dataclasses.fields(self)])