0

是否可以在创建 python 属性后更改它的 getter?

class A:
    _lookup_str = 'hi'
    @property
    def thing():
        value = some_dictionary[_lookup_str]
        # overwrite self.thing so that it is just value, not a special getter
        return value

这个想法是,一旦我查过一次,我就不必再查了(字典永远不会改变)。我可以:

class A:
     _lookup_str = 'hi'
     _thing = None
     @property
     def thing():
         if not value:
             value = some_dictionary[_lookup_str]
         return value

但即使在那里,我也在测试一个条件——这比我完全移除 getter 并用一个值替换它要多。

4

4 回答 4

7

Werkzeug 有一个cached_property装饰器,可以完全满足您的需求。它只是将__dict__第一次函数调用后的函数条目替换为第一次调用的输出。

这是代码(来自github 上的 werkzeug.utils并稍微编辑了长度):

_missing = object()

class cached_property(object):
    """A decorator that converts a function into a lazy property.  The
    function wrapped is called the first time to retrieve the result
    and then that calculated result is used the next time you access
    the value::

        class Foo(object):

            @cached_property
            def foo(self):
                # calculate something important here
                return 42

    The class has to have a `__dict__` in order for this property to
    work.
    """

    # implementation detail: this property is implemented as non-data
    # descriptor.  non-data descriptors are only invoked if there is
    # no entry with the same name in the instance's __dict__.
    # this allows us to completely get rid of the access function call
    # overhead.  If one choses to invoke __get__ by hand the property
    # will still work as expected because the lookup logic is replicated
    # in __get__ for manual invocation.

    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__, _missing)
        if value is _missing:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value

(顺便说一句 - 发布代码或链接到这样的代码更好吗?)

如果您想了解更多关于它为什么起作用的信息,请查看描述符上的 Python 文档@property上面的代码创建了一个允许覆盖它的非数据描述符(与 不同)。

于 2012-08-23T03:58:05.673 回答
0

不推荐,但它适用于旧式类:

>>> class A:
...   @property
...   def thing(self):
...       print 'thing'
...       self.thing = 42
...       return self.thing
... 
>>> a = A()
>>> a.thing
thing
42
>>> a.thing
42
>>> a.thing
42

它不适用于新式类(类型、对象的子类),因此它不适用于所有类都是新式的 Python 3。在这种情况下使用@Jeff Tratner 的答案。

于 2012-08-23T05:10:23.240 回答
0

正如 Jeff Tratner 所指出的,答案是覆盖在__dict__python 对象中找到的属性对象。Werkzeug 的 cached_property 对我来说似乎过于复杂。以下(更简单的)代码对我有用:

def cached_property(f):
    @property
    def g(self, *args, **kwargs):
        print 'trace'
        value = f(self, *args, **kwargs)
        self.__dict__[f.__name__] = value
        return value
    return g

class A:
    @cached_property
    def thing(self):
        return 5

a = A()
print a.thing
print a.thing
print a.thing
print a.thing

# 'trace' is only shown once -- the first time a.thing is accessed.
于 2012-08-23T04:59:20.680 回答
0

JF Sebastian 和 Isaac Sutherland 的答案不适用于新样式类。它将产生 Jeff Tratner 提到的结果;在每次访问时打印出跟踪。

对于新样式类,您需要覆盖__getattribute__

简单修复:

def cached_property(f):
    @property
    def g(self, *args, **kwargs):
        print 'trace'
        value = f(self, *args, **kwargs)
        self.__dict__[f.__name__] = value

        return value
    return g

def cached_class(c):
    def d(self, name):
        getattr = object.__getattribute__
        if (name in getattr(self, '__dict__')):
            return getattr(self, '__dict__')[name]
        return getattr(self, name)

    c.__getattribute__ = d
    return c

@cached_class
class A(object):
    @cached_property
    def thing(self):
        return 5

a = A()
print a.thing
print a.thing
print a.thing
print a.thing
于 2012-08-23T06:59:26.613 回答