duck typing assertþ

  • Thread starter Andriy Kornatskyy
  • Start date
A

Andriy Kornatskyy

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)
 
The post below shows how programmer can assert duck typing between two Python classes:
 
http://mindref.blogspot.com/2012/11/python-duck-typing-assert.html
 
Comments or suggestions are welcome.
 
Thanks.
 
Andriy Kornatskyy
 
S

Steven D'Aprano

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.

The post below shows how programmer can assert duck typing between two
Python classes:
 
http://mindref.blogspot.com/2012/11/python-duck-typing-assert.html

I don't understand the emphasis on assert for this code. It is enough
that looks() return a flag. The caller can then use that as an assertion:

assert looks(Spam).like(Ham)

or as a conditional:

if looks(food).like(Meat):
...
else:
...


Assertions are only one use for this check, and in my opinion, the least
useful one.

And why the warnings? In my opinion, using the warning mechanism as a way
to communicate the differences between the classes is an abuse of
warnings: they're not *warnings*, they are *diagnostic information*.

It is also fragile: the caller may have filtered warnings, and will not
see the messages you generate.

Lastly, I do not understand the purpose of this "wheezy.core" package.
Does it have something to do with Ubuntu Wheezy? The documentation is
unclear -- it refers to it as a "Python package" that "provides core
features", but doesn't say what the purpose of the package is: core
features of *what*? And the examples.rst file doesn't show any examples.

https://bitbucket.org/akorn/wheezy.core/src/ca5b902e9605/doc/examples.rst
 
I

Ian Kelly

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? Of course, given the naming convention and the example,
it's easy to work out, but when you're trying to *write* that from
memory, it could be a nuisance to remember the proper order. This is
already a wart of isinstance and issubclass.

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

Chris Angelico

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

I'm not so sure that it is, tbh. If you read it like an English
sentence, it's clearly testing whether Foo matches the template in
IFoo, but which are you more likely to do: test one class to see if it
satisfies lots of templates, or test one template against every class
you meet? I think probably the latter is, if not more likely than the
former, at least sufficiently plausible as to create confusion. It
makes very good sense to say:

duckmatch(IFoo).compare(Foo)

ie with the arguments the other way.

ChrisA
 
A

Andriy Kornatskyy

Thank you for all comments.
It makes very good sense to say:

duckmatch(IFoo).compare(Foo)

Since we do duck match of IFoo... but there is no `duck match`, there is `duck test`. I believe instead of `compare` is more readable with `equals`. Than it is more from mathematics - precise answer... that you can not guarantee at all in dynamic programming language. So it false to use suchwording to reflect this check. We can only make an assumption that one looks like the other (similar)... with some limitation of cause... understanding what is `duck test`.

http://en.wikipedia.org/wiki/Duck_test

The intent is to make such language `construct` so it reads as English sentence that make sense, and not mandatory `pythonic` way (readability counts, java smokes aside).

is_similar(Foo).to(IFoo) # <= but we lost `duck test` sense here?

Words `looks` and `like` are coming from duck test and point also direction:

