Notlama nedir ve Python'da nasıl kullanabilirim?


378

Python'a yeni başladım ve hatırlamanın ne olduğu ve nasıl kullanılacağı hakkında hiçbir fikrim yok . Ayrıca, basitleştirilmiş bir örnek alabilir miyim?


215
İlgili wikipedia makalesinin ikinci cümlesi, genel olarak yukarıdan aşağıya ayrıştırma algoritmasında [2] [3] polinom zaman ve uzayda belirsizlik ve sol özyinelemeyi barındıran karşılıklı olarak yinelemeli iniş ayrıştırma [1] ifadesini içerdiğinde, SO'ya neler olduğunu sormak tamamen uygun.
Clueless

10
@Clueless: Bu ifadeden önce "Memoization, gibi diğer bağlamlarda (ve hız kazançları dışındaki amaçlar için de kullanılmıştır)." Yani bu sadece örneklerin bir listesidir (ve anlaşılması gerekmez); hatırlamanın açıklamasının bir parçası değildir.
ShreevatsaR

1
@StefanGruenwald Bu bağlantı öldü. Bir güncelleme bulabilir misiniz?
JS.

2
Pdf dosyasına yeni bağlantı, çünkü pycogsci.info kapalı: people.ucsc.edu/~abrsvn/NLTK_parsing_demos.pdf
Stefan Gruenwald

4
@Clueless, Makale aslında " polinom zaman ve uzayda belirsizliği ve sol özyinelemeyi barındıran genel yukarıdan aşağıya ayrıştırma algoritmasında [2] basit karşılıklı yinelemeli iniş ayrıştırma [1] " der . Bu örneği çok daha net yapan basit olanı kaçırdınız :).
studgeek

Yanıtlar:


353

Memoization, yöntem girişlerine dayalı olarak yöntem çağrılarının sonuçlarının hatırlanmasını ("memoization" → "memorandum" → hatırlanması) ve daha sonra sonucu tekrar hesaplamak yerine hatırlanan sonucu döndürmeyi ifade eder. Bunu yöntem sonuçları için bir önbellek olarak düşünebilirsiniz. Daha fazla ayrıntı için, Algoritmalara Giriş (3e), Cormen ve ark.

Python'da not kullanarak faktöriyelleri hesaplamak için basit bir örnek şöyle olacaktır:

factorial_memo = {}
def factorial(k):
    if k < 2: return 1
    if k not in factorial_memo:
        factorial_memo[k] = k * factorial(k-1)
    return factorial_memo[k]

Daha karmaşık hale gelebilir ve not verme sürecini bir sınıfa dahil edebilirsiniz:

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        #Warning: You may wish to do a deepcopy here if returning objects
        return self.memo[args]

Sonra:

def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

factorial = Memoize(factorial)

Python 2.4'te " dekoratörler " olarak bilinen bir özellik eklendi.

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

Python Dekoratör Kütüphanesi adlı benzer dekoratör sahiptir memoizedbiraz daha sağlam daha Memoizeburada gösterilen sınıfın.


2
Bu öneri için teşekkürler. Memoize sınıfı, çok fazla yeniden düzenleme gerektirmeden mevcut koda kolayca uygulanabilen zarif bir çözümdür.
Kaptan Lepton

10
Memoize sınıfı çözümü buggy, aynı işe yaramaz factorial_memo, çünkü factorialiçeride def factorialhala eski anımsama çağırıyor factorial.
adamsmith

9
Bu arada, if k not in factorial_memo:daha iyi okuyan da yazabilirsiniz if not k in factorial_memo:.
ShreevatsaR

5
Bunu gerçekten bir dekoratör olarak yapmalı.
Emlyn O'Regan

3
@ durden2.0 Bunun eski bir yorum olduğunu biliyorum, ama argsbir demet. def some_function(*args)argları bir demet yapar.
Adam Smith

232

Python 3.2'de yeni functools.lru_cache. Varsayılan olarak, yalnızca en son kullanılan 128 çağrıyı önbelleğe alır, ancak önbelleğin asla sona ermemesi gerektiğini belirtmek için maxsizeto Nonedeğerini ayarlayabilirsiniz :

