메모 란 무엇이며 파이썬에서 어떻게 사용할 수 있습니까?


378

방금 파이썬을 시작했는데 메모 가 무엇인지 , 어떻게 사용 하는지 전혀 몰랐 습니다. 또한 간단한 예가 있습니까?


215
관련 위키 백과 기사의 두 번째 문장에 "다항식 시간과 공간에서 모호성과 왼쪽 재귀를 수용하는 일반적인 하향식 파싱 알고리즘 [2] [3]"이라는 문구가 포함 된 경우 SO에게 무슨 일이 일어나고 있는지 묻는 것이 전적으로 적절합니다.
Clueless

10
@Clueless :이 문구 앞에는 "메모 화가 다른 컨텍스트 (및 속도 향상 이외의 목적으로)와 같이 사용되었습니다"가 앞에옵니다. 따라서 이것은 단지 예제 목록 일뿐입니다 (이해할 필요는 없습니다). 메모에 대한 설명의 일부가 아닙니다.
ShreevatsaR

1
@StefanGruenwald 그 링크는 죽었습니다. 업데이트를 찾을 수 있습니까?
JS.

2
pycogsci.info 이후 pdf 파일에 새로운 링크, 다운 : people.ucsc.edu/~abrsvn/NLTK_parsing_demos.pdf
스테판 Gruenwald

4
@Clueless,이 기사는 실제로 " 다항식 시간과 공간에서 모호성과 왼쪽 재귀를 수용하는 일반적인 하향식 파싱 알고리즘 [2] [3]에서 간단한 상호 재귀 강하 분석 [1]"이라고 말합니다. 당신은 simple을 놓쳤으며 , 이는 분명히 그 예를 훨씬 더 명확하게 만듭니다 :).
studgeek

답변:


353

Memoization은 효과적으로 메소드 입력을 기반으로 메소드 호출의 결과를 기억 ( "memoization"→ "memorandum"→ 기억해야 함) 한 다음 결과를 다시 계산하지 않고 기억 된 결과를 리턴합니다. 메소드 결과의 캐시로 생각할 수 있습니다. 자세한 내용은 알고리즘 소개 (3e), Cormen et al.

파이썬에서 메모를 사용하여 계승을 계산하는 간단한 예는 다음과 같습니다.

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]

더 복잡해지고 메모 프로세스를 클래스로 캡슐화 할 수 있습니다.

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]

그때:

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

factorial = Memoize(factorial)

파이썬 데코레이터 에는 " 데코레이터 " 라는 기능 이 추가되어 동일한 기능을 수행하기 위해 다음을 간단히 작성할 수 있습니다.

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

파이썬 데코레이터 라이브러리 라는 유사한 장식이 memoized약간 더 견고보다 Memoize여기에 표시된 클래스를.


2
이 제안에 감사드립니다. Memoize 클래스는 많은 리팩토링없이 기존 코드에 쉽게 적용 할 수있는 우아한 솔루션입니다.
캡틴 렙턴

10
Memoize 클래스 솔루션은 버그가, 그것은 같은 작동하지 않습니다 factorial_memo때문에, factorial내부는 def factorial여전히 이전 unmemoize를 호출합니다 factorial.
adamsmith

9
그건 그렇고, if k not in factorial_memo:보다 쓸 수있는 글을 쓸 수도 있습니다 if not k in factorial_memo:.
ShreevatsaR

5
실제로 이것을 데코레이터로해야합니다.
Emlyn O'Regan

3
@ durden2.0 나는 이것이 오래된 의견이지만 args튜플 이라는 것을 알고있다 . def some_function(*args)args를 튜플로 만듭니다.
Adam Smith

232

Python 3.2의 새로운 기능은 functools.lru_cache입니다. 기본적으로, 그것은 단지 128 개 가장 최근에 사용 된 통화를 캐시하지만 당신은을 설정할 수 있습니다 maxsize위해 None캐시가 만료되지 않도록해야 함을 나타냅니다 :

import functools

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

이 기능 자체는 매우 느리므로 시도해보십시오 fib(36). 약 10 초 정도 기다려야합니다.

lru_cache주석을 추가 하면 특정 값에 대해 함수가 최근에 호출 된 경우 해당 값을 다시 계산하지 않고 캐시 된 이전 결과를 사용합니다. 이 경우 코드 속도가 엄청나게 향상되는 반면 코드는 캐싱의 세부 사항으로 복잡하지 않습니다.


