Decorator for Enforcing Argument Types

George Sakkis george.sakkis at gmail.com
Fri Dec 22 12:45:17 EST 2006


Duncan Booth wrote:

> "John Machin" <sjmachin at lexicon.net> wrote:
>
> >> > if isinstance(....
> >> >     action_for_type1(...
> >> > # big snip
> >> > elif isinstance(...
> >> >     action_typeN( ...
> >> > # no else statement
> >>
> >> Ouch.. someone must have skipped his/her OO class...
> >
> > Quite possibly :-) but that's not the problem here.
> >
> > The method in question might be called say emit_generic(self,
> > any_type_of obj) and so one bunch of isinstance calls is actually
> > needed, but not two bunches. So: lose the decorator and add an else and
> > a raise at the end.
> >
> > There is a secondary problem: annoying the crap out of callers who
> > often know what type they have and want an emit_real which would take
> > an int, a long, or a float and an emit_strg and ... (yes, almost all
> > the possible types are that simple) which wouldn't go through even one
> > chain of isinstance calls.
> >
>
> I think the point that was being made was that the method's body should be
> something like:
>
>    actions[type(arg)](...)
>
> which not only avoids all of the isinstance calls but also the else and the
> raise at the end.

Actually my first thought was that the objects being typechecked belong
(or could be refactored so that they belong) in a hierarchy so that
each action_type_i() could be just an action() method of a subclass
(I've actually seen C++ code - apparently from [ex-]C programmers -
that uses a private 'kind' field and then builds long switch statements
based on 'kind' instead of proper subclassing).

But you're right, if the objects in question don't have (and cannot or
should not grow) an action() method (e.g. builtins), a dict dispatch
based on type is a reasonable choice, at least if you don't care about
handling automatically the derived classes. Otherwise you have to
simulate the mro(), using something like:

class TypeDispatcher(dict):

    def __call__(self, obj, *args, **kwds):
        try: # handle both old and new style classes
             objtype = obj.__class__
        except AttributeError:
             objtype = type(obj)
        for t in itermro(objtype):
            if t in self:
                return self[t](obj, *args, **kwds)
        raise TypeError('No handler for %r' % objtype.__name__)


def itermro(cls):
    from collections import deque
    visited = set()
    queue = deque([cls])
    while queue:
        cls = queue.popleft()
        yield cls
        for cls in cls.__bases__:
            if cls not in visited:
                visited.add(cls)
                queue.append(cls)


if __name__ == '__main__':
    class A(object):
        def foo(self, *args, **kwds):
            return "A.foo dispatcher(%s,%s)" % (args,kwds)

    class B(A):
        def foo(self, *args, **kwds):
            return "B.foo dispatcher(%s,%s)" % (args,kwds)

    d = TypeDispatcher()
    d[A] = lambda self,*args,**kwds: self.foo(*args,**kwds)
    d[int] = lambda self,*args,**kwds: "int dispatcher(%s, %s, %s)" %
(self, args, kwds)

    for obj in A(), B(), 3:
        print d(obj, "hello", "world", x=3, y=3.5)


George




More information about the Python-list mailing list