import functools

@functools.lru_cache(maxsize=None)
def fib(num):
    if num < 2:
        return num
    else:
        return fib(num-1) + fib(num-2)

Bu işlev tek başına çok yavaş, deneyin fib(36)ve yaklaşık on saniye beklemek zorunda kalacaksınız.

lru_cacheEk açıklama eklemek , işlev belirli bir değer için yakın zamanda çağrılmışsa, bu değeri yeniden hesaplamamasını, ancak önbelleğe alınmış önceki sonucu kullanmasını sağlar. Bu durumda, kod önbellek ayrıntılarıyla darmadağın olmamasına rağmen, muazzam bir hız iyileşmesine yol açar.


2
Denenmiş fib (1000), Özyineleme hatası aldı: Maksimum özyineleme derinliği karşılaştırmada aşıldı
X Æ A-12

5
@Andyk Varsayılan Py3 özyineleme sınırı 1000'dir. İlk kez fibçağrıldığında, hatırlama gerçekleşmeden önce temel duruma geri dönmesi gerekir. Yani, davranışınız hemen hemen beklenen.
Quelklef

1
Yanılmıyorsam, ancak süreç öldürülünceye kadar önbelleğe alınır, değil mi? Yoksa sürecin öldürülüp öldürülmediğine bakılmaksızın önbellekliyor mu? Diyelim ki sistemimi yeniden başlattım - önbelleğe alınan sonuçlar hala önbelleğe alınacak mı?
Kristada673

1
@ Kristada673 Evet, işlemin belleğinde saklanır, diskte değil.
Flimm

2
Yinelemeli bir işlev olduğundan ve kendi ara sonuçlarını önbelleğe aldığından, işlevin ilk çalışmasını bile hızlandırdığını unutmayın. Benim gibi mankenleri daha net hale getirmek için doğal olarak yavaş olan özyinelemeli olmayan bir işlevi göstermek iyi olabilir. : D
endolit

61

Diğer cevaplar oldukça iyi olanı kapsar. Bunu tekrarlamıyorum. Sizin için yararlı olabilecek bazı noktalar.

Genellikle not, bir şeyi hesaplayan (pahalı) ve bir değer döndüren herhangi bir işleve uygulayabileceğiniz bir işlemdir. Bu nedenle, genellikle dekoratör olarak uygulanır . Uygulama basittir ve böyle bir şey olurdu

memoised_function = memoise(actual_function)

veya dekoratör olarak ifade edilir

@memoise
def actual_function(arg1, arg2):
   #body

18

Memoization, pahalı hesaplamaların sonuçlarını tutar ve sürekli olarak yeniden hesaplamak yerine önbelleğe alınan sonucu döndürür.

İşte bir örnek:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]

Daha eksiksiz bir açıklama , hafızaya alma hakkındaki wikipedia girişinde bulunabilir .


Hmm, şimdi doğru Python olsaydı, sallanırdı, ama öyle görünmüyor ... tamam, yani "önbellek" bir dict değil mi? Çünkü eğer öyleyse, olmalı if input not in self.cache ve self.cache[input] ( has_keyo zamandan beri kullanılmıyor ... 2.x serisinin başlarında, 2.0 değilse. self.cache(index)Asla doğru değildi. IIRC)
Jürgen A. Erhard

15

hasattrEl işçiliği yapmak isteyenler için yerleşik işlevi unutmayalım . Bu şekilde mem önbelleğini işlev tanımının içinde tutabilirsiniz (genelin aksine).

def fact(n):
    if not hasattr(fact, 'mem'):
        fact.mem = {1: 1}
    if not n in fact.mem:
        fact.mem[n] = n * fact(n - 1)
    return fact.mem[n]

Bu çok pahalı bir fikir gibi görünüyor. Her n için, sadece n için sonuçları değil, aynı zamanda 2 ... n-1 için de sonuçları önbelleğe alır.
codeforester

