Decorator for Enforcing Argument Types

George Sakkis george.sakkis at gmail.com
Fri Dec 22 17:05:48 EST 2006


John Machin wrote:

> Peter  Wang wrote:
> > Bruno Desthuilliers wrote:
> > > <my humble opinion>
> > > Python is dynamic, and fighting against the language is IMHO a really
> > > bad idea. The only places where theres a real need for this kind of
> > > stuff are when dealing with the "outside world" (IOW : inputs and
> > > outputs). And then packages like formencode can do much more than mere
> > > type-checking
> > > </my humble opinion>
> >
> > I don't think proper use of type checking is "fighting against the
> > language".  The goal of any language is to enable programmers to
> > express their intent in a form that executes correctly.
> >
> > Python is extremely expressive but there is a trade-off with
> > correctness - you can easily say something that you don't mean.  Unit
> > testing is sometimes sufficient, but it can never span the infinite
> > space of potential errors.  Type-checking method signatures guarantees
> > a certain amount of low-level correctness, and most type-checking
> > mechanisms also serve as documentation aids.
> >
> > I think that with a sufficiently sophisticated type checking syntax,
> > one can get the best of both worlds.  If the type checker understood
> > interfaces (like PyProtocols) and its syntax had the flexibility to
> > indicate sets of allowed arguments and aggregates of allowed
> > types/interfaces, it would cover the majority of cases without limiting
> > expressive power.
> >
> > I understand that we're all adults, but it's still nice to have the
> > computer tell us when we're being childish. :)
>
> Your comments on the following cut-down and disguised version of a
> *real-world* example would be appreciated:
>
>     @accepts(object, (int, float))
>     def tally(self, anobj):
>         self.total += anobj
>
> I assure you that the comments of a caller whose code did this:
>     fubar.tally(someobj)
> and got this:
>     AssertionError: arg 12345678901L does not match (<type 'int'>,
> <type 'float'>)
> are definitely *not* repeatable in front of the children.

I guess this is not the 'accepts' decorator of the typecheck module;
with that you'd rather write it as @accepts(Self(), Number) and prevent
this error (http://oakwinter.com/code/typecheck/tutorial/methods.html).

I have also a very recent real-world example to share, from the other
side of the fence this time. It's even worse because it's an error that
passes silently. Cut-down version follows:

@cherrypy.expose
def retrieve(self, **kwds):
    queries = kwds['q']
    rows = self._selectRows(*queries)
    # more stuff

'q' here is a multiselect field that is binded to a list of selected
strings. Or so I thought, until someone noticed bizarre results in some
cases. Turns out that if you select a single item from the select box,
'q' is binded to a string instead of a list of length 1, so instead of
retrieving 'apple', she got back the results for 'a', 'p', 'p',
'l','e'.

Bottom line, type checking is a tricky business. In some sense it's
similar to the recall/precision tradeoff in information retrieval. With
stricter type checking, you can increase the precision but may hurt the
recall, i.e. valid code is rejected, as in your example. And vice
versa, loosing up the type checks increases the recall but may hurt the
precision, as in my example.

George




More information about the Python-list mailing list