[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