Credit: Alex Martelli
You need to check if an object has certain necessary attributes, before performing state-altering operations, but you want to avoid type-testing because you know it reduces polymorphism.
In Python, you normally try whatever operations you need to perform. For example, here’s the simplest, no-checks code for manipulations of a list:
def munge1(alist): alist.append(23) alist.extend(range(5)) alist.append(42) alist = alist alist.extend(range(2))
While this is usually adequate, there may be occasional problems. For
example, if the
alist object has an
append method but not an
munge1 function will partially alter
alist before an exception is raised. Such partial
alterations are generally not cleanly undoable, and, depending on
your application, they can be quite a bother.
To avoid partial alteration, you might want to check the type. A naive Look Before You Leap (LBYL) approach looks safer, but it has a serious defect: it loses polymorphism. The worst approach of all is checking for equality of types:
def munge2(alist): if type(alist)==type(): munge1(alist) else: raise TypeError, "expected list, got %s"%type(alist)
def munge3(alist): if isinstance(alist, type): munge1(alist) else: raise TypeError, "expected list, got %s"%type(alist)
The proper solution is accurate LBYL, which is safer and fully polymorphic:
def munge4(alist): # Extract all bound methods you need (immediate exception # if any needed method is missing) append = alist.append extend = alist.extend # Check operations, such as indexing, to raise # exceptions ASAP if signature compatibility is missing try: a=a except IndexError: pass # An empty alist is okay # Operate -- no exceptions expected at this point append(23) extend(range(5)) append(42) alist = alist extend(range(2))
Python functions are naturally polymorphic on their arguments, and checking argument types loses polymorphism. However, we may still get early checks and some extra safety without any substantial cost.
The Easier to Ask Forgiveness than Permission (EAFP) approach, in which we try operations and handle any resulting exceptions, is the normal Pythonic way of life and usually works great. Explicit checking of types severely restricts Python’s normal signature-based polymorphism and should be avoided in most cases. However, if we need to perform several operations on an object, trying to do them all could result in some of them succeeding and partially altering the object before an exception is raised.
For example, suppose that
munge1, in the
recipe’s code, is called with an actual argument
alist that has an
append method but lacks
In this case,
alist will be altered by the first
append, and the attempt to call
extend will raise an exception, leaving
alist’s state partially altered
in a way that may be hard to recover from. Sometimes, a sequence of
operations should be atomic: either all of the alterations happen or
none of them do.
We can get closer to that by switching to LBYL, but in an accurate, careful way. Typically, we extract all bound methods we’ll need, then noninvasively test the necessary operations (such as indexing on both sides of the assignment operator). We move on to actually changing the object state only if all of this succeeds. From there, it’s far less likely (though not impossible) that exceptions will occur in midstream, with state partially altered.
This extra complication is pretty modest, and the slowdown due to the
checks is typically more or less compensated by the extra speed of
using bound methods versus explicit attribute access (at least if the
operations include loops, which is often the case).
It’s important to avoid overdoing the checks, and
assert can help with that. For example, you can
assert callable(append) to
munge4( ). In this case, the compiler will remove the
assert entirely when the program is run with
optimization (i.e., with flags
-OO), while performing the checks when the program
is run for testing and debugging (i.e., without the optimization