我试图在我的应用程序中使用数据类作为(更强类型)字典,并在数据类中使用自定义类型时发现这种奇怪的行为。我在 Windows 上使用 Python 3.11.3。
from dataclasses import dataclass, asdict
class CustomFloatList(list):
def __init__(self, args):
for i, arg in enumerate(args):
assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}"
super().__init__(args)
@classmethod
def from_list(cls, l: list[float]):
return cls(l)
@dataclass
class Poc:
x: CustomFloatList
p = Poc(x=CustomFloatList.from_list([3.0]))
print(p) # Prints Poc(x=[3.0])
print(p.x) # Prints [3.0]
print(asdict(p)) # Prints {'x': []}
如果我使用常规列表[float],则不会发生这种情况,但我在这里使用自定义类来强制执行一些运行时约束。
如何正确执行此操作?
我愿意直接使用
.__dict__
,但我认为asdict()
是处理这个问题的更“官方”的方式
简单的修改使代码的行为符合预期,但效率稍低:
from dataclasses import dataclass, asdict
class CustomFloatList(list):
def __init__(self, args):
dup_args = list(args)
for i, arg in enumerate(dup_args):
assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}"
super().__init__(dup_args)
@classmethod
def from_list(cls, l: list[float]):
return cls(l)
@dataclass
class Poc:
x: CustomFloatList
p = Poc(x=CustomFloatList.from_list([3.0]))
print(p)
print(p.x)
print(asdict(p))
如果您查看 asdict
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)
但是 您的实现会在
__init__
调用之前耗尽它进入
super
的所有迭代器。
不要这样做。如果您想使用超类构造函数,则必须“缓存”这些值。比如:
class CustomFloatList(list):
def __init__(self, args):
args = list(args)
for i, arg in enumerate(args):
assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}"
super().__init__(args)
或者也许:
class CustomFloatList(list):
def __init__(self, args):
for i, arg in enumerate(args):
assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}"
self.append(arg)
这并非特定于
asdict()
。问题是您的子类与真正的 list
类不兼容,因为当输入是生成器时, __init__()
方法无法正常工作。您可以更简单地看到问题
print(CustomFloatList(float(x) for x in range(10)))
这将打印一个空列表。
您在循环中使用生成器来检查所有元素是否为浮点数,然后在调用
super().__init__()
时传递空生成器。
更改它,以便一次提取内容。
class CustomFloatList(list):
def __init__(self, args):
temp = list(args)
for i, arg in enumerate(temp):
assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}"
super().__init__(temp)
@classmethod
def from_list(cls, l: list[float]):
return cls(l)