15

Bunu son derece kullanışlı buldum

def memoize(function):
    from functools import wraps

    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper


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

fibonacci(25)

Birinin neden kullanması gerektiğine ilişkin docs.python.org/3/library/functools.html#functools.wraps adresine bakın functools.wraps.
anishpatel

1
memoBelleğin boşaltılması için manuel olarak temizlemem gerekir mi?
nos

Tüm fikir, sonuçların bir oturum içinde notun içinde saklanmasıdır. Yani hiçbir şey olduğu gibi temizlenmiyor
mr.bjerre

6

Memoization temelde, daha sonraki bir aşamada aynı hesaplama gerekiyorsa özyineleme ağacında gezinme ihtiyacını azaltmak için özyinelemeli algoritmalarla yapılan geçmiş işlemlerin sonuçlarını kaydeder.

bkz. http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

Python'daki Fibonacci Memoization örneği:

fibcache = {}
def fib(num):
    if num in fibcache:
        return fibcache[num]
    else:
        fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
        return fibcache[num]

2
Daha fazla performans için, fibcache'nizi bilinen ilk birkaç değerle önceden tohumlayın, daha sonra bunları kodun 'sıcak yolundan' çıkarmak için ekstra mantığı alabilirsiniz.
jkflying

5

Bellek, işlevlerin veri yapılarına dönüştürülmesidir. Genellikle dönüşümün aşamalı ve tembel bir şekilde gerçekleşmesi (belirli bir alan öğesinin veya "anahtarın" talebi üzerine) istenir. Tembel fonksiyonel dillerde, bu tembel dönüşüm otomatik olarak gerçekleşebilir ve böylece not verme (açık) yan etkiler olmadan uygulanabilir.


5

İlk olarak ilk bölümü cevaplamalıyım: Notlama nedir?

Bu sadece anı için ticaret yapmak için bir yöntem. Çarpım Tablosunu düşünün .

Değişken nesneyi Python'da varsayılan değer olarak kullanmak genellikle kötü kabul edilir. Ancak akıllıca kullanırsanız, a memoization.

İşte http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects adresinden uyarlanmış bir örnek

dictİşlev tanımında bir değişken kullanarak , ara hesaplanan sonuçlar önbelleğe alınabilir (örneğin, hesaplamadan factorial(10)sonra hesaplama yaparken factorial(9), tüm ara sonuçları yeniden kullanabiliriz)

def factorial(n, _cache={1:1}):    
    try:            
        return _cache[n]           
    except IndexError:
        _cache[n] = factorial(n-1)*n
        return _cache[n]

4

İşte mızmız olmadan list veya dict tipi argümanlarla çalışacak bir çözüm:

def memoize(fn):
    """returns a memoized version of any function that can be called
    with the same list of arguments.
    Usage: foo = memoize(foo)"""

    def handle_item(x):
        if isinstance(x, dict):
            return make_tuple(sorted(x.items()))
        elif hasattr(x, '__iter__'):
            return make_tuple(x)
        else:
            return x

    def make_tuple(L):
        return tuple(handle_item(x) for x in L)

    def foo(*args, **kwargs):
        items_cache = make_tuple(sorted(kwargs.items()))
        args_cache = make_tuple(args)
        if (args_cache, items_cache) not in foo.past_calls:
            foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
        return foo.past_calls[(args_cache, items_cache)]
    foo.past_calls = {}
    foo.__name__ = 'memoized_' + fn.__name__
    return foo

Bu yaklaşımın, handle_item'de kendi karma işlevinizi özel bir durum olarak uygulayarak herhangi bir nesneye doğal olarak genişletilebileceğini unutmayın. Örneğin, bu yaklaşımın kümeyi girdi bağımsız değişkeni olarak alan bir işlev için çalışmasını sağlamak için handle_item öğesine ekleyebilirsiniz:

if is_instance(x, set):
    return make_tuple(sorted(list(x)))

