I have a dataclass like this:
@dataclass
class Bla:
arg1: Optional[int] = None
arg2: Optional[str] = None
arg3: Optional[Dict[str, str]] = None
I want this behavior:
>>> bla = Bla(arg1=None, arg2=None, arg3=None)
>>> asdict(bla)
{'arg1': None, 'arg2': None, 'arg3': None}
>>> bla = Bla()
{}
In this specific case, I could use a dict
, but I would lose the possibility of have type-hints (and use mypy)
So I tried this:
class none:
...
@dataclass
class Bla:
arg1: Union[none, int] = none()
arg2: Union[none, str] = none()
arg3: Union[none, Dict[str, str]] = none()
def __post_init__(self) -> None:
for k, v in self.__dict__.copy().items():
if isinstance(v, none):
delattr(self, k)
But the result was:
>>> asdict(Bla())
{'arg1': <__main__.none object at 0x7f71bc0159b0>, 'arg2': <__main__.none object at 0x7f71bc015a90>, 'arg3': <__main__.none object at 0x7f71bc015ac8>}
I expected an empty dict
If try:
>>> a = Bla(None, None, None)
>>> del a.__dict__["arg1"]
>>> asdict(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/(....)/venv/lib/python3.6/site-packages/dataclasses.py", line 1011, in asdict
return _asdict_inner(obj, dict_factory)
File "/home/(...)/venv/lib/python3.6/site-packages/dataclasses.py", line 1018, in _asdict_inner
value = _asdict_inner(getattr(obj, f.name), dict_factory)
AttributeError: 'Bla' object has no attribute 'arg1'
How can I dynamically remove attributes from a dataclass object in a way that I could use asdict
after that?
A data class is a class typically containing mainly data, although there aren't really any restrictions. It is created using the new @dataclass decorator, as follows: from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str.
Python introduced the dataclass in version 3.7 (PEP 557). The dataclass allows you to define classes with less code and more functionality out of the box.
A dataclass can very well have regular instance and class methods. Dataclasses were introduced from Python version 3.7. For Python versions below 3.7, it has to be installed as a library.
This isn't an answer to your question about deleting attributes from a dataclass, but it provides a mechanism to get a custom asdict
that behaves in the way you want:
from dataclasses import dataclass
from typing import Optional, Dict, cast
SENTINEL = cast(None, object()) # have a sentinel that pretends to be 'None'
@dataclass
class Bla:
arg1: Optional[int] = SENTINEL
arg2: Optional[str] = SENTINEL
arg3: Optional[Dict[str, str]] = SENTINEL
def asdict(self):
return {k: v for k, v in self.__dict__.items() if v is not SENTINEL}
Some tests:
>>> Bla().asdict()
{}
>>> Bla(None, None).asdict()
{'arg1': None, 'arg2': None}
>>> Bla(1, 'foo', None).asdict()
{'arg1': 1, 'arg2': 'foo', 'arg3': None}
But remember that it's all a lie, and the attributes do still exist when called explicitly:
>>> print(Bla().arg1)
<object object at 0x7fc89ed84250>
My question was about how to remove attributes from a dataclasses.dataclass
object in a way that I could use the function dataclasses.asdict
to generate dictionaries.
My use case was lots of models that I'd like to store in an easy-to-serialize and type-hinted way, but with the possibility of omitting elements (without having any default values). My first approach was to figure out a way to remove any item that I was not explicitly passed in the dataclass
constructor.
However, as Patrick and Arne point out, wisely, that's not the right thing to try to accomplish with dataclasses
.
The best way I found to solve my use case was to use TypedDict
from the typing_extensions
module, that, with PEP 589, will be part of the standard library in the module typing
in Python 3.8.
In Python 3.8:
from typing import TypedDict, Dict
class Bla(TypedDict):
arg1: int
arg2: str
arg3: Dict[str, str]
In other versions you have to install typing_extensions
module:
pip install typing-extensions
then:
from typing_extensions import TypedDict
Let's try:
>>> Bla(arg1=1, arg2="bla", arg3={"bla":"bla"})
{'arg1': 1, 'arg2': 'bla', 'arg3': {'bla': 'bla'}}
>>> Bla(arg1=1, arg2="bla")
{'arg1': 1, 'arg2': 'bla'}
>>> Bla(arg1=1)
{'arg1': 1}
Very elegant and fits perfectly in my use case.
The only drawback is that TypedDict
doesn't support default values yet. There is a PR for that and I'm hopeful they will do something about.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With