On Tue, Apr 5, 2011 at 05:01, Nick Coghlan <ncoghlan@gmail.com> wrote:
On Tue, Apr 5, 2011 at 9:46 AM, Brett Cannon <brett@python.org> wrote:
>     try:
>         c_heapq.heappop(Spam())
>     except TypeError:
>         # "heap argument must be a list"
>         pass
>
>     try:
>         py_heapq.heappop(Spam())
>     except AttributeError:
>         # "'Foo' object has no attribute 'pop'"
>         pass
>
> This kind of divergence is a problem for users as they unwittingly
> write code that is CPython-specific. This is also an issue for other
> VM teams as they have to deal with bug reports from users thinking
> that they incorrectly implemented the module when in fact it was
> caused by an untested case.

While I agree with the PEP in principle, I disagree with the way this
example is written. Guido has stated in the past that code simply
*cannot* rely on TypeError being consistently thrown instead of
AttributeError (or vice-versa) when it comes to duck-typing. Code that
cares which of the two is thrown is wrong.

Which is unfortunate since least common base class is Exception. But I can add a note to the PEP saying that this is the case and change the example.
 

However, there actually *is* a significant semantic discrepancy in the
heapq case, which is that py_heapq is duck-typed, while c_heapq is
not:

>>> from test.support import import_fresh_module
>>> c_heapq = import_fresh_module('heapq', fresh=['_heapq'])
>>> py_heapq = import_fresh_module('heapq', blocked=['_heapq'])
>>> from collections import UserList
>>> class Seq(UserList): pass
...
>>> c_heapq.heappop(UserList())
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: heap argument must be a list
>>> py_heapq.heappop(UserList())
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/home/ncoghlan/devel/py3k/Lib/heapq.py", line 140, in heappop
   lastelt = heap.pop()    # raises appropriate IndexError if heap is empty
 File "/home/ncoghlan/devel/py3k/Lib/collections/__init__.py", line 848, in pop
   def pop(self, i=-1): return self.data.pop(i)
IndexError: pop from empty list

Cheers,
Nick.

P.S. The reason I was bugging Guido to answer the TypeError vs
AttributeError question in the first place was to find out whether or
not I needed to get rid of the following gross inconsistency in the
behaviour of the with statement relative to other language constructs:

>>> 1()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> with 1: pass
...
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__exit__'



Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia