[PythonCAD] Seeking code suggestions when handling exceptions

Art Haas ahaas at airmail.net
Wed May 18 22:26:48 CEST 2005


Hi.

For the last couple of days I've been puzzling about exception handling
in Python. The thing that got me started down this track was the threads
on the python-dev mailing list dealing with proposals to enhance and
simplify the exception handling in Python. For example, one proposal
was to allow for try/except/finally blocks in the code. This change
would be a nice addition as it could simplify code that currently nests
a try/except block within a try/finally block. Another proposal dealt
with anonymous blocks, generator functions, and templates, and this
proposal seemed to spawn various others as offshoots, and discussion of
these proposals is ongoing. Read the python-dev mailing list archives
for more details:

http://mail.python.org/pipermail/python-dev/

The exception handling thread made me look a bit at how PythonCAD uses
try/finally and try/except blocks, and while doing this I began looking
at the code for sending messages between the various entities. Here's
some sample code to demonstrate how things currently work - the
following is a shortened version of the setThickness() method
in the 'graphicobject.py' file:

setThickness(self, t):
	_ot = self.__thickness
	if abs(_ot - t) > 1e-10:
		self.__thickness = t
		self.sendMessage('thickness_changed', _ot)
		self.modified() # calls self.sendMessage('modified')

So, if the abs() test succeeds the new thickness value is stored, and
then the object calls sendMessgage() and lets any object that listens
to the 'thickness_changed' message via connect() will invoke some
method, and finally the modified() method is called which will send out
the 'modified' message to objects listening for it. That is all fine and
good, but things get sticky if an exception is thrown during the
sendMessage() calls. If a exception is thrown during the first
sendMessage() call above, then modified() is never invoked, and depending
on when the exception is raised some objects listening for 'thickness_changed'
messages may not have the appropriate bound method invoked. If the
exception is raised during the sending of the 'modified' message, then
the problem is just that potentially one or more objects may not get
the notification that the entity thickness has changed.

A possible solution is to use try/finally:

setThickness(self, t):
	_ot = self.__thickness
	if abs(_ot - t) > 1e-10:
		self.__thickness = _t
		try:
			self.sendMessage('thickness_changed', _ot)
		finally:
			self.modified()

Now, the modified() method is guaranteed to be invoked whether or not
an exception was raised during the sending of the 'thickness_changed'
message. Two potential problems arise when doing this, however. First,
there are an unknown number of objects that did not receive the
'thickness_changed' message, and second any exceptions thrown during
the invocation of self.modified() would wipe out the original exception
and replace it with the new one, making debugging more difficult.

A second possible solution is the following:

setThickness(self, t):
	_ot = self.__thickness
	if abs(_ot - t) > 1e-10:
		self.__thickness = _t
		try:
			self.sendMessage('thickness_changed', _ot)
		except:
			raise
		else:
			self.modified()

Here, self.modified() will only be called if the 'thickness_changed'
message was sent successfully, which is what should happen. Any
exception occuring during the self.modified() call would not be handled,
so some number of entities may not be notified of the change, though.
The problems of an exception occurring during the self.modified() call
are also still present.

A third possibility is this:

setThickness(self, t):
	_ot = self.__thickness
	if abs(_ot - t) > 1e-10:
		self.__thickness = _t
		try:
			self.sendMessage('thickness_changed', _ot)
		except:
			pass
		try:
			self.modified()
		except:
			pass

Any errors are ignored, so for better or worse this method finishes but
possibly leaves wreckage behind tripping things up later.

I want to ensure in the sample code above that the 'thickness_changed'
message is sent to all listeners successfully, and also that the
'modified' message is sent to all listeners successfully, and should
something go wrong midway either backout the change or, even better,
inform any listeners that had received messages about the change
that something went wrong and the change failed. I played around with
other try/finally, try/except, and try/except/else blocks, and I
could never hit on a nice, clean solution to this objective. The code
grew more cumbersome, less clear, and those results indicated I
was working in the wrong direction.

It seems like this type of problem is something that anyone programming
in a language which uses exceptions, like C++ or Java, would encounter.
As such, there are probably a variety of strategies that people have
used to deal with exception handling during looping constructs, and
means of reverting things should a problem occur prior to the desired
end point. If we use database terminology, I'd like something like
the following:

setThickness(self, t):
	_ot = self.__thickness
	if abs(_ot - t) > 1e-10:
		self.__thickness = _t
		try:
			self.sendMessage('thickness_changed', _ot)
			self.modified()
		except:
			self.rollback()
		else:
			self.commit()

The trick is writing the rollback() and commit() routines, as well as
constructing a framework where such routines could be made for all
the various attribute setting methods in PythonCAD.

Writing this note has helped me think about things a little, but I still
don't have a good idea as how to solve this problem. Any comments people
care to add about an approach to accomplish rollback/commit type behavior
would be welcomed.

Art
-- 
Man once surrendering his reason, has no remaining guard against absurdities
the most monstrous, and like a ship without rudder, is the sport of every wind.

-Thomas Jefferson to James Smith, 1822


More information about the PythonCAD mailing list