[Python-Dev] Re: [Python-checkins] CVS: python/dist/src/Include rangeobject.h,2.16,2.17

Tim Peters tim.one@home.com
Sun, 8 Jul 2001 19:44:22 -0400

[Greg Ward]
> However, I think that AttributeError is pretty aptly used for the most
> part, and I don't see a great benefit in changing an incorrect
> "thing.property" to raise TypeError.

"The problem" comes up again and again, and in every release (this isn't
something new!), in the specific context of instance objects.  Like what do
you do for

    class C:

    c = C()
    print len(c)

?  Instance objects have *every* interesting tp_xxx slot filled in "just in
case", so the len() implementation code that first checks for the existence
of tp_as_sequence or tp_as_mapping says "NO!" to len(3) but "YES!" to
len(c).  The result is that len(3) produces

    TypeError: len() of unsized object

but len(c) eventually gets around to raising the superficially different

    AttributeError: C instance has no attribute '__len__'

instead.  So in cases like this TypeError really means "and it's damned
obvious", while AttributeError means "but it might have been otherwise had
you defined your class differently, but you didn't, and I'm not exactly sure
*why* someone is asking me for "__len__", so the safest thing to say is that
I don't have such an attribute".

Then the problem is that "just try it and see whether it works" code gets
written based on trying an example in a shell, to see whether AttributeError
or TypeError gets raised in the specific case the author is worried about,
and in a later release it raises the other one instead.

As an old-time Pythoneer, I took the almost total lack of "which exceptions
get raised when, exactly" docs as a warning that this stuff was *expected*
to change frequently, so I've always written "try it and see" code via

    except (TypeError, AttributeError):

That almost never "breaks" across releases.

Here's a specific 2.1 vs 2.2 example:

>>> for i in C(): pass # 2.1
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: C instance has no attribute '__getitem__'

>>> for i in C(): pass # 2.2a0
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: iter() of non-sequence

Since for-loops no longer require __getitem__ at all, even if we *could*
raise the same error in 2.2, it wouldn't make *sense* in 2.2.

In every case I've seen, a switch from AttributeError to TypeError makes
better sense in the end.  Can break code, though!