duck-type-checking?

Joe Strout joe at strout.net
Fri Nov 14 16:49:00 EST 2008


On Nov 14, 2008, at 2:07 PM, Paul McGuire wrote:

> Or to be even more thorough:
> def sub(x: must have getitem, y: must have strip and strip must be
> callable, and y.strip must return something that has replace and
> replace must be callable)
>
> So even this simple example gets nasty in a hurry, let alone the OP's
> case where he stuffs y into a list in order to access it much later,
> in a completely different chunk of code, only to find out that y
> doesn't support the complete string interface as he expected.

Very true.  That's why I think it's not worth trying to be too pure  
about it.  Most of the time, if a method wants a Duck, you're going to  
just give it a Duck.

However, I would like to also handle the occasional case where I can't  
give it a Duck, but I can give it something that is a drop-in  
substitute for a Duck (really, truly, I promise, and if it blows up  
I'll take responsibility for it).

A real-world example from another language (sorry for that, I've been  
away from Python for ten years): in REALbasic, there is a Database  
base class, and a subclass for each particular database backend  
(Postgres, MySQL, whatever).  This works fine most of the time, in  
that you can write general code that takes a Database object and Does  
Stuff with it.

However, all of those database backends are shipped by the vendor, or  
by plugin authors -- you can't create a useful Database subclass  
yourself, in RB code, because it has a private constructor.  So you  
end up making your own database class, but that can't be used with all  
the code that expects a real Database object.

Of course, the framework design there is seriously flawed (Database  
should have been an interface, or at the very least, had a protected  
rather than private constructor).  And in Python, there's no way to  
prevent subclassing AFAIK, so this particular issue wouldn't come up.   
But I still suspect that there may be times when I don't want to  
subclass for some reason (maybe I'm using the Decorator or Adapter or  
Bridge pattern).  Yet I'm willing to guarantee that I've adhered to  
the interface of another class, and will behave like it in any way  
that matters.

So, the level of assertion that I want to make in a method that  
expects a Duck is just that its parameter is either a Duck, or  
something that the caller is claiming is just as good as a Duck.  I'm  
not trying to prevent any possible error; I'm trying to catch the  
stupid errors where I inadvertently pass in something completely  
different, not duck-like at all (probably because some other method  
gave me a result I didn't realize it could produce).

So things like this should suffice:

	# simple element
	assert(is_stringlike(foo))
	assert(is_numeric(foo))
	assert(is_like(foo, Duck))

	# sequence of elements
	assert(seqof_stringlike(foo))
	assert(seqof_numeric(foo))
	assert(seqof_like(foo, Duck))
	# (also "listof_" variants for asserting mutable sequence of whatever)

	# dictionary of elements
	assert(dictof_like(foo, str, int))

Hmm, I was already forced to change my approach by the time I got to  
checking dictionaries.  Perhaps a better formalism would be a "like"  
method that takes an argument, and something that indicates the  
desired type.  This could be a tree if you want to check deeper into a  
container.  Maybe something like:

	assert(fits(foo, dictlike(strlike, seqlike(intlike))))

which asserts that foo is something dictionary-like that maps string- 
like things to something like a sequence of integer-like things.  Most  
cases would not be this complex, of course, but would be closer to

	assert(fits(foo, strlike))

But this is still pretty ugly.  Hmm.  Maybe I'd better wait for  
ABCs.  :)

Cheers,
- Joe




More information about the Python-list mailing list