On Thu, 25 Jan 2007 16:54:05 +0000, Matthew Wilson wrote: > Lately, I've been writing functions like this: > > def f(a, b): > > assert a in [1, 2, 3] > assert b in [4, 5, 6] > > The point is that I'm checking the type and the values of the > parameters.
If somebody passes in a == MyNumericClass(2), which would have worked perfectly fine except for the assert, your code needlessly raises an exception. Actually that's a bad example, because surely MyNumericClass(2) would test equal to int(2) in order to be meaningful. So, arguably, testing that values fall within an appropriate range is not necessarily a bad idea, but type-testing is generally bad. Note also that for real code, a bare assert like that is uselessly uninformative: >>> x = 1 >>> assert x == 3 Traceback (most recent call last): File "<stdin>", line 1, in ? AssertionError This is better: >>> assert x == 3, "x must be equal to three but is %s instead" % x Traceback (most recent call last): File "<stdin>", line 1, in ? AssertionError: x must be equal to three but is 1 instead This is even better still: >>> if x != 3: ... raise ValueError("x must be equal to three but is %s instead" % x) ... Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: x must be equal to three but is 1 instead And even better still is to move that test out of your code and put it into unit tests (if possible). > I'm curious how this does or doesn't fit into python's duck-typing > philosophy. Doesn't fit, although range testing is probably okay. > I find that when I detect invalid parameters overtly, I spend less time > debugging. Yes, probably. But you end up with less useful code: def double(x): """Return x doubled.""" assert x == 2.0 and type(x) == float return 2*x Now I only need to test one case, x == 2.0. See how much testing I don't have to do? *wink* There's a serious point behind the joke. The less your function does, the more constrained it is, the less testing you have to do -- but the less useful it is, and the more work you put onto the users of your function. Instead of saying something like a = MyNumericClass(1) b = MyNumericClass(6) # more code in here... # ... result = f(a, b) you force them to do this: a = MyNumericClass(1) b = MyNumericClass(6) # more code in here... # ... # type-cast a and b to keep your function happy result = f(int(a), int(b)) # and type-cast the result to what I want result = MyNumericClass(result) And that's assuming that they can even do that sort of type-cast without losing too much information. > Are other people doing things like this? Any related commentary is > welcome. Generally speaking, type-checking is often just a way of saying "My function could work perfectly with any number of possible types, but I arbitrarily want it to only work with these few, just because." Depending on what you're trying to do, there are lots of strategies for avoiding type-tests: e.g. better to use isinstance() rather than type, because that will accept subclasses. But it doesn't accept classes that use delegation. Sometimes you might have a series of operations, and you want the lot to either succeed or fail up front, and not fail halfway through (say, you're modifying a list and don't want to make half the changes needed). The solution to that is to check that your input object has all the methods you need: def f(s): """Do something with a string-like object.""" try: upper = s.upper split = s.split except AttributeError: raise TypeError('input is not sufficiently string-like') return upper() Good unit tests will catch anything type and range tests will catch, plus a whole lot of other errors, while type-testing and range-testing will only catch a small percentage of bugs. So if you're relying on type- and range-testing, you're probably not doing enough testing. -- Steven. -- http://mail.python.org/mailman/listinfo/python-list