Python memoising / deferred lookup property decorator
Ostatnio przejrzałem istniejącą bazę kodu zawierającą wiele klas, w których atrybuty instancji odzwierciedlają wartości przechowywane w bazie danych. Zrefakturowałem wiele z tych atrybutów, aby ich wyszukiwanie bazy danych zostało odroczone, tj. nie zostanie zainicjowana w konstruktorze, ale dopiero po pierwszym wczytaniu. Atrybuty te nie zmieniają się w czasie trwania wystąpienia, ale są prawdziwym wąskim gardłem do obliczenia tego po raz pierwszy i naprawdę dostępne tylko w szczególnych przypadkach. Dzięki temu mogą być również buforowane po ich pobraniu z bazy danych (dlatego pasuje to do definicji memoisation, gdzie dane wejściowe są po prostu "brak danych wejściowych").
W kółko wpisuję następujący fragment kodu dla różnych atrybutów w różnych klasach:
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
Czy istnieje istniejący dekorator, który robi to już w Pythonie, o którym po prostu nie wiem? Czy istnieje dość prosty sposób na zdefiniowanie dekoratora, który to robi?
Pracuję pod Pythonem 2.5, ale odpowiedzi 2.6 nadal mogą być interesujące, jeśli są znacznie różne.
Uwaga
To pytanie zostało zadane zanim Python zawierał wiele gotowych dekoratorów do tego. Zaktualizowałem go tylko w celu poprawienia terminologii.
8 answers
Do wszelkiego rodzaju świetnych narzędzi używam boltonów .
Jako część tej biblioteki masz 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-29 00:07:23
Oto przykładowa implementacja leniwego dekoratora Nieruchomości:
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)
Sesja Interaktywna:
>>> 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]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-06 12:43:03
Napisałam to dla siebie... Do użycia dla true jednorazowo obliczonych właściwości leniwych. Podoba mi się, ponieważ unika przyklejania dodatkowych atrybutów na obiektach, a po aktywacji nie marnuje czasu na sprawdzanie obecności atrybutów itp.:
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
Uwaga: klasa lazy_property
jest nie-deskryptorem danych, co oznacza, że jest tylko do odczytu. Dodanie metody __set__
uniemożliwiłoby jej poprawne działanie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-06 12:36:59
Oto wywołanie, które pobiera opcjonalny argument timeout, w __call__
można również skopiować __name__
, __doc__
, __module__
z przestrzeni nazw func:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 22:51:28
property
to klasa. A deskryptor aby być dokładnym. Po prostu wyprowadzić z niego i wdrożyć pożądane zachowanie.
class lazyproperty(property):
....
class testA(object):
....
a = lazyproperty('_a')
b = lazyproperty('_b')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 07:45:57
To, czego naprawdę chcesz, to reify
(źródło: linked!) dekorator z piramidy:
Użyj jako dekorator metody klasowej. Działa prawie dokładnie tak, jak dekorator Pythona
@property
, ale po pierwszym wywołaniu umieszcza wynik metody, którą dekoruje w dict instancji, skutecznie zastępując dekorowaną funkcję zmienną instancji. Jest to, w języku Python, deskryptor Nie-danych. Poniżej znajduje się przykład i jego użycie:>>> 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-12 18:34:06
Do tej pory istnieje pomieszanie pojęć i/lub pomieszanie pojęć zarówno w pytaniach, jak i w odpowiedziach.
Leniwa ocena oznacza tylko, że coś jest oceniane w czasie wykonywania w ostatnim możliwym momencie, gdy potrzebna jest wartość. dekorator standardowy (*) funkcja jest obliczana tylko i za każdym razem, gdy potrzebujesz wartości tej właściwości. (zobacz artykuł na Wikipedii o leniwej ocenie)
@property
właśnie to robi.
(*)faktycznie prawdziwa leniwa ocena (porównaj np. haskell) jest bardzo trudne do osiągnięcia w Pythonie (i skutkuje kodem, który jest daleki od idiomatycznego).
Memoizacja jest właściwym określeniem tego, czego asker wydaje się szukać. Czyste funkcje, które nie zależą od efektów ubocznych, mogą być bezpiecznie zapamiętane i w functools istnieje dekorator @functools.lru_cache
więc nie ma potrzeby pisania własnych dekoratorów, chyba że potrzebujesz specjalistycznego zachowania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-26 19:13:36
Możesz to zrobić ładnie i łatwo, budując klasę z natywnej właściwości Pythona:
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
Możemy używać tej właściwości jak zwykłej właściwości klasy (jest to również wsparcie przypisanie pozycji, jak widać)
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)
Wartość obliczana tylko za pierwszym razem, a następnie użyliśmy naszej zapisanej wartości
Wyjście:
I am calculating value
My calculated value
My calculated value
2
2
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-30 08:59:29