Ответ 1
Нет, получатель будет вызываться каждый раз при доступе к свойству.
Мой вопрос заключается в том, что интерпретатор выполняет следующие две части кода:
class A(object):
def __init__(self):
self.__x = None
@property
def x(self):
if not self.__x:
self.__x = ... #some complicated action
return self.__x
и намного проще:
class A(object):
@property
def x(self):
return ... #some complicated action
I.e., является интерпретатором достаточно умным для кэширования свойства x
?
Мое предположение заключается в том, что x
не меняется - найти это сложно, но как только вы его найдете один раз, нет причин снова его искать.
Нет, получатель будет вызываться каждый раз при доступе к свойству.
Вам не нужно добавлять декоратор memoize:
class memoized(object):
"""Decorator that caches a function return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncachable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __repr__(self):
"""Return the function docstring."""
return self.func.__doc__
def __get__(self, obj, objtype):
"""Support instance methods."""
return functools.partial(self.__call__, obj)
@memoized
def fibonacci(n):
"Return the nth fibonacci number."
if n in (0, 1):
return n
return fibonacci(n-1) + fibonacci(n-2)
print fibonacci(12)
Свойства не кэшируют свои возвращаемые значения автоматически. Геттер (и сеттеры) предназначены для вызова каждый раз при доступе к свойству.
Тем не менее, Денис Откидах написал замечательный хранитель атрибутов кеширования (опубликованный в Python Cookbook, 2-е издание, а также первоначально на ActiveState под Лицензия PSF) для этой цели:
class cache(object):
'''Computes attribute value and caches it in the instance.
Python Cookbook (Denis Otkidach) https://stackoverflow.com/users/168352/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 attribute value
result = self.method(inst)
# setattr redefines the instance attribute so this doesn't get called again
setattr(inst, self.name, result)
return result
Вот пример, демонстрирующий его использование:
def demo_cache():
class Foo(object):
@cache
def bar(self):
print 'Calculating self.bar'
return 42
foo=Foo()
print(foo.bar)
# Calculating self.bar
# 42
print(foo.bar)
# 42
foo.bar=1
print(foo.bar)
# 1
print(Foo.bar)
# __get__ called with inst = None
# <__main__.cache object at 0xb7709b4c>
# Deleting `foo.bar` from `foo.__dict__` re-exposes the property defined in `Foo`.
# Thus, calling `foo.bar` again recalculates the value again.
del foo.bar
print(foo.bar)
# Calculating self.bar
# 42
demo_cache()
Python 3.2 предлагает встроенный декоратор, который можно использовать для создания кеша LRU:
@functools.lru_cache(maxsize=128, typed=False)
Кроме того, если вы используете Flask/Werkzeug, там @cached_property
декоратор @cached_property
.
Для Django попробуйте from django.utils.functional import cached_property
Декоратор Дениса Откидаха, упомянутый в ответе @unutbu, был опубликован в Cookbook O'Reilly Python. К сожалению, O'Reilly не указывает какую-либо лицензию на примеры кода - так же, как неофициальное разрешение на повторное использование кода.
Если вам нужен декоратор свойств кэширования с либеральной лицензией, вы можете использовать Ken Seehof @cached_property
от рецепты кода ActiveState. Он явно опубликован под лицензией MIT.
def cached_property(f):
"""returns a cached property that is calculated by function f"""
def get(self):
try:
return self._property_cache[f]
except AttributeError:
self._property_cache = {}
x = self._property_cache[f] = f(self)
return x
except KeyError:
x = self._property_cache[f] = f(self)
return x
return property(get)
Примечание: добавление для полноты доступных опций.
Нет, property
не кэшируется по умолчанию. Однако есть несколько вариантов получения этого поведения, я хотел бы добавить еще одно:
Мне пришлось искать это, потому что у меня был такой же вопрос.
Пакет functools из стандартной библиотеки также получит декоратор cached_property. К сожалению, он доступен только с Python 3.8 (со времени этого сообщения, 3.8a0). Альтернативой ожиданию является использование пользовательской, например, такой, как указано в файле 0xc0de) или Django, и теперь переключитесь позже:
from django.utils.functional import cached_property
# from functools import cached_property # Only 3.8+ :(