Reputation: 450
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'.
Upvotes: 1
Views: 2017
Reputation: 2327
One option here which would avoid calling object.__setattr__
would be to do the following:
open
function which returns a replacement dataclass object that has the same filename but opens the file and stores it in the file field of the new object.import dataclasses
from dataclasses import dataclass, field
import io
@dataclass(frozen=True)
class Book:
filename: str
file: io.TextIOWrapper = field(default=None)
def open(self):
return dataclasses.replace(self, file=open(self.filename))
def close(self):
self.file.close()
book = Book("testfile.txt")
book = book.open()
book.close()
print(book) # Book(filename='testfile.txt', file=<_io.TextIOWrapper name='testfile.txt' mode='r' encoding='cp1252'>)
Got the idea from this article: https://python.plainenglish.io/why-and-how-to-write-frozen-dataclasses-in-python-69050ad5c9d4
Upvotes: 2
Reputation: 33833
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()
Upvotes: 2