Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 31 lines
 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[4] = alist[3]
    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[0]=a[0]
    except IndexError: pass    # empty alist's OK

    # and finally, we operate -- no exceptions expected
    append(23)
    extend(range(5))
    append(42)
    alist[4] = alist[3]
    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].

3 comments

rbeer 20 years, 3 months ago  # | flag

Question. What is a bound-method?

Paddy McCarthy 14 years, 4 months ago  # | flag

Typo. The name in the try statement , should it be alist and not a?

Created by Alex Martelli on Wed, 21 Mar 2001 (PSF)
Python recipes (4591)
Alex Martelli's recipes (27)

Required Modules

  • (none specified)

Other Information and Tasks