views:

15

answers:

1

There's a little thing I want to do in Python, similar to the built-in property, that I'm not sure how to do.

I call this class LazilyEvaluatedConstantProperty. It is intended for properties that should be calculated only once and do not change, but they should be created lazily rather than on object creation, for performance.

Here's the usage:

class MyObject(object):

    # ... Regular definitions here

    def _get_personality(self):
        # Time consuming process that creates a personality for this object.
        print('Calculating personality...')
        time.sleep(5)
        return 'Nice person'

    personality = LazilyEvaluatedConstantProperty(_get_personality)

You can see that the usage is similar to property, except there's only a getter, and no setter or deleter.

The intention is that on the first access to my_object.personality, the _get_personality method will be called, and then the result will be cached and _get_personality will never be called again for this object.

What is my problem with implementing this? I want to do something a bit tricky to improve performance: I want that after the first access and _get_personality call, personality will become a data attribute of the object, so lookup will be faster on subsequent calls. But I don't know how it's possible since I don't have a reference to the object.

Does anyone have an idea?

A: 

I implemented it:

class LazilyEvaluatedConstantProperty(object):
    '''
    A property that is calculated (a) lazily and (b) only once for an object.

    Usage:

        class MyObject(object):

            # ... Regular definitions here

            def _get_personality(self):
                print('Calculating personality...')
                time.sleep(5) # Time consuming process that creates personality
                return 'Nice person'

            personality = LazilyEvaluatedConstantProperty(_get_personality)

    '''
    def __init__(self, getter, name=None):
        '''
        Construct the LEC-property.

        You may optionally pass in the name the this property has in the class;
        This will save a bit of processing later.
        '''
        self.getter = getter
        self.our_name = name


    def __get__(self, obj, our_type=None):

        value = self.getter(obj)

        if not self.our_name:
            if not our_type:
                our_type = type(obj)
            (self.our_name,) = (key for (key, value) in 
                                vars(our_type).iteritems()
                                if value is self)

        setattr(obj, self.our_name, value)

        return value

For the future, the maintained implementation could probably be found here:

http://github.com/cool-RR/GarlicSim/blob/development/garlicsim/garlicsim/general_misc/misc_tools.py

cool-RR