duck typing assert

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Nov 9 08:36:39 EST 2012


On Thu, 08 Nov 2012 18:00:58 -0700, Ian Kelly wrote:

> On Thu, Nov 8, 2012 at 4:33 PM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> On Thu, 08 Nov 2012 20:34:58 +0300, Andriy Kornatskyy wrote:
>>
>>> People who come from strongly typed languages that offer interfaces
>>> often are confused by lack of one in Python. Python, being dynamic
>>> typing programming language, follows duck typing principal. It can as
>>> simple as this:
>>>
>>> assert looks(Foo).like(IFoo)
>>
>> How very cute. And I don't mean that in a good way.
>>
>> Why is this a class with a method, instead of a function that takes two
>> class arguments (plus any optional arguments needed)?
>>
>> looks_like(Foo, IFoo)
>>
>> is less "cute", reads better to English speakers, and much more
>> Pythonic. This isn't Java, not everything needs to be a class.
> 
> I disagree.  Does that test whether Foo looks like IFoo, or IFoo looks
> like Foo? 

What's the difference? "Looks like" is a symmetric comparison, like 
"equal" and "almost equal", but not "subset", "less than" etc. If x looks 
like y, then necessarily y must look like x. If Katy Perry looks like 
Zooey Deschanel, then it stands to reason that Zooey Deschanel looks like 
Katy Perry. James Woods looks like Erwin Schroedinger, and Erwin 
Schroedinger looks like James Woods.

http://tvrefill.com/wp-content/uploads/2010/12/zooey-deschanel.jpg

http://cheezburger.com/6704400128


So in that sense, looks(Spam).like(Ham) must always be the same as 
looks(Ham).like(Spam), and the order of operators doesn't matter.

But that's not what we want! And to his credit, that's not what Andriy 
Kornatskyy's code actually implements. The problem is with the name, 
which is actively misleading by suggesting a symmetrical comparison for 
one which is not symmetrical.

Suppose we want to make a movie with Zooey Deschanel, but she's not 
available, so we want a replacement who is duck-type compatible with her. 
The replacement doesn't need to be a Deschanel sister, but she does need 
to do *at least* everything Zooey can do. If she can do more, that's 
fine, but she can't do less.

Since Katy Perry can do everything Zooey can do, PLUS she sings, we can 
replace Zooey with Katy: Katy is duck-type compatible with Zooey. But we 
can't replace Katy with Zooey, because Zooey can't sing.[1]

(I think... I really don't actually know if Zooey Deschanel can sing or 
not. Just go along with the example.)

The point I am making is that "looks like" is not a good description for 
this function. "Looks like" must be symmetrical, but the function we 
actually want is not symmetrical. It is actually a "subset" type 
relationship: given "looks(Zooey).like(Katy)", it checks that the public 
methods etc. of Zooey are a subset of the methods of Katy. That is, that 
instances of Katy can be used instead of instances of Zooey.

Or is it the other way? Damned if I know. Let's find out:

class Zooey:
    def act(self): pass

class Katy:
    def act(self): pass
    def sing(self): pass


py> looks(Zooey).like(Katy)
__main__:2: UserWarning: 'sing': is missing.
False

I guessed wrong. The looks.like method as implemented tests that the 
right-hand size operand is a subset of the right-hand-side:

py> looks(Katy).like(Zooey)
True


I argue that this is the wrong way around. (Even if it isn't the wrong 
way around, it certainly isn't clear or obvious which way you have to 
write the operands!)

Consider this use-case:

candidates = [Hilary, Jennifer, Katy]
expected = looks(Zooey)  # instantiate the looks class once only
for actor in candidates:
    if expected.like(actor):
        make_movie_with(actor())


That's much nicer and more efficient than the way you have to write it 
now:

candidates = [Hilary, Jennifer, Katy]
for actor in candidates:
    # instantiate the looks class every time we want to test another class
    if looks(actor).like(Zooey):
        make_movie_with(actor())


So... it's a cute name, that sounds English-like. But it doesn't actually 
describe what the function does, it is wasteful for at least one useful 
use-case, and it's not clear which order you have to supply the two 
arguments.


> looks(Foo).like(IFoo), on the other hand, is crystal clear about which
> argument is which.

I hope that by now you can see why I say that it is as clear as mud.





[1] Some people might argue that neither can Katy Perry.


-- 
Steven



More information about the Python-list mailing list