İşlev dönüş değerlerini önbelleğe almak için bir dekoratör var mı?


158

Aşağıdakileri göz önünde bulundur:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

Ben yeniyim ama bence önbellek bir dekoratöre dönüştürülebilir. Sadece böyle bir tane bulamadım;)

PS, gerçek hesaplama değiştirilebilir değerlere bağlı değildir


Orada böyle bir yeteneğe sahip bir dekoratör olabilir, ancak ne istediğinizi tam olarak belirtmediniz. Ne tür bir önbellek kullanıyorsunuz? Ve değer nasıl anahtarlanacak? Kodunuzdan gerçekten ne istediğini önbelleğe alınmış salt okunur bir özellik olduğunu varsayıyorum.
David Berger

"Önbellek" adını verdiğiniz işlemi yapan not defteri dekoratörleri vardır; tipik olarak, sonuçları argümanlarına bağlı olan (kendilik gibi değişebilir şeylere değil!) gibi işlevler üzerinde çalışırlar ve ayrı bir not saklarlar.
Alex Martelli

Yanıtlar:


206

Python 3.2'den itibaren yerleşik bir dekoratör var:

@functools.lru_cache(maxsize=100, typed=False)

Dekoratör, en son yapılan aramaların maksimum boyutuna kadar kaydedilebilen bir memoizing callable ile bir fonksiyon sarmak için. Pahalı veya G / Ç bağlantılı bir işlev aynı argümanlarla periyodik olarak çağrıldığında zaman kazanabilir.

