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

Steven D'Aprano steve at pearwood.info
Tue Aug 24 13:51:21 CEST 2010


On Tue, 24 Aug 2010 11:09:10 am Guido van Rossum wrote:
> On Mon, Aug 23, 2010 at 4:56 PM, Steven D'Aprano <steve at pearwood.info> 
wrote:
[...]
> > I have always thought that hasattr() does what it says on the box:
> > it tests for the *existence* of an attribute, that is, one that
> > statically exists rather than being dynamically generated. In other
> > words, it is a key in the instance __dict__ or is inherited from
> > the class __dict__ or that of a superclass, or a __slot__.
>
> It tests for the existence of an attribute -- how the attribute is
> defined should not have to occur to you 

But that's the thing... as far as I am concerned, a dynamically defined 
attribute *doesn't* exist. If it existed, __getattr__ would never be 
called. A minor semantic difference, to be sure, but it's real to me.

Whether I should care about the difference is a separate issue.

This conversation has been valuable to me for one thing though... it 
reminded me of a piece of code I had written a long time ago. A 
simplified version:

class K(object):
    def __getattribute__(self, name):
        if hasattr(self, name):
            # no computation needed
            print "Attr exists"
        else:
            # compute something...
            print "Attr doesn't exist"

I couldn't work out why it was behaving so strangely, and rather than 
spend time investigating, I abandoned the whole dynamic attribute 
approach and did something completely different. So at least now I know 
why it wasn't working as I expected.


> (and there are lots of other 
> ways for attributes to be defined besides the ways you came up with
> just now).

I never suggested that it would be easy.


> > Now that I know that hasattr doesn't do what I thought it does or
> > what the name implies it does, it has little or no utility for me.
> > In the future, I'll just write a try...except block and catch
> > errors if the attribute doesn't exist.
>
> The try/except block *also* requires you to break your train of
> thought. And most of the time the error case just isn't important.
> You sound like you are over-engineering it and focusing too  much on
> performance instead of on getting things done.

Performance could be an issue, of course, if somebody writes an 
expensive __getattr__ or property. Computed attributes should be cheap. 
But I'm actually more concerned about side-effects than performance. If 
I'm not worried about potential side-effects, I use a try...except 
block. If I am worried, I "Look Before You Leap". Only now I've learned 
that what I thought was LBYL is nothing of the sort, and hasattr gives 
me no protection against side-effects. That being the case, I might as 
well just stick to try...except and be done with it.

I'm not suggesting this is the One True Way. If others prefer hasattr, 
then I have no problem with that. I'm just saying that now that I know 
what it actually does, its value *for me* is minimal.


> Like those people who 
> learn that it saves an usec to copy a built-in function into a defalt
> arg (def foo(arg1, len=len):  ...) and then overuse the trick even
> when the time it took them to write the exra line is more than the
> time they'll save in a lifetime in execution time.

Aha! The penny drops! Is that why so many methods in random.Random have 
an "int=int" argument? E.g.

    def randrange(self, start, stop=None, step=1, int=int, default=None,
                  maxwidth=1L<<BPF):

I'd wondered about that.

So there you go, now I've learned two things.



-- 
Steven D'Aprano


More information about the Python-Dev mailing list