实现Python中的惰性计算常量属性

93 阅读2分钟

在Python中,我们可能会遇到这样的场景:需要创建一些只计算一次且不会改变的属性,但为了提高性能,我们希望这些属性在对象创建时不进行初始化,而是延迟到第一次访问时才计算。为了实现这一目标,我们可以创建一个名为 LazilyEvaluatedConstantProperty 的类。

huake_00210_.jpg

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)

如上代码所示,LazilyEvaluatedConstantProperty 类的用法与内置的 property 类似,但它只提供了一个 getter 方法,没有 setter 或 deleter 方法。在第一次访问 my_object.personality 时,_get_personality 方法将被调用,然后结果会被缓存起来,并在对象后续的访问中不再调用 _get_personality 方法。

2、解决方案

为了提高性能,我们希望在第一次访问并调用 _get_personality 方法后,将 personality 属性作为对象的一个数据属性,以便后续调用时可以更快地查找。我们可以通过以下方式来实现这一目标:

class CachedProperty(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 = CachedProperty(_get_personality)

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

        You may optionally pass in the name that 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):

        if obj is None:
            # We're being accessed from the class itself, not from an object
            return self

        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

在这个类中,我们定义了 __init__ 方法和 __get__ 方法。在 __init__ 方法中,我们接受一个 getter 函数和一个可选的属性名作为参数。在 __get__ 方法中,我们首先检查对象是否为 None,如果是,则表明我们正在从类本身而不是从对象访问属性,此时我们直接返回 self。如果不是,我们调用 getter 函数获取属性值,并将其存储在 value 变量中。

接下来,我们检查属性名是否为空。 如果为空,我们使用 vars(our_type).iteritems() 方法获取类的所有属性和值,并使用一个生成器表达式找到与 self 相对应的属性名,将其存储在 self.our_name 中。最后,我们将属性值存储在对象的 self.our_name 属性中,并返回属性值。

通过这种方式,我们在第一次访问属性时计算了属性值,并在后续访问中直接从对象的数据属性中获取属性值,从而提高了访问速度。