2
RecursionError 가지고 시도 FIB (1000) : 비교를 초과 최대 재귀 깊이
X Æ (A-12)에서

5
@Andyk 기본 Py3 재귀 제한은 1000입니다. 처음 fib호출 될 때 메모가 발생하기 전에 기본 사례로 재귀를 내려야합니다. 그래서 당신의 행동은 거의 예상됩니다.
Quelklef

1
내가 실수하지 않으면 프로세스가 종료되지 않을 때까지만 캐시됩니다. 아니면 프로세스가 종료되었는지 여부에 관계없이 캐시됩니까? 예를 들어 시스템을 다시 시작한다고 가정하면 캐시 된 결과가 계속 캐시됩니까?
Kristada673

1
@ Kristada673 예, 디스크가 아닌 프로세스 메모리에 저장됩니다.
Flimm

2
재귀 함수이고 자체 중간 결과를 캐싱하기 때문에 첫 번째 함수 실행 속도도 빨라집니다. 비재 귀적 기능을 설명하는 것이 좋을 것입니다. : D
endolith '

61

다른 답변은 그것이 무엇인지 잘 다룹니다. 나는 그것을 반복하지 않습니다. 당신에게 도움이 될만한 몇 가지 요점.

일반적으로 메모는 무언가를 계산하고 값이 비싼 모든 함수에 적용 할 수있는 작업입니다. 이 때문에 종종 데코레이터 로 구현됩니다 . 구현은 간단하며 다음과 같습니다.

memoised_function = memoise(actual_function)

또는 데코레이터로 표현

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

18

메모 화는 값 비싼 계산 결과를 유지하고 캐시 된 결과를 지속적으로 다시 계산하지 않고 반환합니다.

예를 들면 다음과 같습니다.

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

더 자세한 설명은 메모 에 대한 wikipedia 항목에서 찾을 수 있습니다 .


흠, 이제 그것이 올바른 파이썬이라면, 그것은 흔들 리 겠지만, ... 괜찮아 보이지 않는 것처럼 보이므로 "캐시"는 받아쓰기가 아닌가? 이 경우 때문에해야 if input not in self.cache 하고 self.cache[input] ( has_key이후 ... 초기 2.X 시리즈, 아닌 경우 2.0되지 않습니다. self.cache(index)결코 정확했다 IIRC.)
위르겐 A. 에어 하드

15

hasattr수공예를 원하는 사람들을 위해 내장 기능을 잊지 마십시오 . 이렇게하면 전역과 달리 함수 정의 내에 mem 캐시를 유지할 수 있습니다.

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]

이것은 매우 비싼 아이디어처럼 보입니다. 모든 n에 대해 n에 대한 결과뿐만 아니라 2 ... n-1에 대한 결과도 캐시합니다.
codeforester

15

나는 이것이 매우 유용하다는 것을 알았습니다.

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)

사용해야 하는 이유는 docs.python.org/3/library/functools.html#functools.wraps 를 참조하십시오 functools.wraps.
anishpatel

1
memo메모리가 확보되도록 수동으로 지워야 합니까?
nos

전체 아이디어는 결과가 세션 내 메모에 저장된다는 것입니다. 즉, 아무 것도 그대로 지워지지 않습니다
mr.bjerre

6

메모 화는 기본적으로 재귀 알고리즘으로 수행 한 과거 작업의 결과를 저장하여 나중에 동일한 계산이 필요한 경우 재귀 트리를 통과 할 필요성을 줄입니다.

참조 http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/를

Python의 피보나치 메모 화 예제 :

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
처음 몇 개의 알려진 값으로 fibcache를 미리 시드하려면 성능을 높이기 위해 코드의 '핫 경로'에서이를 처리하기위한 추가 논리를 취할 수 있습니다.
jkflying

5

메모 화는 함수를 데이터 구조로 변환하는 것입니다. 일반적으로 변환은 점진적이고 느리게 수행되기를 원합니다 (주어진 도메인 요소 또는 "키"의 요구에 따라). 게으른 기능적 언어에서이 게으른 변환은 자동으로 발생할 수 있으므로 메모를 (명시적인) 부작용없이 구현할 수 있습니다.


5

첫 번째 부분에 먼저 대답해야합니다. 메모 란 무엇입니까?

그것은 단지 시간 동안 메모리를 교환하는 방법 일뿐입니다. 생각 곱셈 표 .

