new syntax for cleanup (was Re: destructors order not guaranteed?)
Alex Martelli
aleaxit at yahoo.com
Thu Nov 2 04:55:58 EST 2000
More information about the Python-list mailing list
Thu Nov 2 04:55:58 EST 2000
- Previous message (by thread): new syntax for cleanup (was Re: destructors order not guaranteed?)
- Next message (by thread): new syntax for cleanup (was Re: destructors order not guaranteed?)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
"Jeff Petkau" <jpet at eskimo.com> wrote in message news:Nk8M5.2535$Vk6.390261 at paloalto-snr1.gtei.net... [snip] > > So, the beautiful C++ idiom...: > > > > template <class Resource> > > struct Locker { > > Resource& pr; > > void (*closer)(Resource&); > > Locker(Resource& pr, void (*closer)(Resource&)): > > pr(pr), closer(closer) {} > > ~Locker() { closer(pr); } > > }; > > > > has no appropriate Python translation (nor Java, etc). > > This is the one thing I really miss from C++ when I'm using > sensible languges. Ditto. But it's just a matter of convenience. > There are lots of things that normally > get cleaned up in stack unwind order--locks, semaphores, > open files, fp state, APIs with push/pop semantics like > OpenGL, etc.--and it's surprisingly difficult to get this > right in Python. And in C++, too -- if you want to consider every possible exception and handle it decently. Exception-safe code is NO walkover in any language. Consider...: > cases correctly. (Your example illustrates the most common > form of this bug--if acquire(), getit(), or dropIt() throws, > cleanup code is either missed or called when it shouldn't be.) If a function called in a C++ destructor throws, and the exception exits from the destructor, what happens next is pandemonium. "A destructor must not throw" is not written in the C++ standard, but it might as well have been, given the disasters caused by destructors that throw. And having each destructor completely wrapped by a totally generic try/catch is hardly a solution -- exceptions are not meant to be totally ignored in such ways, and there's no telling how compromised the semantic state of your program might be if you do that in some library code. So, in practice, a silent unwritten convention "functions that release resources (and other cleanup tasks typically called in destructors) must never find themselves in a situation in which an exception _must_ be raised" seems to have arisen (at least in the C++ circles I frequent). This does have system-level architectural implications, but it seems they're generally sensible ones (e.g., they basically forbid the pattern where an object accumulates 'work to be done' indications, and fires it all off on destruction -- when it's probably too late to do anything sensible if terrible, exception-requiring anomalies should emerge; not a good pattern anyway, that is...). You _are_ entirely right that it was silly of me to 'translate' the C++ snippet { Locker<dbhandle> lock1(myDatabase.acquire(), dbRelease); to the Python snippet: try: lock1=myDatabase.acquire() # ... finally: dbRelease(lock1) because the latter explicity requires the finalization to take place _if the acquisition throws_, which is when we most definitely *don't* want it. So, it should be: lock1 = myDatabase.acquire() try: # ... finally: dbRelease(lock1) i.e., release (no matter what else happens in the meantime) if and only if the acquisition itself succeeds. You're also right that this places the finalization textually far from the initialization, and may engender excessive nesting due to granularity issues, unless tempered with other idioms (whether those idioms should also cover up for exception-throwing finalization ops, well, that may be harder). E.g., lock2 = None lock1 = myDatabase.acquire() try: lock2 = myResource.getit() fonz() finally: if not lock2 is None: dropIt(lock2) dbRelease(lock1) This avoids the extra nesting by using lock2 as a flag of 'has this ever been acquired' (and several variants are of course possible). If dropIt throws, this still has problems (no dbRelease gets called). Your idea for a 'cleanup' (to be written close to the initialization, but executed on exit, like a 'finally') is interesting. I'm not sure a context dependent keyword makes much sense here, though. Perhaps one could overload 'finally' (which is already a keyword), when not paired with 'try', to mean "do this on exiting the current frame" rather than "do this on exiting the try-block"; the meaning seems close enough that the overload could help understanding rather than confuse. Or maybe finally-on-its-own could mean, as in your first suggestion for cleanup, "wrap a try block around all the rest of this block, with this finally-clause" -- this would restrict the change to the parser (isolated-finally, rather than being an error, would generate bytecode that is already currently valid). (I'm even starting to wonder if isolated-except might not be similarly useful, with a totally similar interpretation to isolated-finally...) Alex
- Previous message (by thread): new syntax for cleanup (was Re: destructors order not guaranteed?)
- Next message (by thread): new syntax for cleanup (was Re: destructors order not guaranteed?)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
More information about the Python-list mailing list