duck-type-checking?

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Thu Nov 13 22:55:10 EST 2008


On Thu, 13 Nov 2008 13:11:12 -0800, George Sakkis wrote:

> On Nov 13, 10:15 am, Joe Strout <j... at strout.net> wrote:
>> On Nov 12, 2008, at 7:32 PM, Steven D'Aprano wrote:
>>
>> > While the recipe is great, it can be tiresome to apply all the time.
>> > I would factor out the checks into a function, something like this:
>>
>> > def isstringlike(obj, methods=None):
>> >    """Return True if obj is sufficiently string-like.""" if
>> >    isinstance(obj, basestring):
>> >        return True
>> >    if methods is None:
>> >        methods = ['upper', 'lower', '__len__', '__getitem__']
>> >    for method in methods:
>> >        if not hasattr(obj, method):
>> >            return False
>> >    # To really be string-like, the following test should pass. if
>> >    len(obj) > 0:
>> >        s = obj[0]
>> >        if s[0] != s:
>> >            return False
>> >    return True
>>
>> Thanks for this, too; that's the sort of method I had in mind.  That
>> last test for string-likeness is particularly clever.  I'll need to
>> think more deeply about the implications.
> 
> To me this seems it combines the worst of both worlds: the explicitness
> of LBYL with the heuristic nature of duck typing.. might as well call it
> "doyoufeellucky typing". If you are going to Look Before You Leap, try
> to stick to isinstance/issubclass checks (especially in 2.6+ that they
> can be overriden) instead of crafting ad- hoc rules of what makes an
> object be X-like.

That's crazy talk. Duck-typing is, at it's very nature, ad-hoc. You want 
something that is just duck-like enough for your application, without 
caring if it is an honest-to-goodness duck. "Duck-like" depends on the 
specific application, in fact the specific *function*. You can't get any 
more ad-hoc than that.

What I posted, taken from Alex Martelli, is duck-typing. It's just that 
the duck-type checks are performed before any other work is done. The 
isinstance check at the start of the function was merely an optimization. 
I didn't think I needed to say so explicitly, it should have been obvious.

Take this example:

def foo(alist):
    alist.sort()
    alist.append(5)


The argument can be any object with sort and append methods (assumed to 
act in place). But what happens if you pass it an object with a sort 
method but no append? The exception doesn't occur until *after* the 
object is sorted, which leaves it in an inconsistent state. This can be 
undesirable: you might need the function foo to be atomic, either the 
entire function succeeds, or none of it.

Duck-typing is great, but sometimes "if it walks like a duck and quacks 
like a duck it might as well be a duck" is not enough. Once you've built 
an expensive gold-plated duck pond, you *don't* want your "duck" to sink 
straight to the bottom of the pond and drown the first time you put it on 
the water. You want to find out that it can swim like a duck *before* 
building the pond.


-- 
Steven



More information about the Python-list mailing list