파이썬에서 가변 객체를 기본값으로 사용하는 것은 일반적으로 나쁜 것으로 간주됩니다. 그러나 그것을 현명하게 사용한다면 실제로를 구현하는 것이 유용 할 수 있습니다 memoization.

다음은 http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects 에서 수정 된 예입니다.

dict함수 정의에서 변경 가능 을 사용 하여 중간 계산 결과를 캐시 할 수 있습니다 (예 : 계산 factorial(10)후 계산할 때 factorial(9)모든 중간 결과를 재사용 할 수 있음)

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

4

다음은 목록 또는 dict 유형 인수로 작동하지 않는 솔루션입니다.

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

handle_item에서 특별한 경우로 고유 한 해시 함수를 구현하여이 방법을 모든 객체로 자연스럽게 확장 할 수 있습니다. 예를 들어, 입력 인수로 집합을 취하는 함수에이 접근 방식을 적용하려면 handle_item에 추가 할 수 있습니다.

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

1
좋은 시도입니다. 삐걱 거리지 않고의 list인수 [1, 2, 3]는 실수로 set값이 다른 다른 인수와 동일한 것으로 간주 될 수 있습니다 {1, 2, 3}. 또한 세트는 사전과 같이 정렬되지 않으므로이어야 sorted()합니다. 또한 재귀 데이터 구조 인수는 무한 루프를 유발합니다.
martineau

그러나 세트는 특수 케이스 handle_item (x) 및 정렬로 처리해야합니다. 나는이 구현이 세트를 다루지 않는다고 말해서는 안되지만, 요점은 특별한 케이스 handle_item으로 쉽게 확장 할 수 있으며 클래스 또는 반복 가능한 객체에 대해서도 동일하게 작동한다는 것입니다 해시 함수를 직접 작성하려고합니다. 다차원 목록이나 사전을 다루는 까다로운 부분은 이미 여기서 다루어 졌으므로이 메모 기능은 단순한 "해시 가능한 인수 만 사용합니다"유형보다 기본으로 작업하기가 훨씬 쉽다는 것을 알았습니다.
RussellStewart

내가 언급 한 문제는 lists와 sets가 같은 것으로 "튜플 링"되어 서로 구별 할 수 없기 때문에 발생합니다. sets최신 업데이트 에 설명 된 지원을 추가하기위한 예제 코드는 두렵지 않습니다. 이것은 "memoize"d 테스트 함수에 대한 별도의 전달 [1,2,3]{1,2,3}인수로, 그리고 두 번 호출되는지 여부를 두 번 호출하는지 여부 를 쉽게 확인할 수 있습니다 .
martineau

예, 그 문제를 읽었지만 언급 한 다른 문제보다 훨씬 작기 때문에 해결하지 못했습니다. 고정 인수가 목록 또는 집합 일 수있는 메모 화 된 함수를 마지막으로 쓴 시간은 언제였으며 두 결과는 다른 결과를 낳았습니까? 이런 드문 경우가 발생하면 handle_item을 다시 작성하여 앞에 추가하십시오. 요소가 집합이면 0, 목록이면 1입니다.
RussellStewart

사실, 거기에 비슷한 문제의 lists와 dict이 때문에이야 가능 A가에 대한 list호출의 결과 그것은 정확히 같은 것을 가지고 make_tuple(sorted(x.items()))사전에 대한합니다. 두 경우 모두에 대한 간단한 해결책은 type()생성 된 튜플에 값 을 포함시키는 것 입니다. 나는 sets 를 처리하는 더 간단한 방법을 생각할 수 있지만 일반화하지는 않습니다.
martineau

3

inspect.getargspec을 사용하여 키워드 인수가 전달 된 순서와 상관없이 위치 및 키워드 인수와 함께 작동하는 솔루션 :

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

비슷한 질문 : 동등한 varargs 함수를 식별하면 파이썬에서 메모가 필요합니다.


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 cache대신 간단하게 사용할 수 있습니다 . 를 사용 cache.keys하면 파이썬 2에서 불필요한 목록을 만들 수 있습니다
n611x007

2

이미 제공된 답변에 추가하고 싶었던 Python 데코레이터 라이브러리 에는 "해시 불가능한 유형"을 기억할 수있는 간단하면서도 유용한 구현이 있습니다 functools.lru_cache.


1
이 데코레이터는 "unhashable types"를 기억 하지 않습니다 ! 그것은 단지에 대하여가는 메모이 제이션없이 함수를 호출로 다시 떨어질 명시 적으로 더 나은 암시보다 교리.
ostrokach 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.