Fibonacci sayılarını hesaplamak için LRU önbellek örneği :

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> print([fib(n) for n in range(16)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> print(fib.cache_info())
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Python 2.x ile sıkışıp kalıyorsanız, diğer uyumlu not kütüphanelerinin listesi:




@gerrit teoride genel olarak yıkanabilir nesneler için çalışır - bazı yıkanabilir nesneler yalnızca aynı nesne olduklarında eşittir (açık bir __hash __ () işlevi olmayan kullanıcı tanımlı nesneler gibi).
Jonathan

1
@Jonathan Çalışıyor, ama yanlış. Yıkanabilir, değişken bir bağımsız değişken iletir ve işlevin ilk çağrısından sonra nesnenin değerini değiştirirsem, ikinci çağrı orijinal nesneyi değil, değiştirilen nesneyi döndürür. Neredeyse kesinlikle kullanıcının istediği bu değil. Değişken argümanlar için çalışması için lru_cache, önbelleğe aldığı sonucun bir kopyasını alması gerekir ve functools.lru_cacheuygulamada böyle bir kopya yapılmaz . Bunu yapmak, büyük bir nesneyi önbelleğe almak için kullanıldığında bulunması zor bellek sorunları yaratma riskini de beraberinde getirir.
gerrit

@gerrit Buradan takip eder misiniz: stackoverflow.com/questions/44583381/… ? Ben senin örneğini tamamen takip etmedim.
Jonathan

28

Eğer gibi geliyor değil (farklı argüman değerleri için önbellek dönüş değerlerine istediğiniz yere yani sen genel durumda ilgilenen değiliz) genel amaçlı memoization taslağıydı. Yani, buna sahip olmak istersiniz:

x = obj.name  # expensive
y = obj.name  # cheap

genel amaçlı bir not dekoratörü size şunları verir:

x = obj.name()  # expensive
y = obj.name()  # cheap

Metod çağrısı sözdiziminin daha iyi bir stil olduğunu, çünkü özellik sözdiziminin hızlı bir arama önerdiğini pahalı hesaplama olasılığını önerdiğini belirtiyorum.

[Güncelleme: Daha önce bağladığım ve burada alıntıladığım sınıf tabanlı not çözücü, yöntemler için çalışmıyor. Bunu bir dekoratör işleviyle değiştirdim.] Genel amaçlı bir not dekoratör kullanmak istiyorsanız, işte basit bir işlem:

def memoize(function):
  memo = {}
  def wrapper(*args):
    if args in memo:
      return memo[args]
    else:
      rv = function(*args)
      memo[args] = rv
      return rv
  return wrapper

Örnek kullanım:

@memoize
def fibonacci(n):
  if n < 2: return n
  return fibonacci(n - 1) + fibonacci(n - 2)

Önbellek boyutunu sınırlayan başka bir not dekoratörü burada bulunabilir .


Tüm cevaplarda bahsedilen dekoratörlerin hiçbiri yöntemler için çalışmıyor! Muhtemelen sınıf temelli oldukları için. Sadece bir benlik mi geçti? Diğerleri iyi çalışıyor, ancak değerleri işlevlerde saklamak çok acımasız.
Tobias

2
Args yıkanamazsa bir sorunla karşılaşabileceğinizi düşünüyorum.
Bilinmiyor

1
@Bilinmeyen Evet, burada alıntıladığım ilk dekoratör yıkanabilir türlerle sınırlıdır. ActiveState'teki (önbellek boyut sınırlamasıyla) argümanları elbette daha pahalı ama daha genel olan (yıkanabilir) bir dizeye alır.
Nathan Kitchen

@vanity Sınıf tabanlı dekoratörlerin sınırlamalarına dikkat ettiğiniz için teşekkür ederiz. Yanıtları, yöntemler için çalışan bir dekoratör işlevini göstermek için revize ettim (aslında bunu test ettim).
Nathan Kitchen

1
@SiminJie Dekoratör yalnızca bir kez çağrılır ve döndürdüğü sarılmış işlev, tüm farklı çağrılar için kullanılan işlevle aynıdır fibonacci. Bu işlev her zaman aynı memosözlüğü kullanır .
Nathan Kitchen

22
class memorize(dict):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        return self[args]

    def __missing__(self, key):
        result = self[key] = self.func(*key)
        return result

Örnek kullanımlar:

>>> @memorize
... def foo(a, b):
...     return a * b
>>> foo(2, 4)
8
>>> foo
{(2, 4): 8}
>>> foo('hi', 3)
'hihihi'
>>> foo
{(2, 4): 8, ('hi', 3): 'hihihi'}

Garip! Bu nasıl çalışıyor? Gördüğüm diğer dekoratörler gibi görünmüyor.
PascalVKooten

1
Anahtar kelime bağımsız değişkenleri kullanılıyorsa, bu çözüm bir TypeError döndürür, örneğin foo (3, b = 5)
kadee

1
Çözümün sorunu, bir bellek sınırının olmamasıdır. İsimlendirilmiş argümanlara gelince, onları ______ ve __sess__ gibi ** nargs'e ekleyebilirsiniz
Leonid Mednikov

16

Python 3.8 functools.cached_propertydekoratör

https://docs.python.org/dev/library/functools.html#functools.cached_property

cached_propertyWerkzeug from https://stackoverflow.com/a/5295190/895245 adresinden bahsedildi, ancak sözde türetilmiş bir sürüm 3.8 ile birleştirilecek, bu harika.

Bu dekoratör önbellekleme @propertyveya @functools.lru_cacheherhangi bir argümanınız olmadığı zamanlar için bir temizleyici olarak görülebilir .

Dokümanlar şöyle diyor:

@functools.cached_property(func)

Sınıf yöntemini, değeri bir kez hesaplanan ve daha sonra örneğin yaşamı için normal bir öznitelik olarak önbelleğe alınan bir özelliğe dönüştürün. Önbellek ekleyerek property () öğesine benzer. Aksi takdirde etkili bir şekilde değiştirilemeyen örneklerin pahalı hesaplanmış özellikleri için kullanışlıdır.

Misal:

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

3.8 sürümündeki yenilikler.

Not Bu dekoratör , her örnekte dict özniteliğinin değiştirilebilir bir eşleme olmasını gerektirir . Bu, metasınıflar (bazı türlerdeki dict öznitelikleri sınıf ad alanı için salt okunur proxy'ler olduğu için) ve tanımlanmış yuvalardan biri olarak dict içermeyen yuvaları belirten bazı türlerle (bu tür sınıflar gibi) çalışmayacağı anlamına gelir. bir dict niteliği vermeyin ).



9

İşlev yanıtlarını önbelleğe almak için bu basit dekoratör sınıfını kodladım. Projelerim için ÇOK yararlı buluyorum:

from datetime import datetime, timedelta 

class cached(object):
    def __init__(self, *args, **kwargs):
        self.cached_function_responses = {}
        self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))

    def __call__(self, func):
        def inner(*args, **kwargs):
            max_age = kwargs.get('max_age', self.default_max_age)
            if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
                if 'max_age' in kwargs: del kwargs['max_age']
                res = func(*args, **kwargs)
                self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
            return self.cached_function_responses[func]['data']
        return inner

Kullanımı basittir:

import time

@cached
def myfunc(a):
    print "in func"
    return (a, datetime.now())

@cached(default_max_age = timedelta(seconds=6))
def cacheable_test(a):
    print "in cacheable test: "
    return (a, datetime.now())


print cacheable_test(1,max_age=timedelta(seconds=5))
print cacheable_test(2,max_age=timedelta(seconds=5))
time.sleep(7)
print cacheable_test(3,max_age=timedelta(seconds=5))

1
İlk @cachedparantez eksik. Else sadece dönecektir cachedyerine nesne myfuncgibi çağrıldığında myfunc()sonra innerhep bir dönüş değeri olarak geri dönecektir
Markus Meskanen

6

YASAL UYARI: Ben kids.cache yazarım .

Kontrol etmelisiniz kids.cache, @cachepython 2 ve python 3 üzerinde çalışan bir dekoratör sağlar. Bağımlılık yok, ~ 100 kod satırı. Örneğin, kodunuzu göz önünde bulundurarak kullanmak çok basittir, bunu şu şekilde kullanabilirsiniz:

pip install kids.cache

Sonra

from kids.cache import cache
...
class MyClass(object):
    ...
    @cache            # <-- That's all you need to do
    @property
    def name(self):
        return 1 + 1  # supposedly expensive calculation

Veya @cachedekoratörü @property(aynı sonuçtan) sonra koyabilirsiniz .

Bir özellikte önbellek kullanımına tembel değerlendirme denir , kids.cacheçok daha fazlasını yapabilir (herhangi bir argüman, özellik, herhangi bir yöntem türü ve hatta sınıflarla işlev üzerinde çalışır ...). Gelişmiş kullanıcılar için, python 2 ve python 3'e (LRU, LFU, TTL, RR önbellek) süslü önbellek depoları sağlayan kids.cachedestekler cachetools.

ÖNEMLİ NOT : varsayılan önbellek deposu, kids.cachesürekli büyüyen bir önbellek deposuna yol açacağı için her zaman farklı sorguları olan uzun süren program için tavsiye edilmeyen standart bir diksiyondur. Bu kullanım için örneğin kullanarak diğer önbellek depolarını ekleyebilirsiniz ( @cache(use=cachetools.LRUCache(maxsize=2))işlevinizi / mülkünüzü / sınıfınızı / yönteminizi süslemek için ...)


Bu modül, python 2 ~ 0.9s'de yavaş bir içe aktarma süresiyle sonuçlanıyor gibi görünüyor (bkz: pastebin.com/raw/aA1ZBE9Z ). Bunun bu satırdan kaynaklandığından şüpheleniyorum github.com/0k/kids.cache/blob/master/src/kids/__init__.py#L3 (cf setuptools giriş noktaları). Bunun için bir sorun oluşturuyorum.
Att Righ

Yukarıdaki github.com/0k/kids.cache/issues/9 için bir sorun .
Att Righ

Bu bellek sızıntısına yol açar.
Timothy Zhang

@vaab bir örneğini oluşturmak carasında MyClassve ile kontrol objgraph.show_backrefs([c], max_depth=10), sınıf nesnesinden ref zinciri vardır MyClassiçin c. Yani cserbest bırakılıncaya kadar asla serbest MyClassbırakılmayacaktı.
Timothy Zhang

@TimothyZhang davetlisiniz ve endişelerinizi github.com/0k/kids.cache/issues/10 adresinden ekleyebilirsiniz . Stackoverflow bu konuda doğru bir tartışma yapmak için doğru yer değil. Ve daha fazla açıklamaya ihtiyaç vardır. Geri bildiriminiz için teşekkür ederiz.
vaab


4

Orada fastcache olan "Python 3 functools.lru_cache C uygulaması standart kütüphanesinde üzerinde 10-30x sağlanan hızlanmayı sağlar."

Seçilen cevapla aynı , sadece farklı içe aktarma:

from fastcache import lru_cache
@lru_cache(maxsize=128, typed=False)
def f(a, b):
    pass

Ayrıca, yüklenmesi gereken functools'un aksine Anaconda'da yüklü olarak gelir .


1
functoolsstandart kütüphanenin bir parçasıdır, yayınladığınız bağlantı rastgele bir git çatalı veya başka bir şeye ...
cz


3

Django Framework kullanıyorsanız, API'lerin kullanımının bir görünümünü veya yanıtını önbelleğe almak için böyle bir özelliğe sahiptir @cache_page(time)ve başka seçenekler de olabilir.

Misal:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

Daha fazla ayrıntıyı burada bulabilirsiniz .


2

Memoize Örneği ile birlikte aşağıdaki python paketlerini buldum:

  • önbellek ; Önbelleğe alınmış işlevler için ttl ve \ ya da çağrı sayısını ayarlamayı sağlar; Ayrıca, bir şifreli dosya tabanlı önbellek kullanabilirsiniz ...
  • percache

1

Böyle bir şey uyguladım, kalıcılık için turşu kullanarak ve neredeyse kesinlikle benzersiz kısa kimlikler için sha1'i kullandım. Temel olarak önbellek, bir sha1 elde etmek için işlevin kodunu ve bağımsız değişkenlerin yığınını karma hale getirerek adında o sha1 ile bir dosya aradı. Varsa, açtı ve sonucu döndürdü; değilse, işlevi çağırır ve sonucu kaydeder (isteğe bağlı olarak yalnızca işlenmesi belirli bir zaman alırsa tasarruf sağlar).

Dedi ki, yemin ederim ki bunu yapan mevcut bir modül buldum ve kendimi burada bu modülü bulmaya çalışırken buluyorum ... Bulabildiğim en yakın şey, şu doğru görünüyor: http: //chase-seibert.github. / 2011/11/23 / pythondjango-disk tabanlı önbelleğe alma-decorator.html io / blog

Bununla birlikte gördüğüm tek sorun, büyük diziler için iyi çalışmaz, çünkü dev diziler için benzersiz olmayan str (arg) hashes.

Bir sınıfın içeriğinin güvenli bir karma değerini döndüren bir unique_hash () protokolü olsaydı iyi olurdu . Temelde elle ilgilendiğim türler için bunu uyguladım.




1

@lru_cache varsayılan işlev değerleri ile mükemmel değil

Benim memdekoratör:

import inspect


def get_default_args(f):
    signature = inspect.signature(f)
    return {
        k: v.default
        for k, v in signature.parameters.items()
        if v.default is not inspect.Parameter.empty
    }


def full_kwargs(f, kwargs):
    res = dict(get_default_args(f))
    res.update(kwargs)
    return res


def mem(func):
    cache = dict()

    def wrapper(*args, **kwargs):
        kwargs = full_kwargs(func, kwargs)
        key = list(args)
        key.extend(kwargs.values())
        key = hash(tuple(key))
        if key in cache:
            return cache[key]
        else:
            res = func(*args, **kwargs)
            cache[key] = res
            return res
    return wrapper

ve test kodu:

from time import sleep


@mem
def count(a, *x, z=10):
    sleep(2)
    x = list(x)
    x.append(z)
    x.append(a)
    return sum(x)


def main():
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5))
    print(count(1,2,3,4,5, z=6))
    print(count(1,2,3,4,5, z=6))
    print(count(1))
    print(count(1, z=10))


if __name__ == '__main__':
    main()

sonuç - uyku ile sadece 3 kez

ama @lru_cacheonunla 4 kez olacak, çünkü bu:

print(count(1))
print(count(1, z=10))

iki kez hesaplanacaktır (varsayılanlarla kötü çalışma)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.