I want to create an immutable class that reads a file and do other things. I have problems with the mutability:
from dataclasses import dataclass
import io
@dataclass(frozen=True)
class Book:
filename: str
#file: io.TextIOWrapper
def __new__(cls, filename):
self = super().__new__(cls)
self.file = open(filename, "r")
return self
def __post_init__(self):
#self.file = open(self.filename, "r")
pass
def close(self):
self.file.close()
book = Book("testfile.txt")
book.close()
print(book)
This is the error I get:
Traceback (most recent call last):
File "D:\Sync1\Code\Python3\EconoPy\Version_0.2\test.py", line 32, in <module>
book = Book("testfile.txt")
File "D:\Sync1\Code\Python3\EconoPy\Version_0.2\test.py", line 17, in __new__
self.file = open(filename, "r")
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'file'
I want to set the attribute self.file
from the input filename
, but the 'frozening' is forbidding that. With the __post_init__
I can do that if I remove the 'frozening'.
At the moment this looks like an inappropriate use of a dataclass.
Opening the file when the class is instantiated and having a close
method... this looks like a glorified file object so far - maybe it would be better as a subclass of TextIOWrapper
?
Or as a context manager? https://docs.python.org/3/library/stdtypes.html#typecontextmanager
Anyway, for sake of answering your question as asked, we can find the solution in the docs here https://docs.python.org/3/library/dataclasses.html#frozen-instances
...we can bypass the enforcement of immutability by using object.__setattr__
.
So a working example would look like:
import io
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Book:
filename: str
file: io.TextIOWrapper = field(init=False)
def __post_init__(self):
object.__setattr__(self, "file", open(self.filename, "r"))
def close(self):
self.file.close()