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