2012-06-06 2 views
6

그래서 저는 최근에 메모 작성에 관한 질문을하고 큰 답을 얻었습니다. 이제 나는 그것을 다음 단계로 가져 가고 싶습니다. 꽤 많은 인터넷 검색 결과를 바탕으로, 키워드 인수를 취한 함수를 캐시 할 수있는 메모 데코레이터의 참조 구현을 찾을 수 없었습니다. 실제로 대부분의 사람들은 단순히 캐시 조회를위한 키로 *args을 사용 했으므로 목록이나 dicts를 인수로 허용하는 함수를 메모하려는 경우에도 손상 될 수 있습니다.파이썬에서 메모 데코레이터의 키워드 인수를 지원하는 파이썬적인 방법이 있습니까?

필자의 경우 함수의 첫 번째 인수는 캐시 조회를위한 dict 키로 사용하기에 적합한 고유 한 식별자이지만 키워드 인수를 사용하고 동일한 캐시에 계속 액세스 할 수 있기를 원했습니다. 내 말은 my_func('unique_id', 10)my_func(foo=10, func_id='unique_id') 모두 동일한 캐시 된 결과를 반환해야한다는 것입니다.

이 작업을 수행하려면 '첫 번째 인수에 해당하는 키워드에 대해 kwargs를 검사하십시오.'라는 말도 안되는 방법이 필요합니다. 이것은 내가 생각해 낸 것입니다 :

class memoize(object): 
    def __init__(self, cls): 
     if type(cls) is FunctionType: 
      # Let's just pretend that the function you gave us is a class. 
      cls.instances = {} 
      cls.__init__ = cls 
     self.cls = cls 
     self.__dict__.update(cls.__dict__) 

    def __call__(self, *args, **kwargs): 
     """Return a cached instance of the appropriate class if it exists.""" 
     # This is some dark magic we're using here, but it's how we discover 
     # that the first argument to Photograph.__init__ is 'filename', but the 
     # first argument to Camera.__init__ is 'camera_id' in a general way. 
     delta = 2 if type(self.cls) is FunctionType else 1 
     first_keyword_arg = [k 
      for k, v in inspect.getcallargs(
       self.cls.__init__, 
       'self', 
       'first argument', 
       *['subsequent args'] * (len(args) + len(kwargs) - delta)).items() 
        if v == 'first argument'][0] 
     key = kwargs.get(first_keyword_arg) or args[0] 
     print key 
     if key not in self.cls.instances: 
      self.cls.instances[key] = self.cls(*args, **kwargs) 
     return self.cls.instances[key] 

이것은 실제로 작동한다는 것입니다. 예, 당신은 다음과 같이 장식하는 경우 :

@memoize 
class FooBar: 
    instances = {} 

    def __init__(self, unique_id, irrelevant=None): 
     print id(self) 

그런 다음 코드에서 당신이 FooBar('12345', 20) 또는 FooBar(irrelevant=20, unique_id='12345')중 하나가 실제로는 foobar의 동일한 인스턴스를 얻을 호출 할 수 있습니다. 그런 다음 첫 번째 인수에 대해 다른 이름을 가진 다른 클래스를 정의 할 수 있습니다. 일반적인 방식으로 작동하기 때문입니다 (예 : 데코레이터가이 클래스가 작동하도록 데코 레이팅하는 클래스에 대해 특정 정보를 알 필요가 없음).

문제가

, 그것은 경건 치 못한 혼란 ;-) inspect.getcallargs 당신이 그것을 제공 한 인수에 정의 된 키워드를 매핑 사전인가를 반환하기 때문에 의미가 있습니다

, 그래서 좀 가짜 인수를 공급하고 다음의 DICT를 검사 통과 된 첫 번째 인수

그런 것들이 존재한다면, 두 가지 종류의 인수를 반환하는 inspect.getcallargs의 유사어가 키워드 인수의 사전이 아니라 인수 목록으로 통합됩니다. 즉,이 같은 수있는 것 :

