Article of interest: Python pros/cons for the enterprise

Jeff Schwab jeff at schwabcenter.com
Sat Feb 23 11:40:13 EST 2008


Paul Rubin wrote:
> Jeff Schwab <jeff at schwabcenter.com> writes:
>> The most traditional, easiest way to open a file in C++ is to use an
>> fstream object, so the file is guaranteed to be closed when the
>> fstream goes out of scope.  
> 
> Python has this too, except it's using a special type of scope
> created by the "with" statement.

Yes, this seems to be the Python way:  For each popular feature of some
other language, create a less flexible Python feature that achieves the
same effect in the most common cases (e.g. lambda to imitate function
literals, or recursive assignment to allow x = y = z).


>> CPython offers a similar feature, since
>> you can create a temporary object whose reference count will become
>> zero at the end of the statement where it is defined:
> 
>>      $ echo world >hello
>>      $ python
>>      >>> file('hello').read()
>>      'world\n'
> 
> CPython does not guarantee that the reference count will become zero
> at the end of the statement.  It only happens to work that way in your
> example, because the file.read operation doesn't make any new
> references to the file object anywhere.

It doesn't "happen" to work that way in the example; it works that way
by design.  I see what you're saying, though, and it is a good point.
Given a statements of the form:

	some_class().method()

The method body could create an external reference to the instance of
some_class, such that the instance would not be reclaimed at the end of
the statement.


> Other code might well do
> something different, especially in a complex multi-statement scope.
> Your scheme's

It's not "my" scheme.  I got it from Martelli.


> determinism relies on the programmer accurately keeping
> track of reference counts in their head, which is precisely what
> automatic resource management is supposed to avoid.

This is a special case of the reference count being 1, then immediately 
dropping to zero.  It is simple and convenient.  The approach is, as you 
rightly point out, not extensible to more complicated situations in 
Python, because the reference counting ceases to be trivial.

The point is that once you tie object lifetimes to scope, rather than 
unpredictable garbage collection, you can predict with perfect ease and 
comfort exactly where the objects are created and destroyed.  If you can 
then request that arbitrary actions be taken automatically when those 
events happen, you can pair up resource acquisitions and releases very 
easily.  Each resource has an "owner" object whose constructor acquires, 
and whose destructor releases.  The resources are released in the 
reverse order, which is almost always exactly what you want.

Suppose you are using objects that have to be closed when you have 
finished with them.  You would associate this concept with a type:

     class Closer:
         def __init__(self, closable):
             self.closable = closable)
         def __del__(self):
             self.closable.close()

The C++-style paradigm would then let you do this:

     def my_func(a, b, c):
         a_closer = Closer(a)
         b_closer = Closer(b)
         c_closer = Closer(c)

	# ... arbitrary code ...

If an exception gets thrown, the objects get closed.  If you return 
normally, the objects get closed.  This is what "with" is supposed to 
replace, except that it only seems to cover the trivial case of a 
single, all-in-one cleanup func.  That's only a direct replacement for a 
single constructor/destructor pair, unless you're willing to have an 
additional, nested with-statement for each resource.

Now suppose there is an object type whose instances need to be 
"released" rather than "closed;" i.e., they have a release() method, but 
no close() method.  No problem:  You have the Closer class get its 
action indirectly from a mapping of types to close-actions.  Whenever 
you have a type whose instances require some kind of cleanup action, you 
add an entry to the mapping.

     class Closer:
	actions = TypeActionMap()

         # ...

         def __del__(self):
             actions[type(self.closable)](self.closable)

The mapping is not quite as simple as a dict, because of inheritance. 
This is what function overloads and C++ template specializations are 
meant to achieve.  Similar functionality could be implemented in Python 
via pure Python mapping types, represented above by TypeActionMap.


> If you want
> reliable destruction it's better to set it up explicitly, using
> "with".

That's true of the current language.  I don't have enough experience 
with "with" yet to know whether it's a realistic solution to the issue. 
  IMO, they are at least preferable to Java-style finally-clauses, but 
probably not a replacement for C++-style RAII.



More information about the Python-list mailing list