Search code examples
pythonpython-dataclasses

Python dataclasses: assign variable to field in a frozen instance from input


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'.


Solution

  • 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()