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 email@example.com
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.