为什么 dataclasses.astuple 返回 class 属性的深度拷贝?
Why is dataclasses.astuple returning a deepcopy of class attributes?
在下面的代码中,astuple
函数正在对数据 class 的 class 属性进行深度复制。为什么它不会产生与函数 my_tuple
相同的结果?
import copy
import dataclasses
@dataclasses.dataclass
class Demo:
a_number: int
a_bool: bool
classy: 'YOhY'
def my_tuple(self):
return self.a_number, self.a_bool, self.classy
class YOhY:
def __repr__(self):
return (self.__class__.__qualname__ + f" id={id(self)}")
why = YOhY()
print(why) # YOhY id=4369078368
demo = 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=4369078368
trupled = dataclasses.astuple(demo)
print(trupled) # YOhY id=4374460064
trupled2 = trupled
print(trupled2) # YOhY id=4374460064
trupled3 = copy.copy(trupled)
print(trupled3) # YOhY id=4374460064
trupled4 = copy.deepcopy(trupled)
print(trupled4) # YOhY id=4374460176
脚注
由于 Anthony Sottile's 出色的响应清楚地表明这是编码到 Python 3.7 中的行为。任何希望 astuple 以与 collections.namedtuple 相同的方式解压的人都需要将其替换为类似于 Demo.my_tuple
的方法。下面的代码没有 my_tuple 脆弱,因为如果数据 class 的字段发生变化,它不需要修改。另一方面,如果 __slots__
正在使用,它将不起作用。
只要 class 或其超 class 中存在 __hash__
方法,两个版本的代码都会构成威胁。请参阅 Python 3.7 文档以了解 unsafe_hash
,特别是 '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)])
这似乎是 astuple
的 undocumented 行为(asdict
似乎也是)。
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.
这是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)
这里的深层复制似乎是有意的,但可能应该记录在案。
在下面的代码中,astuple
函数正在对数据 class 的 class 属性进行深度复制。为什么它不会产生与函数 my_tuple
相同的结果?
import copy
import dataclasses
@dataclasses.dataclass
class Demo:
a_number: int
a_bool: bool
classy: 'YOhY'
def my_tuple(self):
return self.a_number, self.a_bool, self.classy
class YOhY:
def __repr__(self):
return (self.__class__.__qualname__ + f" id={id(self)}")
why = YOhY()
print(why) # YOhY id=4369078368
demo = 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=4369078368
trupled = dataclasses.astuple(demo)
print(trupled) # YOhY id=4374460064
trupled2 = trupled
print(trupled2) # YOhY id=4374460064
trupled3 = copy.copy(trupled)
print(trupled3) # YOhY id=4374460064
trupled4 = copy.deepcopy(trupled)
print(trupled4) # YOhY id=4374460176
脚注
由于 Anthony Sottile's 出色的响应清楚地表明这是编码到 Python 3.7 中的行为。任何希望 astuple 以与 collections.namedtuple 相同的方式解压的人都需要将其替换为类似于 Demo.my_tuple
的方法。下面的代码没有 my_tuple 脆弱,因为如果数据 class 的字段发生变化,它不需要修改。另一方面,如果 __slots__
正在使用,它将不起作用。
只要 class 或其超 class 中存在 __hash__
方法,两个版本的代码都会构成威胁。请参阅 Python 3.7 文档以了解 unsafe_hash
,特别是 '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)])
这似乎是 astuple
的 undocumented 行为(asdict
似乎也是)。
dataclasses.astuple(*, tuple_factory=tuple)
Converts the dataclass
instance
to a tuple (by using the factory functiontuple_factory
). Each dataclass is converted to a tuple of its field values. dataclasses, dicts, lists, and tuples are recursed into.
这是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)
这里的深层复制似乎是有意的,但可能应该记录在案。