Python design - initializing, setting, and getting class attributes

487 views Asked by At

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.

2

There are 2 answers

4
Tagc On

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.

4
pragman On

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