It’s my short test program and output (with error).
Why does the print command try to print an attribute that does not exist?
Program:
from typing import List, Tuple, Optional
from dataclasses import dataclass
import sys
@dataclass
class Phrase:
text: str
words: Optional[List]
trans: Optional[Tuple]
forComp: Optional[List]
transLen: int = 0
def test10():
print('Python %s on %s' % (sys.version, sys.platform))
ob = Phrase('ala', ['as'], None, None)
print(ob)
if hasattr(ob, 'words'):
delattr(ob, 'words')
print('After del', ob)
if __name__ == '__main__':
test10()
Output:
Python 3.11.4 (main, Jul 13 2023, 22:13:17) [GCC 11.3.0] on linux
Phrase(text='ala', words=['as'], trans=None, forComp=None, transLen=0)
After del Traceback (most recent call last):
File "/home/heniek/sync-sent/test-dc.py", line 22, in <module>
test10()
File "/home/heniek/sync-sent/test-dc.py", line 19, in test10
print('After del', ob)
File "/usr/local/lib/python3.11/dataclasses.py", line 240, in wrapper
result = user_function(self)
^^^^^^^^^^^^^^^^^^^
File "<string>", line 3, in __repr__
AttributeError: 'Phrase' object has no attribute 'words'
Process finished with exit code 1
When you print ob, you’re calling the __repr__ method of Phrase. You didn’t write this method; rather, it’s being supplied by the @dataclass decorator. The __repr__ method that gets generated by dataclass is roughly equivalent to having written
Notably, there’s no checks for whether the attributes set in __init__ still exist; they’re assumed to still be there. When you later delete an attribute with delattr(ob, 'words'), you’re breaking an assumption of dataclass.
If you want to have a dynamic __repr__ that changes what’s displayed based on which attributes are present, you’ll need to write that logic yourself. Alternatively, if you want to always exclude words from the repr, that can be done using dataclasses.field.
Generally, having attributes that only sometimes exist is a foot-gun that is usually best avoided. Instead, it’s often simpler to work with attribtues that always exist and which take on a default or sentinel value when a true value isn’t available. Since you declared words to be a list[Any] | None, it may make more sense to set it to an empty list or None rather than deleting the attribute.
Thanks for the advice.
I only remove attributes when saving the object to a JSON file. Otherwise I have a lot of “empty” data (null, , {}) in the file. When reloading the JSON file, these empty attributes are automatically recreated in the program. The object in the running program always has a full set of attributes. These empty ones too.