Python Forum

Full Version: How does open context manager work?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
While answering a question in another thread I wrote this:
import contextlib
 
@contextlib.contextmanager
def CreateFile(FilePath):
    file = open(FilePath, 'w', encoding="utf-8")
    file.write("Court,Location,Citation Number,Case Description,File Date\n")
    yield file
    file.close()
I occasionally create context managers when writing code that requires "cleaning up", but I never did it for a file. To use the code above, I am forced to us a context manager:
with CreateFile(filename) as file:
    # do stuff with file
I cannot do this because CreateFile creates a context object, not a file:
file = CreateFile(filename)
So how does open() let me do this?
file_one = open(filelename_one)
with open(filename_two) as file_two:
    # do file_two stuff
The best I can do to mimic open() is this:
class CreateFile():
    def __init__(self, filename, mode="w", encoding="utf-8"):
        print(f"CreateFile({filename})")
        self.file = open(filename, mode, encoding=encoding)
        self.file.write("Court,Location,Citation Number,Case Description,File Date\n")

    def __enter__(self):
        print("enter")
        return self.file

    def __exit__(self, *_):
        print("exit")
        self.file.close()

    def __getattr__(self, attribute):
        print(f"__getattr__({attribute})")
        return getattr(self.file, attribute)

file = CreateFile("junk.txt")
file.write("This is a test\n")
print(file.tell())
file.close()

print("\n")
with CreateFile("junk2.txt") as file:
    file.write("This is a test\n")
Output:
CreateFile(junk.txt) __getattr__(write) __getattr__(tell) 75 __getattr__(close) CreateFile(junk2.txt) enter exit
I would imagine you have pretty much mimicked open, __enter__ would return itself on the real open
Maybe something like this
class MyOpen:
    def __init__(self, file):
        print(f"Opened {file}")

    def __enter__(self):
        print("Enter")
        return self

    def __exit__(self, *args):
        print("Exit")
        return self.close()

    def write(self, string):
        print(f"write: {string}")

    def close(self):
        print("File closed")

    def tell(self):
        return 75


print("file = CreateFile()")
file = MyOpen("junk.txt")
file.write("This is a test")
print(file.tell())
file.close()

print("\n\nwith CreateFile() as file")
with MyOpen("junk2.txt") as file:
    file.write("This is a test")
Output:
file = CreateFile() Opened junk.txt write: This is a test 75 File closed with CreateFile() as file Opened junk2.txt Enter write: This is a test Exit File closed
No sure I understand the issue but you could nest contexts like so
import contextlib
  
@contextlib.contextmanager
def CreateFile(FilePath):
    with open(FilePath, 'w', encoding="utf-8") as file:
        file.write("Court,Location,Citation Number,Case Description,File Date\n")
        yield file
There is no issue. I am wondering how open() can be used like a function or a conrext. The contexts I make have to be used with "with" and don't work when called like a function. Not without extra effort. I was wondering if I'm missing something.
The built-in function open returns relying on mode and buffering an instance of TextIOWrapper, BufferedReader or FileIO.
Each object has the __enter__ method, which is called by the contextmanager. The returned object is the instance itself.
After leaving the block, the __exit__ method is called.

Easy example:
class File:
    def __init__(self, path):
        print("Creating instance")
        self.path = path
    def __enter__(self):
        print("Entering Context")
        return self
    def __exit__(self, exc_type, exc_obj, exc_tb):
        print("Leaving context")
    def __str__(self):
        return "<Instance of File>"

def open(path):
    """
    Returns a new Instance of File
    """
    return File(path)


with open("something") as fd:
    print(fd)

  1. When open("something") is called, a new instance of File is returned
  2. The contextmanager calls the __enter__ method
  3. The returned object from __enter__ is assigned to fd
  4. Statements in the with block are executed
  5. after the last statement in the block, tthe __exit__ method of the instance is called
So the reason why you can use open() in both a context (with open() as f:) and like a function (f = open()) is because open is special?
open is just a function, which returns an object.
The returned object is used and not the function open itself.

The returned instance has the methods __enter__ and __exit__ which gives the object the ability to used with a conextmanager.
If you're not using the contextmanager, these methods are never called.
So the magic is not in the open, but the underlying objects. open() might return a text io wrapper. Text IO wrapper knows how to open/create/read/write/whatever, but it also has __enter__ and __exit__ methods. This is essentially what Yoriz posted. If I want to use my classes in a context manager I should design context management into the classes instead of tacking it on with something like contextlib.