Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

A Parade of New Features Debuts in Python 2.5 : Page 4

Python 2.5 still has the smell of fresh paint but it's the perfect time to drill down on the most important new features in this comprehensive release. Read on for detailed explanations and examples of exception handling, resource management, conditional expressions, and more.


advertisement
Resource Management With 'with'
People always ask me: 'Why doesn't C++ have a finally clause to clean up resources in the face of exceptions? What happens if an exception is thrown before some resource was cleaned up properly?' Well, the truth is nobody ever asked me that. It's an utter lie. Still, lots of people wonder about this issue and newsgroups are rife with suggestions to add a finally keyword to C++. It won't happen. The reason is that C++ has a different way to guarantee resource cleanup. It's called a destructor.

Every C++ object has a destructor that is called automatically when an object that's allocated on the stack exits its enclosing scope even when an exception is thrown. C++ preaches that you should wrap every resource in an object that will take care of the cleanup in its destructor. This idiom is called "Resource Acquisition Is Initialization" (RAII). Is RAII better than an explicit try-finally block? Usually, it's much better. It allows simpler and more readable code. Rather than write the cleanup code in every place you use the resource, you write it just once—in the resource object destructor. You don't have to introduce a try-finally block with the mandatory nesting and name scoping. It's impossible to forget to call cleanup code.

Well, guess what? RAII is exactly the model for the new 'with' statement in Python. It allows you to use a resource (that supports it) in a special with-block and not worry about cleanup. I'll develop a mini-example soon, but first I'll whet your appetite:



lock = threading.Lock() with lock: # Critical section of code ...

The lock will be released automatically at the end of the critical section. A shorter version is:

with threading.Lock() as lock: # Critical section of code ...

The next example is more general. In it I have an ImportantResource class and a ResourceCoordinator class. The ResourceCoordinator is responsible for coordinating the use of a single resource between multiple users. Users are supposed to acquire the resource from the ResourceCoordinator, use it, and relinquish it.

When looking at the code notice that 'with' is not yet a full-fledged Python statement. In order to use it you must import it using __future__. This is a standard Python mechanism to introduce new keywords in case you have classes or variables that are named 'with'. In Python 2.6 'with' will gain first-class status.

from __future__ import with_statement import random class ImportantResource(object): def __init__(self, rc): self._cookie = None self._resourceCoordinator = rc def use(self): print 'ImportantResource - good job!' def __enter__(self): pass def __exit__(self, type, value, tb): self._resourceCoordinator.relinquish(self._cookie) class ResourceCoordinator(object): def __init__(self): self._resource = ImportantResource(self) self._cookie = None def acquire(self): if self._cookie == None: self._cookie = random.random() print 'ResourceCoordinator - resource acquired' self._resource._cookie = self._cookie return (self._cookie, self._resource) else: return (None, None) def relinquish(self, cookie): if cookie == self._cookie: self._cookie = None print 'ResourceCoordinator - resource released'

If the resource is not being held by someone the ResourceCoordinator hands out the resource and a random cookie when acquire() is called. If the cookie is something other than None it means the resource is unavailable (someone already holds it) and a pair of Nones is returned. When the holder calls relinquish() the ResourceCoordinator sets its cookie to None, so the next acquirer will be able to get hold of the resource. The cookie is also used to verify (weakly) that the relinquisher is indeed the current holder. This is a very poor and unsafe implementation of a resource coordination framework meant only to demonstrate the 'with' statement.

The ImportantResource instance is initialized by the ResourceCoordinator and gets the current cookie every time it is acquired. The __enter__ and __exit__ methods are what makes ImportantResource a context manager qualified to participate in the 'with' game. The __enter__ method is called upon entry to the 'with' block and the result is assigned to the 'as' variable if one is available (see the lock variable in the threading example). The __exit__ method is called upon exit from the 'with' block. If an exception was raised it receives the type, value, and traceback, otherwise all the parameters are None.

It's time to experiment a little with the ResourceCoordinator. I created three experiments. In each experiment the resource is acquired from the ResourceCoordinator, used, and relinquished. In experiment_1 the user must remember to relinquish the resource. If an exception is raised the resource will never be relinquished.

def experiment_1(rc): """Not so good. If exception is thrown resource is not relinquished""" print print '-'*5, 'Experiment 1' cookie, resource = rc.acquire() resource.use() rc.relinquish(cookie)

In experiment_2 the code is placed inside a try-finally block. The user must still remember to relinquish the resource properly in the finally clause using the returned cookie, but at least it is guaranteed to happen even in the face of exceptions.

def experiment_2(rc): """Better. finally ensures that the resource is relinquished""" print print '-'*5, 'Experiment 2' try: cookie, resource = rc.acquire() resource.use() finally: rc.relinquish(cookie)

In experiment_3 I used a with-block and it is much more concise. No need to manage a cookie or explicitly call relinquish.

def experiment_3(rc): """Much Better. Using the 'with' statement effectively""" print print '-'*5, 'Experiment 3' resource = rc.acquire()[1] # no need to store the cookie now with resource: resource.use()

I was kind enough not to raise any exceptions in all experiments so the results are identical. Here is the main script that invokes all experiments and the output:

if __name__=='__main__': rc = ResourceCoordinator() experiment_1(rc) experiment_2(rc) experiment_3(rc)

Output:

----- Experiment 1 ResourceCoordinator - resource acquired ImportantResource - good job! ResourceCoordinator - resource released ----- Experiment 2 ResourceCoordinator - resource acquired ImportantResource - good job! ResourceCoordinator - resource released ----- Experiment 3 ResourceCoordinator - resource acquired ImportantResource - good job! ResourceCoordinator - resource released

The 'with' statement supports another idiom, which is similar to the IDisposable interface in C#. If an object has a method named close() (yes, that would definitely be file objects) then they can be used as context managers too, without implementing __enter__ and __exit__. Here is how it is done:

from contextlib import closing with closing(open('test.txt', 'w')) as f: f.write('Yeah, it works!!!') print open('test.txt').read()

Note that 'closing' must be explicitly imported from contextlib.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date