Python 메모 / 지연 조회 속성 데코레이터
최근에 인스턴스 속성이 데이터베이스에 저장된 값을 반영하는 많은 클래스를 포함하는 기존 코드베이스를 살펴 보았습니다. 데이터베이스 조회가 지연되도록 이러한 속성을 많이 리팩터링했습니다. 생성자에서 초기화되지 않고 처음 읽을 때만 초기화됩니다. 이러한 속성은 인스턴스의 수명 동안 변경되지 않지만 처음 계산하는 데에는 실제 병목 현상이 발생하고 특수한 경우에만 실제로 액세스됩니다. 따라서 데이터베이스에서 검색된 후에도 캐시 될 수 있습니다 (따라서 입력이 단순히 "입력 없음" 인 메모 화 정의에 적합 ).
다양한 클래스의 다양한 속성에 대해 다음 코드 스 니펫을 반복해서 입력합니다.
class testA(object):
def __init__(self):
self._a = None
self._b = None
@property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
@property
def b(self):
#etc
내가 알지 못하는 파이썬에서 이미 이것을 수행하는 기존 데코레이터가 있습니까? 아니면 이것을 수행하는 데코레이터를 정의하는 합리적으로 간단한 방법이 있습니까?
저는 Python 2.5에서 작업하고 있지만 2.6 답변이 크게 다르면 여전히 흥미로울 수 있습니다.
노트
이 질문은 파이썬이이를 위해 많은 기성 데코레이터를 포함하기 전에 요청되었습니다. 용어를 수정하기 위해서만 업데이트했습니다.
모든 종류의 훌륭한 유틸리티를 위해 볼튼을 사용 하고 있습니다.
해당 라이브러리의 일부로 cachedproperty 있습니다 .
from boltons.cacheutils import cachedproperty
class Foo(object):
def __init__(self):
self.value = 4
@cachedproperty
def cached_prop(self):
self.value += 1
return self.value
f = Foo()
print(f.value) # initial value
print(f.cached_prop) # cached property is calculated
f.value = 1
print(f.cached_prop) # same value for the cached property - it isn't calculated again
print(f.value) # the backing value is different (it's essentially unrelated value)
다음은 lazy 속성 데코레이터의 구현 예입니다.
import functools
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
@functools.wraps(fn)
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class Test(object):
@lazyprop
def a(self):
print 'generating "a"'
return range(5)
대화식 세션 :
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
I wrote this one for myself... To be used for true one-time calculated lazy properties. I like it because it avoids sticking extra attributes on objects, and once activated does not waste time checking for attribute presence, etc.:
import functools
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
# copy the getter function's docstring and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, cls):
if obj is None:
return self
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
class Test(object):
@lazy_property
def results(self):
calcs = 1 # Do a lot of calculation here
return calcs
Note: The lazy_property
class is a non-data descriptor, which means it is read-only. Adding a __set__
method would prevent it from working correctly.
Here's a callable that takes an optional timeout argument, in the __call__
you could also copy over the __name__
, __doc__
, __module__
from func's namespace:
import time
class Lazyproperty(object):
def __init__(self, timeout=None):
self.timeout = timeout
self._cache = {}
def __call__(self, func):
self.func = func
return self
def __get__(self, obj, objcls):
if obj not in self._cache or \
(self.timeout and time.time() - self._cache[key][1] > self.timeout):
self._cache[obj] = (self.func(obj), time.time())
return self._cache[obj]
ex:
class Foo(object):
@Lazyproperty(10)
def bar(self):
print('calculating')
return 'bar'
>>> x = Foo()
>>> print(x.bar)
calculating
bar
>>> print(x.bar)
bar
...(waiting 10 seconds)...
>>> print(x.bar)
calculating
bar
property
is a class. A descriptor to be exact. Simply derive from it and implement the desired behavior.
class lazyproperty(property):
....
class testA(object):
....
a = lazyproperty('_a')
b = lazyproperty('_b')
What you really want is the reify
(source linked!) decorator from Pyramid:
Use as a class method decorator. It operates almost exactly like the Python
@property
decorator, but it puts the result of the method it decorates into the instance dict after the first call, effectively replacing the function it decorates with an instance variable. It is, in Python parlance, a non-data descriptor. The following is an example and its usage:>>> from pyramid.decorator import reify >>> class Foo(object): ... @reify ... def jammy(self): ... print('jammy called') ... return 1 >>> f = Foo() >>> v = f.jammy jammy called >>> print(v) 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 >>> # Note: reassignment is possible >>> f.jammy = 2 >>> f.jammy 2
There is a mix up of terms and/or confusion of concepts both in question and in answers so far.
Lazy evaluation just means that something is evaluated at runtime at the last possible moment when a value is needed. The standard (*) The decorated function is evaluated only and every time you need the value of that property. (see wikipedia article about lazy evaluation) @property
decorator does just that.
(*)Actually a true lazy evaluation (compare e.g. haskell) is very hard to achieve in python (and results in code which is far from idiomatic).
Memoization is the correct term for what the asker seems to be looking for. Pure functions that do not depend on side effects for return value evaluation can be safely memoized and there is actually a decorator in functools @functools.lru_cache
so no need for writing own decorators unless you need specialized behavior.
You can do this nice and easily by building a class from Python native property:
class cached_property(property):
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 __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, None)
if value is None:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
We can use this property class like regular class property ( It's also support item assignment as you can see)
class SampleClass():
@cached_property
def cached_property(self):
print('I am calculating value')
return 'My calculated value'
c = SampleClass()
print(c.cached_property)
print(c.cached_property)
c.cached_property = 2
print(c.cached_property)
print(c.cached_property)
Value only calculated first time and after that we used our saved value
Output:
I am calculating value
My calculated value
My calculated value
2
2
참고URL : https://stackoverflow.com/questions/3012421/python-memoising-deferred-lookup-property-decorator
'program tip' 카테고리의 다른 글
Spring applicationContext에서 시스템 환경 변수를 읽는 방법 (0) | 2020.08.11 |
---|---|
IIS Express를 IP 주소에 바인딩 (0) | 2020.08.11 |
프로세스에 대한 개별 CPU 코어 사용량을 어떻게 측정합니까? (0) | 2020.08.11 |
Javascript에서 날짜 비교는 어떻게합니까? (0) | 2020.08.11 |
nodejs stdin에서 키 입력을 읽는 방법 (0) | 2020.08.11 |