3

I have a class in which a method first needs to verify that an attribute is present and otherwise call a function to compute it. Then, ensuring that the attribute is not None, it performs some operations with it. I can see two slightly different design choices:

class myclass():
    def __init__(self):
        self.attr = None

    def compute_attribute(self):
        self.attr = 1

    def print_attribute(self):
        if self.attr is None:
            self.compute_attribute()
        print self.attr

And

class myclass2():
    def __init__(self):
        pass

    def compute_attribute(self):
        self.attr = 1
        return self.attr

    def print_attribute(self):
        try:
            attr = self.attr
        except AttributeError:
            attr = self.compute_attribute()
        if attr is not None:
            print attr

In the first design, I need to make sure that all the class attributes are set to None in advance, which can become verbose but also clarify the structure of the object.

The second choice seems to be the more widely used one. However, for my purposes (scientific computing related to information theory) using try except blocks everywhere can be a bit of an overkill given that this class doesn't really interact with other classes, it just takes data and computes a bunch of things.

Pietro Marchesi
  • 852
  • 2
  • 8
  • 18
  • 1
    I think you want something like this: http://stackoverflow.com/questions/3012421/python-memoising-deferred-lookup-property-decorator. Getting a class to print itself isn't very pythonic; implement `__repr__` and/or `__str__` instead. – jonrsharpe Jan 07 '17 at 13:52
  • As each class object surely should have attribute `attr`, it's better to use first class design. That clarifies that what are the attributes of this class are. you can set attr as class attribute also, whenever you will access with self.attr it will swallow copy of attr and you can set/get only for perticular object too. – Roshan Jan 07 '17 at 14:01

2 Answers2

0

Firstly, you can use hasattr to check if an object has an attribute, it returns True if the attribute exists.

hasattr(object, attribute) # will return True if the object has the attribute

Secondly, You can customise attribute access in Python, you can read more about it here: https://docs.python.org/2/reference/datamodel.html#customizing-attribute-access

Basically, you override the __getattr__ method to achieve this, so something like:

class myclass2(): def init(self): pass

def compute_attr(self):
    self.attr = 1
    return self.attr

def print_attribute(self):
    print self.attr

def __getattr__(self, name):
    if hasattr(self, name) and getattr(self, name)!=None:
        return getattr(self, name):
    else:
        compute_method="compute_"+name; 
        if hasattr(self, compute_method):
            return getattr(self, compute_method)()

Make sure you only use getattr to access the attribute within __getattr__ or you'll end up with infinite recursion

pragman
  • 1,564
  • 16
  • 19
  • Doing `if hasattr(self, name) and getattr(self, name)!=None:` was my initial idea - it checks everything I need in one line, and it doesn't depend on whether I remembered to set the attribute or not, but after reading posts like [this](https://hynek.me/articles/hasattr/) I get the impression that `hasattr` is not a generally safe choice. – Pietro Marchesi Jan 07 '17 at 14:21
  • IMHO, It really depends on your system, if you depend on a lot of third party classes, then probably not a good idea, but if not, then I don't see why you can't consider it. – pragman Jan 07 '17 at 14:25
  • @PietroMarchesi FYI you'd need `getattr(self, name, None) is not None:`; by default, `getattr` throws an `AttributeError` for missing attributes and you should test for `None` by identity. – jonrsharpe Jan 07 '17 at 14:30
  • @jonrsharpe thanks for that! I didn't know it existed. – pragman Jan 07 '17 at 14:37
-1

Based on the answer jonrsharpe linked, I offer a third design choice. The idea here is that no special conditional logic is required at all either by the clients of MyClass or by code within MyClass itself. Instead, a decorator is applied to a function that does the (hypothetically expensive) computation of the property, and then that result is stored.

This means that the expensive computation is done lazily (only if a client tries to access the property) and only performed once.

def lazyprop(fn):
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazyprop(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)

    return _lazyprop


class MyClass(object):
    @lazyprop
    def attr(self):
        print('Generating attr')
        return 1

    def __repr__(self):
        return str(self.attr)


if __name__ == '__main__':
    o = MyClass()
    print(o.__dict__, end='\n\n')
    print(o, end='\n\n')
    print(o.__dict__, end='\n\n')
    print(o)

Output

{}

Generating attr
1

{'_lazy_attr': 1}

1

Edit

Application of Cyclone's answer to OP's context:

class lazy_property(object):
    '''
    meant to be used for lazy evaluation of an object attribute.
    property should represent non-mutable data, as it replaces itself.
    '''

    def __init__(self, fget):
        self.fget = fget
        self.func_name = fget.__name__

    def __get__(self, obj, cls):
        if obj is None:
            return None
        value = self.fget(obj)
        setattr(obj, self.func_name, value)
        return value


class MyClass(object):
    @lazy_property
    def attr(self):
        print('Generating attr')
        return 1

    def __repr__(self):
        return str(self.attr)


if __name__ == '__main__':
    o = MyClass()
    print(o.__dict__, end='\n\n')
    print(o, end='\n\n')
    print(o.__dict__, end='\n\n')
    print(o)

The output is identical to above.

Community
  • 1
  • 1
Tagc
  • 8,736
  • 7
  • 61
  • 114
  • 1
    This isn't *based on* so much *exactly the same as*. If you think this question is a duplicate, please flag it as such rather than copying the answer across. – jonrsharpe Jan 07 '17 at 14:15
  • @jonrsharpe I'm not confident enough to classify it as a duplicate (if it is, why did you link to the other answer instead of flagging it yourself?), but I'll leave this answer up for the time being as I think it might still help OP (I've tailored it specifically to his requirements). I'll delete it if he accepts another answer or if my answer hits -3. – Tagc Jan 07 '17 at 14:28
  • 1
    On the linked question, it also seems as though a later - yet to me more mysterious - [answer](http://stackoverflow.com/a/6849299/5429658) is preferred. – Pietro Marchesi Jan 07 '17 at 15:12
  • @PietroMarchesi Thanks for pointing that out, it's a very clever answer. I've updated my post to show how that solution can be applied to your problem as well, and it works as expected. – Tagc Jan 07 '17 at 15:22