views:

486

answers:

5

Is it possible to make a decorator that makes attributes lazy that do not eval when you try to access it with hasattr()? I worked out how to make it lazy, but hasattr() makes it evaluate prematurely. E.g.,

class lazyattribute:
    # Magic.

class A:
    @lazyattribute
    def bar(self):
      print("Computing")
      return 5

>>> a = A()
>>> print(a.bar)
'Computing'
5
>>> print(a.bar)
5
>>> b = A()
>>> hasattr(b, 'bar') 
'Computing'
5
# Wanted output: 5
A: 

The problem is that hasattr uses getattr so your attribute is always going to be evaluated when you use hasattr. If you post the code for your lazyattribute magic hopefully someone can suggest an alternative way of testing the presence of the attribute which doesn't require hasattr or getattr. See the help for hasattr:

>>> help(hasattr)
Help on built-in function hasattr in module __builtin__:

hasattr(...)
    hasattr(object, name) -> bool

    Return whether the object has an attribute with the given name.
    (This is done by calling getattr(object, name) and catching exceptions.)
mikej
+3  A: 

It may be difficult to do. From the hasattr documentation:

hasattr(object, name)

The arguments are an object and a string. The result is True if the string is the name of one of the object’s attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.)

Since attributes may be generated dynamically by a __getattr__ method, there's no other way to reliably check for their presence. For your special situation, maybe testing the dictionaries explicitly would be enough:

any('bar' in d for d in (b.__dict__, b.__class__.__dict__))
orip
A: 
Terence Honles
+1  A: 

What nobody seems to have addressed so far is that perhaps the best thing to do is not to use hasattr(). Instead, go for EAFP (Easier to Ask Forgiveness than Permission).

try:
    x = foo.bar
except AttributeError:
    # what went in your else-block
    ...
else:
    # what went in your if hasattr(foo, "bar") block
    ...

This is obviously not a drop-in replacement, and you might have to move stuff around a bit, but it's possibly the "nicest" solution (subjectively, of course).

Devin Jeanpierre
I agree that if I was refactoring I wouldn't rely on hasattr in a case like this, but I would probably prefer in any case to avoid attributes that don't necessarily exist. A dict-like object with `has_key` / `__contains__` would be nice and explicit.
orip
A: 

I'm curious why you need something like this. If hasattr ends up calling your "compute function", then so be it. Just how lazy does your property need to be anyway?

Still, here's a rather inelegeant way of doing it by examining the calling function's name. It could probably be coded a little better, but I don't think it should ever be used seriously.

import inspect

class lazyattribute(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, kls=None):
        if obj is None or inspect.stack()[1][4][0].startswith('hasattr'):
            return None
        value = self.func(obj)
        setattr(obj, self.func.__name__, value)
        return value

class Foo(object):
    @lazyattribute
    def bar(self):
        return 42
ars
I did try that, but inspecting the stack doesn't help. The stack only has a __get__ call as it's first element, etc.
Tim Dumol
Tim, this works on my end. Can you post your lazyattribute code, perhaps it's something else that's the problem?
ars