Why is dataclasses.astuple returning a deepcopy of class attributes?

2024/10/3 17:13:26

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)])
Answer

This seems to be an undocumented behaviour of astuple (and asdict it seems as well).

dataclasses.astuple(*, tuple_factory=tuple)

Converts the dataclass instance to a tuple (by using the factory function tuple_factory). Each dataclass is converted to a tuple of its field values. dataclasses, dicts, lists, and tuples are recursed into.

Here's the source:

def _asdict_inner(obj, dict_factory):if _is_dataclass_instance(obj):result = []for f in fields(obj):value = _asdict_inner(getattr(obj, f.name), dict_factory)result.append((f.name, value))return dict_factory(result)elif isinstance(obj, (list, tuple)):return type(obj)(_asdict_inner(v, dict_factory) for v in obj)elif isinstance(obj, dict):return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))for k, v in obj.items())else:
return copy.deepcopy(obj)

The deepcopy here seems intentional, though probably should be documented.

https://en.xdnf.cn/q/70703.html

Related Q&A

customize dateutil.parser century inference logic

I am working on old text files with 2-digit years where the default century logic in dateutil.parser doesnt seem to work well. For example, the attack on Pearl Harbor was not on dparser.parse("12…

How can I check a Python unicode string to see that it *actually* is proper Unicode?

So I have this page:http://hub.iis.sinica.edu.tw/cytoHubba/Apparently its all kinds of messed up, as it gets decoded properly but when I try to save it in postgres I get:DatabaseError: invalid byte seq…

Test assertions for tuples with floats

I have a function that returns a tuple that, among others, contains a float value. Usually I use assertAlmostEquals to compare those, but this does not work with tuples. Also, the tuple contains other …

Django: Assigning ForeignKey - Unable to get repr for class

I ask this question here because, in my searches, this error has been generally related to queries rather than ForeignKey assignment.The error I am getting occurs in a method of a model. Here is the co…

Counting day-of-week-hour pairs between two dates

Consider the following list of day-of-week-hour pairs in 24H format:{Mon: [9,23],Thu: [12, 13, 14],Tue: [11, 12, 14],Wed: [11, 12, 13, 14]Fri: [13],Sat: [],Sun: [], }and two time points, e.g.:Start:dat…

Download A Single File Using Multiple Threads

Im trying to create a Download Manager for Linux that lets me download one single file using multiple threads. This is what Im trying to do : Divide the file to be downloaded into different parts by sp…

Merge string tensors in TensorFlow

I work with a lot of dtype="str" data. Ive been trying to build a simple graph as in https://www.tensorflow.org/versions/master/api_docs/python/train.html#SummaryWriter. For a simple operat…

How to reduce memory usage of threaded python code?

I wrote about 50 classes that I use to connect and work with websites using mechanize and threading. They all work concurrently, but they dont depend on each other. So that means 1 class - 1 website - …

Connection is closed when a SQLAlchemy event triggers a Celery task

When one of my unit tests deletes a SQLAlchemy object, the object triggers an after_delete event which triggers a Celery task to delete a file from the drive.The task is CELERY_ALWAYS_EAGER = True when…

Python escape sequence \N{name} not working as per definition

I am trying to print unicode characters given their name as follows:# -*- coding: utf-8 -*- print "\N{SOLIDUS}" print "\N{BLACK SPADE SUIT}"However the output I get is not very enco…