【问题标题】:How to replace/bypass a class property?如何替换/绕过类属性?
【发布时间】:2016-12-06 09:56:25
【问题描述】:

我想要一个具有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 中替换 obj.attr 会创建一个无限循环(从 setter 内部访问 setter)。我还想过首先删除 setter,以便能够使用del self.attr 执行常规self.attr = …,但这会调用属性删除器(如果有),这会重新创建无限循环问题(在任何地方对self.attr 的修改通常都倾向于通过属性规则)。

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

【问题讨论】:

    标签: python properties attributes


    【解决方案1】:

    这看起来有点像过早优化:您想通过更改描述符本身来跳过方法调用。

    这完全有可能,但必须有理由。

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

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

    • 不定义obj.attr
    • 覆盖__getattr__,如果参数是“attr”,obj.attr = new_value,否则引发AttributeError

    一旦设置了obj.attr__getattr__ 将不再被调用,因为它仅在属性不存在时调用。 (__getattribute__ 会一直被调用。)

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

    示例:

    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
    

    编辑:请参阅the other answer,尤其是如果您使用 Python 3.6 或更高版本。

    【讨论】:

    • 我只是在写同样的东西,我会赞成你的,这很好用!
    • 谢谢。我不确定我是否理解“从你的属性修改描述符,你必须编辑你的类”的部分:事实上,需要的不是类本身的更新(这会破坏@ 987654333@ 用于新实例),但更新了 instanceattr 属性。对吗?
    • 不,因为只要attr在类上定义数据描述符,就会被使用。 Python 属性查找顺序可能有点棘手,here is some more reading on the subject
    【解决方案2】:

    对于使用描述符协议的新型类,您可以通过创建自己的自定义描述符类来做到这一点,该类的__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
    

    【讨论】:

    • 这不是你想要的吗?属性只是描述符实例。
    • 这也完全符合 OP 的要求,但在 Python 3.6 中会更好,它可以使用 __set_name__ 而不是使用 __name__,这可能会在其他装饰器存在的情况下做意想不到的事情。跨度>
    • 这也是解决 OP 问题的一个更好的方法:他尝试“删除”setter,虽然起初不必定义它,使用描述符而不是 property。我相信这应该是公认的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-24
    相关资源
    最近更新 更多