# 1
looks(Foo).like(IFoo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar'])

English sentence equivalent: if functions in Foo looks like one in IFoo than, probably, IFoo can be replaced with Foo; notice to check __len__, it is safe to ignore function `foo` and arguments passed to `bar`.

# 2
looks(Foo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar']).like(IFoo)

English sentence equivalent: while looking at Foo notice to check `__len__`, it is safe to ignore function `foo` and arguments passed to `bar`, than probably it like IFoo.

I think #1 is easier to understand once it is written. Thoughts?

Also construction looks(Foo).like(IFoo) points direction of check. It youneed the two be replaceable you need two asserts:

assert looks(Foo).like(IFoo)
assert looks(IFoo).like(Foo)

Thanks.

Andriy Kornatskyy


----------------------------------------
 
S

Steven D'Aprano

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.
 
A

Andriy Kornatskyy

1. In looks-like we check features of Foo (that may be superset) of what IFoo offers.

assert looks(Foo).like(IFoo)

2. We can check if Foo is limited to IFoo only:

assert looks(IFoo).like(Foo)

So it valid to have both asserts.

Thanks.

Andriy Kornatskyy


----------------------------------------
From: (e-mail address removed)
Subject: Re: duck typing assert
Date: Fri, 9 Nov 2012 13:36:39 +0000
To: (e-mail address removed)

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.
 
C

Chris Angelico

1. In looks-like we check features of Foo (that may be superset) of what IFoo offers.

assert looks(Foo).like(IFoo)

2. We can check if Foo is limited to IFoo only:

assert looks(IFoo).like(Foo)

So it valid to have both asserts.

You'll almost never need #2, but since there's no difference between a
"class" and an "interface", it's perfectly legal to switch them
around.

But I would generally expect that unrecognized methods are never a
problem (assuming they don't collide with anything) - that, as in
Steven's example, it's fine to have an actor who can sing when you
don't need her to. When you post job openings, you don't normally ask
for someone with "5+ years Python experience and unable to program in
REXX" [1]. You're checking for a minimum set of requirements.

[1] Though I suppose you might ask for someone who's unable to program
in Pascal. Might save you some hassle.

ChrisA
 
A

Andriy Kornatskyy

There is sense for #2. Let me explain. There is basic IFoo implementation and improved Foo. While I switched to Foo, I still want to be as close to IFoo as possible, since there might be sense to switch to Foo2 later, which conform to IFoo.

Here is the problem: if I will not assert #2 potentially I will use some `features` that are not in IFoo, thus that breaks my code switching to Foo2later.

That might not apply for 100% usability cases, just wanted to point that out as reasonable thing.

Thanks.

Andriy Kornatskyy


----------------------------------------
Date: Sat, 10 Nov 2012 01:15:36 +1100
Subject: Re: duck typing assert
From: (e-mail address removed)
To: (e-mail address removed)

1. In looks-like we check features of Foo (that may be superset) of what IFoo offers.

assert looks(Foo).like(IFoo)

2. We can check if Foo is limited to IFoo only:

assert looks(IFoo).like(Foo)

So it valid to have both asserts.

You'll almost never need #2, but since there's no difference between a
"class" and an "interface", it's perfectly legal to switch them
around.

But I would generally expect that unrecognized methods are never a
problem (assuming they don't collide with anything) - that, as in
Steven's example, it's fine to have an actor who can sing when you
don't need her to. When you post job openings, you don't normally ask
for someone with "5+ years Python experience and unable to program in
REXX" [1]. You're checking for a minimum set of requirements.

[1] Though I suppose you might ask for someone who's unable to program
in Pascal. Might save you some hassle.

ChrisA
 
P

Prasad, Ramit

Andriy said:
Thank you for all comments.

It makes very good sense to say:

duckmatch(IFoo).compare(Foo)

Since we do duck match of IFoo...but there is no `duck match`, there is `duck test`. I believe instead of
`compare` is more readable with `equals`. Than it is more from mathematics - precise answer... that you can not
guarantee at all in dynamic programming language. So it false to use such wordingto reflect this check. We can
only make an assumption that one looks like the other (similar)... with some limitation of cause...
understanding what is `duck test`.

http://en.wikipedia.org/wiki/Duck_test

The intent is to make such language `construct` so it reads as English sentence that make sense, and not
mandatory `pythonic` way (readability counts, java smokes aside).

is_similar(Foo).to(IFoo) # <= but we lost `duck test` sense here?

Words `looks` and `like` are coming from duck test and point also direction:

# 1
looks(Foo).like(IFoo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar'])

English sentence equivalent: if functions in Foo looks like one in IFoo than, probably, IFoo can be replaced
with Foo; notice to check __len__, it is safe to ignore function `foo` and arguments passed to `bar`.

# 2
looks(Foo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar']).like(IFoo)

English sentence equivalent: while looking at Foo notice to check `__len__`, it is safe to ignore function `foo`
and arguments passed to `bar`, than probably it like IFoo.

What about?

duck(Foo).equivalent_to(IFoo, <kwargs>)
duck(Foo).matches(IFoo, <kwargs>)

This email is confidential and subject to important disclaimers and
conditions including on offers for the purchase or sale of
securities, accuracy and completeness of information, viruses,
confidentiality, legal privilege, and legal entity disclaimers,
available at http://www.jpmorgan.com/pages/disclosures/email.
 
A

Andriy Kornatskyy

duck(Foo).match(IFoo, <kwargs>)
duck(Foo).like(IFoo, <kwargs>)

Hm... function name in most cases is read as verb... this may cause confusion:
duck => synonyms => immerse, dip

Thanks.

Andriy Kornatskyy

----------------------------------------
From: (e-mail address removed)
To: (e-mail address removed); (e-mail address removed)
Subject: RE: duck typing assert
Date: Fri, 9 Nov 2012 17:37:29 +0000

Andriy said:
Thank you for all comments.
It makes very good sense to say:

duckmatch(IFoo).compare(Foo)

Since we do duck match of IFoo... but there is no `duck match`, thereis `duck test`. I believe instead of
`compare` is more readable with `equals`. Than it is more from mathematics - precise answer... that you can not
guarantee at all in dynamic programming language. So it false to use such wording to reflect this check. We can
only make an assumption that one looks like the other (similar)... withsome limitation of cause...
understanding what is `duck test`.

http://en.wikipedia.org/wiki/Duck_test

The intent is to make such language `construct` so it reads as English sentence that make sense, and not
mandatory `pythonic` way (readability counts, java smokes aside).

is_similar(Foo).to(IFoo) # <= but we lost `duck test` sense here?

Words `looks` and `like` are coming from duck test and point also direction:

# 1
looks(Foo).like(IFoo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar'])

English sentence equivalent: if functions in Foo looks like one in IFoothan, probably, IFoo can be replaced
with Foo; notice to check __len__, it is safe to ignore function `foo` and arguments passed to `bar`.

# 2
looks(Foo, notice=['__len__'], ignore_funcs=['foo'], ignore_argspec['bar']).like(IFoo)

English sentence equivalent: while looking at Foo notice to check `__len__`, it is safe to ignore function `foo`
and arguments passed to `bar`, than probably it like IFoo.

What about?

duck(Foo).equivalent_to(IFoo, <kwargs>)
duck(Foo).matches(IFoo, <kwargs>)

This email is confidential and subject to important disclaimers and
conditions including on offers for the purchase or sale of
securities, accuracy and completeness of information, viruses,
confidentiality, legal privilege, and legal entity disclaimers,
available at http://www.jpmorgan.com/pages/disclosures/email.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top