1
İyi girişim. Sızma olmadan, bir listargümanı [1, 2, 3]yanlışlıkla setdeğeri olan farklı bir argümanla aynı kabul edilebilir {1, 2, 3}. Ek olarak, setler sözlükler gibi sıralanmamıştır, bu yüzden de olması gerekir sorted(). Ayrıca, özyinelemeli veri yapısı bağımsız değişkeninin sonsuz döngüye neden olacağını unutmayın.
martineau

Evet, setler özel gövde handle_item (x) ve sıralama ile ele alınmalıdır. Bu uygulamanın setleri işlediğini söylememeliydim, çünkü değil - ama nokta, özel muhafaza handle_item ile bunu yapmak için kolayca genişletilebileceği ve aynı sürece herhangi bir sınıf veya yinelenebilir nesne için çalışacağı sürece karma işlevini kendiniz yazmak istersiniz. Zor kısmı - çok boyutlu listeler veya sözlükler ile ilgileniyor - zaten burada ele alındı, bu yüzden bu not işlevinin basit bir "Ben sadece yıkanabilir argümanlar alıyorum" türlerinden daha temel olarak çalıştığını gördüm.
RussellStewart

Bahsettiğim sorun, lists ve sets'nin aynı şeye "ayrıştırılması" ve bu nedenle birbirinden ayırt edilemez hale gelmesinden kaynaklanıyor. En setsson güncellemenizde açıklanan destek için örnek kod eklemek korkmamdan kaçınmıyor. Bu ayrı ayrı geçerek [1,2,3]ve {1,2,3}bir "memoize" d test fonksiyonuna bir argüman olarak ve olması gerektiği gibi iki kez çağrılıp çağrılmadığını görerek kolayca görülebilir .
martineau

evet, ben bu problemi okudum, ama değinmedim çünkü bahsettiğiniz diğer sorudan çok daha küçük olduğunu düşünüyorum. En son ne zaman sabit bir argümanın liste veya küme olabileceği ve ikisinin farklı çıktılarla sonuçlandığı bir not fonksiyonu yazdınız? Böyle nadir bir durumla karşılaşacak olsaydınız, başa dönmek için yine handle_item'i yeniden yazardınız, öğe bir kümeyse 0, bir liste ise 1 deyin.
RussellStewart

Aslında, benzer bir mesele lists ve dictbunun nedeni s olası bir için listçağrıda kaynaklanan içinde tam olarak aynı şeyi yapmak make_tuple(sorted(x.items()))bir sözlük için. Her iki durum için de basit bir çözüm, type()oluşturulan demet içerisindeki değerin dahil edilmesi olacaktır . Özellikle sets'yi ele almanın daha basit bir yolunu düşünebilirim , ancak genelleme yapmaz.
martineau

3

Anahtar kelime bağımsız değişkenlerinin geçirildiği sıradan bağımsız olarak hem konumsal hem de anahtar kelime bağımsız değişkenleriyle çalışan çözüm ( inspect.getargspec kullanılarak ):

import inspect
import functools

def memoize(fn):
    cache = fn.cache = {}
    @functools.wraps(fn)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
        key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
        if key not in cache:
            cache[key] = fn(**kwargs)
        return cache[key]
    return memoizer

Benzer soru: Python'da eşdeğer varargs işlevinin tanımlanması


2
cache = {}
def fib(n):
    if n <= 1:
        return n
    else:
        if n not in cache:
            cache[n] = fib(n-1) + fib(n-2)
        return cache[n]

4
if n not in cachebunun yerine sadece kullanabilirsiniz . kullanarak cache.keyspython 2'de gereksiz bir liste oluşturacaksınız
n611x007

2

Sadece verilen cevaplara eklemek istedim, Python dekoratör kütüphanesinin aksine "paylaşılamaz türler" i hatırlayabilen bazı basit ama kullanışlı uygulamaları var functools.lru_cache.


1
Bu dekoratör "paylaşılamaz türleri" hatırlamıyor ! Sadece işlevi hatırlamadan çağırmaya geri döner, açık karşıya karşı çıkmak örtülü dogmadan daha iyidir .
ostrokach
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.