替换属性以获得性能增益

Spy*_*cho 6 python properties

情况

此问题类似,我想替换一个属性.与那个问题不同,我不想在子类中覆盖它.我想在init和属性本身中替换它以提高效率,这样它就不必调用每次调用属性时计算值的函数.

我有一个课程,里面有一个属性.构造函数可以获取属性的值.如果传递了值,我想用值替换属性(不只是设置属性).这是因为属性本​​身会计算该值,这是一项昂贵的操作.类似地,我想用属性计算的值替换属性,以便将来对属性的调用不必重新计算:

class MyClass(object):
    def __init__(self, someVar=None):
        if someVar is not None: self.someVar = someVar

    @property
    def someVar(self):
        self.someVar = calc_some_var()
        return self.someVar
Run Code Online (Sandbox Code Playgroud)

问题

上面的代码不起作用,因为执行self.someVar =不会替换someVar函数.它试图调用属性的setter,它没有定义.

潜在解决方案

我知道我可以用稍微不同的方式实现同​​样的目的:

class MyClass(object):
    def __init__(self, someVar=None):
        self._someVar = someVar

    @property
    def someVar(self):
        if self._someVar is None:
            self._someVar = calc_some_var()
        return self._someVar
Run Code Online (Sandbox Code Playgroud)

这将略微降低效率,因为每次调用属性时都必须检查None.该应用程序对性能至关重要,因此这可能会或可能不够好.

有没有办法替换类实例上的属性?如果我能够做到这一点会有多高效(即避免无检查和函数调用)?

unu*_*tbu 15

您正在寻找的是Denis Otkidach优秀的CachedAttribute:

class CachedAttribute(object):    
    '''Computes attribute value and caches it in the instance.
    From the Python Cookbook (Denis Otkidach)
    This decorator allows you to create a property which can be computed once and
    accessed many times. Sort of like memoization.
    '''
    def __init__(self, method, name=None):
        # record the unbound-method and the name
        self.method = method
        self.name = name or method.__name__
        self.__doc__ = method.__doc__
    def __get__(self, inst, cls):
        # self: <__main__.cache object at 0xb781340c>
        # inst: <__main__.Foo object at 0xb781348c>
        # cls: <class '__main__.Foo'>       
        if inst is None:
            # instance attribute accessed on class, return self
            # You get here if you write `Foo.bar`
            return self
        # compute, cache and return the instance's attribute value
        result = self.method(inst)
        # setattr redefines the instance's attribute so this doesn't get called again
        setattr(inst, self.name, result)
        return result
Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

def demo_cache():
    class Foo(object):
        @CachedAttribute
        def bar(self):
            print 'Calculating self.bar'  
            return 42
    foo=Foo()
    print(foo.bar)
    # Calculating self.bar
    # 42
Run Code Online (Sandbox Code Playgroud)

请注意,访问foo.bar后续时间不会调用getter函数.(Calculating self.bar不打印.)

    print(foo.bar)    
    # 42
    foo.bar=1
    print(foo.bar)
    # 1
Run Code Online (Sandbox Code Playgroud)

删除foo.barfoo.__dict__重新暴露在中定义的属性Foo.因此,foo.bar再次调用再次重新计算该值.

    del foo.bar
    print(foo.bar)
    # Calculating self.bar
    # 42

demo_cache()
Run Code Online (Sandbox Code Playgroud)

装饰器发布在Python Cookbook中,也可以在ActiveState上找到.

这是有效的,因为虽然属性存在于类中__dict__,但在计算之后,在实例中创建了同名属性__dict__.Python的属性查找规则优先于实例中的属性__dict__,因此类中的属性会被有效覆盖.