如何更换/绕过类属性?

我想有一个具有属性 attr 的类,当第一次访问时,它运行一个函数并返回一个值,然后变为该值(其类型更改等) .

可以通过以下方式获得类似的行为:

class MyClass(object):
    @property
    def attr(self):
        try:
            return self._cached_result
        except AttributeError:
            result = ...
            self._cached_result = result
            return result

obj = MyClass()
print obj.attr  # First calculation
print obj.attr  # Cached result is used

但是, .attr 在执行此操作时不会成为初始结果 . 如果它这样做会更有效率 .

难点在于 obj.attr 设置为属性后,无法轻松设置其他内容,因为无限循环会自然出现 . 因此,在上面的代码中, obj.attr 属性没有setter,因此无法直接修改它 . 如果定义了setter,则在此setter中替换 obj.attr 会创建一个无限循环(从setter中访问setter) . 我还想到了第一次删除setter以便能够使用 del self.attr 进行常规 self.attr = … ,但这会调用属性删除器(如果有的话),这会重新创建无限循环问题( self.attr 的修改通常会通过 property 规则) .

那么,有没有办法绕过属性机制并在 MyClass.attr.__getter__ 内用任何东西替换绑定属性 obj.attr

回答(2)

2 years ago

这看起来有点像过早优化:您希望通过使描述符自身更改来跳过方法调用 .

这是完全可能的,但它必须是合理的 .

要修改属性中的描述符,您必须编辑您的类,这可能不是您想要的 .

我认为实现这一目标的更好方法是:

  • 不定义 obj.attr

  • 覆盖 __getattr__ ,如果参数为"attr", obj.attr = new_value ,否则引发 AttributeError

设置 obj.attr 后,将不再调用 __getattr__ ,因为只有在该属性不存在时才会调用它 . ( __getattribute__ 是一直被调用的那个 . )

与初始提议的主要区别在于第一个属性访问速度较慢,因为 __getattr__ 的方法调用开销,但它将与常规 __dict__ 查找一样 .

Example :

class MyClass(object):

    def __getattr__(self, name):
        if name == 'attr':
            self.attr = ...
            return self.attr
        raise AttributeError(name)

obj = MyClass()
print obj.attr  # First calculation
print obj.attr  # Cached result is used

EDIT : 请参阅the other answer,特别是如果您使用的是Python 3.6或更高版本 .

2 years ago

对于使用描述符协议的新式类,您可以通过创建自己的自定义描述符类来完成此操作,该类最多只能调用一次 __get__() 方法 . 当发生这种情况时,然后通过创建类方法具有相同名称的实例属性来缓存结果 .

这就是我的意思 .

from __future__ import print_function

class cached_property(object):
    """Descriptor class for making class methods lazily-evaluated and caches the result."""
    def __init__(self, func):
        self.func = func

    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            value = self.func(inst)
            setattr(inst, self.func.__name__, value)
            return value

class MyClass(object):
    @cached_property
    def attr(self):
        print('doing long calculation...', end='')
        result = 42
        return result

obj = MyClass()
print(obj.attr)  # -> doing long calculation...42
print(obj.attr)  # -> 42