Monday, April 2, 2007

Python: Class objects as signal arguments, redux

Just the other day, I mentioned that I'm using class objects as signal arguments to functions/methods in some code I'm working on. Not entirely unlike perl, it seems that, increasingly, There Is More Than One Way To Do It in python so JJ's comment that he uses the same technique is very reassuring.

However, one thing still doesn't sit right with me regarding this technique: a novice programmer may see "class" and think they need to instantiate it and pass the instance as the function/method argument. Using my previous example:

ApplyFlavor("blueberry", CurrentWidget)
Class object passed as argument; widget is CurrentWidget so code that flavors the "current widget" is executed.

ApplyFlavor("blueberry", CurrentWidget())
Caller mistakenly passes instance as argument; widget is not CurrentWidget so ApplyFlavor() does not recognize the signal argument and runs code that accesses the widget object in ways that will probably throw exceptions.


This is nothing that a little documentation wouldn't fix, but I've yet to meet a junior programmer that actually reads all the documentation I wrote for them. So, being proactive, let's assume they don't read directions and catch their mistake for them:

from warnings import warn

class AllWidgets(object):
"""Placeholder for indicating action applies for all widgets
"""
def __new__(cls):
warn("%s should not be instantiated" % cls.__name__,
stacklevel=2)
return cls

....

def ApplyFlavor(flavor, widget=CurrentWidget):
if widget is CurrentWidget:
# Code that flavors the "current widget".
....
elif widget is AllWidgets:
# Code that flavors all widgets.
....
else:
# Code that flavors just the widget specified.
....

Now you cannot instantiate the class; trying to do so just returns an instance of the class object itself so AllWidgets() is AllWidgets.

If you don't mind whether users erroneously try to instantiate the class, you can omit the warning. If, like me, you believe such errors are signs of misunderstanding that could lead to further bugs, issue a warning or -- if you are really zealous -- throw an exception from the __new__() method.

No comments: