Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improvizing a drop-in replacement for the "with" statement for Python 2.4

Can you suggest a way to code a drop-in replacement for the "with" statement that will work in Python 2.4?

It would be a hack, but it would allow me to port my project to Python 2.4 more nicely.

EDIT: Removed irrelevant metaclass sketch

like image 253
Ram Rachum Avatar asked Oct 10 '09 09:10

Ram Rachum


3 Answers

Just use try-finally.

Really, this may be nice as a mental exercise, but if you actually do it in code you care about you will end up with ugly, hard to maintain code.

like image 77
itsadok Avatar answered Oct 16 '22 11:10

itsadok


You could (ab)use decorators to do this, I think. The following works, eg:

def execute_with_context_manager(man):
    def decorator(f):
        target = man.__enter__()
        exc = True
        try:
            try:
                f(target)
            except:
                exc = False
                if not man.__exit__(*sys.exc_info()):
                    raise
        finally:
            if exc:
                man.__exit__(None, None, None)
        return None
    return decorator

@execute_with_context_manager(open("/etc/motd"))
def inside(motd_file):
    for line in motd_file:
        print line,

(Well, in Python 2.4 file objects don't have __enter__ and __exit__ methods, but otherwise it works)

The idea is you're replacing the with line in:

with bar() as foo:
    do_something_with(foo)
    do_something_else_with(foo)
    # etc...

with the decorated function "declaration" in:

@execute_with_context_manager( bar() )
def dummyname( foo ):
    do_something_with(foo)
    do_something_else_with(foo)
    # etc...

but getting the same behaviour (the do_something_... code executed). Note the decorator changes the function declaration into an immediate invocation which is more than a little evil.

like image 38
Anthony Towns Avatar answered Oct 16 '22 09:10

Anthony Towns


Since you need to exit the context manager both during errors and not errors, I don't think it's possible to do a generic usecase with metaclasses, or in fact at all. You are going to need try/finally blocks for that.

But maybe it's possible to do something else in your case. That depends on what you use the context manager for.

Using __del__ can help in some cases, like deallocating resource, but since you can't be sure it gets called, it can only be used of you need to release resources that will be released when the program exits. That also won't work if you are handling exceptions in the __exit__ method.

I guess the cleanest method is to wrap the whole context management in a sort of context managing call, and extract the code block into a method. Something like this (untested code, but mostly stolen from PEP 343):

def call_as_context_manager(mgr, function):
    exit = mgr.__exit__
    value = mgr.__enter__()
    exc = True
    try:
        try:
            function(value)
        except:
            exc = False
            if not exit(*sys.exc_info()):
                raise
    finally:
        if exc:
            exit(None, None, None)
like image 1
Lennart Regebro Avatar answered Oct 16 '22 10:10

Lennart Regebro