duck-type-checking?

Joe Strout joe at strout.net
Wed Nov 12 13:22:33 EST 2008


On Nov 12, 2008, at 10:45 AM, Tim Rowe wrote:

> What do you actually mean by "Quacks like a string"? Supports the
> 'count()' method? Then you find out if it doesn't when you try to
> apply the 'count()' method. Supports some method that you don't
> actually use? Then why do you care?

Because if I write a method with the intention of treating the  
arguments like strings in various ways (slicing, combining with other  
strings, printing to stdout or writing to a file, etc. etc.), and some  
idiot (i.e. me six weeks later or long after I should have gone to  
bed) manages to accidentally pass in something else, then I want my  
program to blow up right away, not plant a roadside bomb and  
cheerfully wait for me to drive by.

This is not hypothetical -- just last week I had a hard-to-track-down  
abend that ultimately turned out to be an NLTK.Tree object stored  
someplace that I expected to only contain strings.  I found it by  
littering my code with assertions of the form  
isinstance(foo,basestring).  If I'd had those in there in the first  
place, not only documenting my assumptions but letting the computer  
check them for me, it would have saved me a lot of grief.

But in the spirit of duck-typing, I shouldn't actually check that foo  
is a basestring.  I should instead check that foo quacks like a  
basestring.  I'd define that is:

"x quacks like a basestring if it implements all the public methods of  
basestring, and can be used in pretty much any context that a  
basestring can."

I have to say "pretty much" since obviously there may be some evil  
context that actually checks isinstance.  But that's the pathological  
case, and we shouldn't let it prevent us from neatly handling the  
typical case.

> The point about duck typing is that something might quack like a duck
> but not walk like a duck -- one of those duck calls that hunters use,
> for instance. Quacking like a duck doesn't actually mean it /is/ a
> duck, it means that it will do instead of a duck if the quack is all
> you want.

Well, that's one point, but it's not the only point.  If I have code  
that expects to be working with strings, and I want to purposely give  
it something else, then it's reasonable to expect that the something- 
else will act like a string in every way that a string is likely to be  
exercised.  My string wrapper or doppleganger_string or whatever  
should implement all the methods, and support all the operators and  
type conversions, that basestring does.

> If you need to know that it walks like a duck, mates like a duck and
> tastes like a duck when roasted, you probably want it to really /be/ a
> duck and should go back to inheritance.

I can't agree; there are times when inheritance just won't do, for  
example when you don't have control over the object creation, because  
they come from some factory method you can't change.  In that case you  
may need to make a wrapper instead of a subclass, but if you've  
faithfully implemented the interface of the original class, you should  
be able to use it wherever the original class could be used (within  
reason).

So, since it's pretty clear by now that there's no standard idiom for  
this, I'll try to cook up something myself.  For classes, I think we  
could do a two-stage test:

1. If the given object isinstance of the specified class, then all is  
good and return immediately.
2. Otherwise, check each of the public attributes of the specified  
class, and make sure that the given object has corresponding callable  
attributes.

For case 2, we might be able to cache the result so that we don't do  
all that work again the next time the same type comparison is done.

Anyway, I'll evolve something in our shop here and live with it a  
while, and in a few months I'll either share what we develop for this  
purpose, or admit it was a horrible idea all along.  :)

Cheers,
- Joe




More information about the Python-list mailing list