Python Forum
How does open context manager work?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How does open context manager work?
#1
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
Reply
#2
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
Reply
#3
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
Reply
#4
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.
Reply
#5
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
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#6
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?
Reply
#7
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.
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#8
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.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  INSTALL MANAGER wrappitup123 0 22 May-25-2026, 03:49 PM
Last Post: wrappitup123
  [PROBLEM] Removing IDLE from context menu WebSpider 1 1,744 Sep-28-2023, 03:35 PM
Last Post: deanhystad
  How to not open the context menu if a mouse gesture is executed? MicheliBarcello 2 2,098 Aug-22-2023, 02:47 PM
Last Post: deanhystad
  Open a new window does not work Nietzsche 4 3,108 Jun-14-2023, 08:52 AM
Last Post: Nietzsche
  with open context inside of a recursive function billykid999 1 1,962 May-23-2023, 02:37 AM
Last Post: deanhystad
  Context-sensitive delimiter ZZTurn 9 4,557 May-16-2023, 07:31 AM
Last Post: Gribouillis
  Decimal context stevendaprano 1 1,987 Apr-11-2022, 09:44 PM
Last Post: deanhystad
  How to create an app manager _ShevaKadu 8 6,803 Nov-01-2020, 12:47 PM
Last Post: _ShevaKadu
  TextIOWrapper.tell() with Python 3.6.9 in context of 0D/0A fschaef 0 3,178 Mar-29-2020, 09:18 AM
Last Post: fschaef
  Time execution of a "for loop" with zip different in 2 equivalent context sebastien 1 2,919 Oct-11-2019, 11:07 AM
Last Post: sebastien

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020