Python functions are naturally polymorphic on their arguments, and checking argument types loses polymorphism -- but we may still get early checks and some extra safety without any real cost.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# simplest, no-checks code for sample manipulations of a list # (but if alist has, e.g., .append and not .extend, *it will # be partially altered* before an exception is raised...) def munge1(alist): alist.append(23) alist.extend(range(5)) alist.append(42) alist = alist alist.extend(range(2)) # naive "look before you leap": safer, but loses polymorphism def munge2(alist): if type(alist)==type(): munge1(alist) # accurate "look before you leap": safer AND fully polymorphic def munge3(alist): # first, we extract all bound-methods we'll need append = alist.append extend = alist.extend # then, we check operations, such as indexing try: a=a except IndexError: pass # empty alist's OK # and finally, we operate -- no exceptions expected append(23) extend(range(5)) append(42) alist = alist extend(range(2))
"It's easier to ask forgiveness than permission" (just try operating, handle any resulting exception) is the normal Pythonic way of life, and mostly works great -- explicit checking of types severely restricts polymorphism. But, if we need to do several operations on an object, then just trying to do them all risks SOME of them succeeding (and altering the object) before an exception is later raised -- e.g., suppose that munge1, above, is passed an argument that does have an .append method but not .extend; sometimes we want the operation sequence to be "atomic" -- either all of them happen, or none does. We can get closer to that by switching to "look before you leap", but in an accurate, careful way: typically, we extract all bound-methods we'll need, then, we non-invasively test _operations_ we'll need (such as, here, indexing on both sides of the assignment operator) -- only if all of this succeeds do we move on to the part where we actually change object state, and, there, it's less likely (although of course NOT impossible) that exceptions will occur. The extra complication is pretty modest, and the slow-down 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 typical). [It's important to avoid overdoing the checks, but assert can help with that, e.g., one may add "assert callable(append)" etc to munge3].