How does open () work with and without `with`?

I would like to write a function similar to open . I would like to call it with with , but also without with .

When I use contextlib.contextmanager my function works fine with with :

 @contextmanager def versioned(file_path, mode): version = calculate_version(file_path, mode) versioned_file = open(file_path, mode) yield versioned_file versioned_file.close() 

So, I use it as follows:

 with versioned('file.txt', 'r') as versioned_file: versioned_file.write(...) 

How to use it without with :

 versioned_file = versioned('file.txt', 'r') versioned_file.write(...) versioned_file.close() 

He complains:

 AttributeError: 'GeneratorContextManager' object has no attribute 'write' 
+6
source share
3 answers

You have the following:

 @contextmanager def versioned(file_path, mode): # some setup code yield versioned_file # some teardown code 

Your main problem, of course, is that you yield from the context manager exits the with statement via as , but is not the object returned by your function. You need a function that returns what behaves as an open() object. That is, the object of the context manager, which gives itself.

Whatever you can do, it depends on what you can do with the versioned_file type. If you cannot change this, you are mostly out of luck. If you can change it, you need to implement the __enter__ and __exit__ functions, as described in PEP 343 .

In your code example, however, it already has it, and your break code is the same as it is when you exit the context. So don't worry about contextlib, just return the result of open() .

For other examples where you need __enter__ and __exit__ , if you like the contextlib style (and who doesn't?), You can combine two things. Write a context function that is decorated by @contextmanager and gives self . Then do:

 def __enter__(self): self.context = context() # if context() is a method use a different name! return self.context.__enter__() def __exit__(self, *args): return self.context.__exit__(*args) 

It mostly depends on whether you find it better or worse than splitting the installation code into __enter__ and the break code into __exit__ . I usually find it better.

+2
source

The problem is that the contextmanager provides exactly that; the context manager to be used in the with statement. A function call does not return a file object, but a special context generator that provides the __enter__ and __exit__ functions. If you want the work with and the "normal" assignments to work, then you will need to have some kind of object as the return value from your fully functional function, and also provide contextual functions.

You can do this quite easily by creating your own type and manually providing contextual functions:

 class MyOpener: def __init__ (self, filename): print('Opening {}'.format(filename)) def close (self): print('Closing file.') def write (self, text): print('Writing "{}"'.format(text)) def __enter__ (self): return self def __exit__ (self, exc_type, exc_value, traceback): self.close() 
 >>> f = MyOpener('file') Opening file >>> f.write('foo') Writing "foo" >>> f.close() Closing file. >>> with MyOpener('file') as f: f.write('foo') Opening file Writing "foo" Closing file. 
+8
source

Do you really need to use contextlib.contextmanager? If you have a custom thread, you would like to use the Poke solution.

But since you are simply returning a file object, why do you need to solve all the problems:

 def versioned(file_path, mode): version = calculate_version(file_path, mode) return open(file_path, mode) with versioned('test.conf', 'r') as stream: print stream.read() f = versioned('test.conf', 'r') print f.read() f.close() 

Both will work just fine :)

+2
source

All Articles