Issue
I'm trying to use a dataclass as a (more strongly typed) dictionary in my application, and found this strange behavior when using a custom type subclassing list
within the dataclass. I'm using Python 3.11.3 on Windows.
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': []}
This does not occur if I use a regular list[float], but I'm using a custom class here to enforce some runtime constraints.
How do I do this correctly?
I'm open to just using .__dict__
directly, but I thought asdict()
was the more "official" way to handle this
A simple modification makes the code behave as expected, but is slightly less efficient:
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))
Solution
If you look at the source code of asdict
, you'll see that passes a generator expression that recursively calls itself on the elements of a list when it encounters a list:
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)
But your implementation depletes any iterator it gets in __init__
before the super
call.
Don't do that. You'll have to "cache" the values if you want to use the superclass constructor. Something like:
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)
Or perhaps:
class CustomFloatList(list):
def __init__(self, args):
super().__init__(args)
for i, arg in enumerate(self):
if not isinstance(arg, float):
raise TypeError(f"Expected index {i} to be a float, but it's a {type(arg).__name__}")
Answered By - juanpa.arrivillaga
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.