def __call__(self, *args, **kwargs): 
    key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1] 
    if key not in self.cls.instances: 
     self.cls.instances[key] = self.cls(*args, **kwargs) 
    return self.cls.instances[key] 

나는이 직접 조회 캐시 키로 inspect.getcallargs에서 제공하는 DICT를 사용하는 것입니다 태클로 볼 수있는 다른 방법으로, 그러나 그것은에서 동일한 문자열을 만들기 위해 반복적 인 방법을 필요로 (열쇠를 정렬 한 후에 문자열을 직접 만들어야한다고 생각합니다.) 동일한 해시가 있습니다.

아무도 이것에 대한 의견이 있습니까? 키워드 인수가있는 함수를 호출하고 결과를 캐시하려는 것은 잘못입니까? 아니면 그냥 아주 어려운가?

답변

4

2.X으로 백 포트 될 수

import inspect 

class key_memoized(object): 
    def __init__(self, func): 
     self.func = func 
     self.cache = {} 

    def __call__(self, *args, **kwargs): 
     key = self.key(args, kwargs) 
     if key not in self.cache: 
      self.cache[key] = self.func(*args, **kwargs) 
     return self.cache[key] 

    def normalize_args(self, args, kwargs): 
     spec = inspect.getargs(self.func.__code__).args 
     return dict(kwargs.items() + zip(spec, args)) 

    def key(self, args, kwargs): 
     a = self.normalize_args(args, kwargs) 
     return tuple(sorted(a.items())) 

예 :

@key_memoized 
def foo(bar, baz, spam): 
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam) 
    return bar + baz + spam 

print foo(1, 2, 3) 
print foo(1, 2, spam=3)   #memoized 
print foo(spam=3, baz=2, bar=1) #memoized 

주 보다 구체적인 메모 작성 전략을 제공하기 위해 key_memoized을 확장하고 key() 메서드를 재정의 할 수도 있습니다. 예를 들어 인수의 일부를 무시 :

재미있는
class memoize_by_bar(key_memoized): 
    def key(self, args, kwargs): 
     return self.normalize_args(args, kwargs)['bar'] 

@memoize_by_bar 
def foo(bar, baz, spam): 
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam) 
    return bar 

print foo('x', 'ignore1', 'ignore2') 
print foo('x', 'ignore3', 'ignore4') 
+0

이 모양이 마음에 들지만'key'가'tuple (a.items()) '를 반환하는 부분에 대해서는 걱정이됩니다. 그것은 분명하지만 똑같은 dicts에 대해 동일한 순서로 키를 정렬 할 수 있습니까? 나는 dict의 순서가 매겨지지 않고 동일한 입력이 주어지면 반복 가능한 문자열을 생성하기 위해'str ({1 : 2,3 : 4}) '과 같은 것에 의존한다고 들었습니다. Very Very Wrong. – robru

+0

'inspect.getargspec (func) .args [0]'은 내가 물어 본 특정 질문 (첫 번째 인수의 이름을 찾는 방법)에 대한 정확한 대답 인 것으로 보이지만 이것을 보다 일반적인 해결책. 내가 그것을 기교있게 할 시간이 있으면 나중에 결과를 게시 할 것입니다. – robru

+0

@Robru : dict 정렬에 대한 좋은 지적. 'tuple (sorted (a.items()))'(다른 옵션은'frozenset (a.items())')으로 변경되었습니다. – georg

3

lru_cache을 시도해보십시오

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

데코레이터가 MAXSIZE 가장 최근 통화까지 저장하는 memoizing 호출과 기능을 포장. 값 비싼 I/O 바운드 기능이 동일한 인수로 주기적으로 호출 될 때 시간을 절약 할 수 있습니다.

lru_cache는 파이썬 3.2에 추가 된, 그러나 나는 다음과 같은 좋을 것

+0

에 대해 읽을 수있는,하지만 난 캐시 된 인스턴스를 반복 할 수 있어야 클래스 staticmethods을 가지고 있기 때문에 불행히도 내 상황에서 작동하지 않기 때문에 캐시 자체가 필요 클래스 속성으로 노출 될 수 있습니다. – robru

관련 문제