[Python-Dev] 'hasattr' is broken by design

Guido van Rossum guido at python.org
Mon Aug 23 22:03:50 CEST 2010


On Mon, Aug 23, 2010 at 12:47 PM, Raymond Hettinger
<raymond.hettinger at gmail.com> wrote:
>
> On Aug 23, 2010, at 7:22 AM, Yury Selivanov wrote:
>
>> I know the issue has been discussed several times already, however I couldn't find any reasonable explanation of its strange behaviour.  The main problem with 'hasattr' function is that is swallows all exceptions derived from Exception class.  It's a good thing that it doesn't do that with BaseException as it was fixed not a long time ago, but it's definitely not enough.
>>
>> First of all, this behaviour of 'hasattr' contradicts with the very core principle of python: "Errors should never pass silently."  And since 'hasattr' function is in builtins module and is a widely used function it impacts the whole language.
>>
>> Secondly, take a look at the following:
>>
>>>>> class Test:
>>    ...     @property
>>    ...     def attr(self):
>>    ...         self['foo']
>>    ...
>>>>> hasattr(Test(), 'attr')
>>    False
>>
>> There can be any exception instead of KeyError in the above snippet of code, but this small example shows how 'hasattr': misleadingly breaks the code logic (1) and masks bug (2).  And that's the simplest possible example, there are much more in real life.
>>
>> While (1) is maybe acceptable for someone, there is no excuse for the (2).  Moreover, current 'hasattr' behaviour tremendously complicates use of '__getattribute__' magic.  And forget about importlib magic with LazyImports, one 'hasattr' ruins everything by catching ImportError.
>>
>>
>> To conclude:
>>
>> 1) I propose to change 'hasattr' behaviour in Python 3, making it to swallow only AttributeError exceptions (exactly like 'getattr').  Probably, Python 3.2 release is our last chance.
>>
>> 2) If you afraid that this new behaviour will break too much python 2 code converted with 2to3, we can introduce another 'hasattr' function defined in 2to3 module itself, and make it imported automatically in all files passed through 2to3 transformation pipeline.  This new function will mimic 'hasattr' behaviour from python 2 and converted code should work as expected.
>
>
> Thanks for the nice analysis and good example.
>
> I disagree with the solution though.  If we want to see the exceptions associated
> with actually getting an attribute, then using getattr() instead is a perfectly
> reasonable solution that people can already use without a language change.
>
> But hasattr() has a far different set of use cases, so we should explore
> an alternate solution to the problem.  The usual reason that people use
> hasattr() instead of getattr() is that they want to check for the presence of
> of a method/attribute without actually running it, binding it, or triggering
> any other behavior.
>
> As your example shows, property() defeats this intent by actually executing
> the code.   A better behavior would not run the code at all.  It would check
> the dictionaries along the MRO but not execute any descriptors associated
> with a given key.
>
> IMO, this is a much better solution, more in line with known use cases
> for hasattr().   If the proposed change when through, it would fail to
> address the common use case and cause people to start writing their
> own versions of hasattr() that just scan but do not run code.

Hm... That sounds like scope creep to me. Properties are supposed to
be cheap and idempotent. Trying to figure out whether an attribute
exists without using __getattr__ is fraught with problems -- as soon
as a class overrides __getattr__ or __getattribute__ you're hosed
anyway.

I can vouch that the reason hasattr() catches too many exceptions is
that when I first added it (around 1990, I think :-) I wasn't very
attuned yet to the problems it could cause.

The main problem I can see with letting exceptions other than
AttributeError bubble through (besides perverted dependencies on the
current semantics) is that there are some situations where it is
pretty arbitrary whether TypeError or AttributeError is raised. I
can't recall the details, and possibly this was more of a problem with
classic classes, but I do think it warrants some research.

-- 
--Guido van Rossum (python.org/~guido)


More information about the Python-Dev mailing list