2011-03-29 5 views
15

저는 파이썬 데코레이터 (와우, 훌륭한 기능!)에 익숙하지 않고, self 인수가 일종의 혼합되어 있기 때문에 다음 작업을 수행하는 데 어려움이 있습니다. 나는이 프로그램을 실행할 때파이썬 데코레이터 자체가 섞여 있습니다.

#this is the decorator 
class cacher(object): 

    def __init__(self, f): 
     self.f = f 
     self.cache = {} 

    def __call__(self, *args): 
     fname = self.f.__name__ 
     if (fname not in self.cache): 
      self.cache[fname] = self.f(self,*args) 
     else: 
      print "using cache" 
     return self.cache[fname] 

class Session(p.Session): 

    def __init__(self, user, passw): 
     self.pl = p.Session(user, passw) 

    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 

s = Session(u,p) 
s.get_something() 

, 내가 얻을 : 나는 self.cache[fname] = self.f(self,*args)

문제 할 라인에 대한

get_something called with self = <__main__.cacher object at 0x020870F0> 
Traceback: 
... 
AttributeError: 'cacher' object has no attribute 'pl' 

- 물론, 문제는 self가 cacher 객체 대신 때문이다 실제로는 pl 속성이없는 Session 인스턴스의 그러나 나는 이것을 해결하는 방법을 찾을 수 없습니다.

솔루션 내가 생각했지만,을 사용할 수 없습니다 - 내가 self 오른쪽 맥락에서 평가 될 수 있도록 데코레이터 클래스 (이 article의 2.1 절에 같은) 대신 값의 함수를 반환하고 생각 , 그러나 그것은 내 decorator가 클래스로 구현되고 build-in __call__ 메서드를 사용하기 때문에 불가능합니다. 그렇다면 나는 이 아니라이 내 장식자를위한 클래스를 사용하여 __call__method를 필요로하지 않는다고 생각했지만 데코레이터 호출간에 상태를 유지해야하기 때문에 할 수 없다. self.cache 속성).

질문 - 그래서, 떨어져 글로벌 cache 사전 변수 (I 시도,하지만 작동합니다 생각하지 않았다)이 장식 작업을 할 다른 방법이를 사용?

편집 :이 SO 질문은 Decorating python class methods, how do I pass the instance to the decorator?

+0

이렇게하면 '세션'의 모든 인스턴스가 동일한 캐시를 공유한다는 것을 알고 계십니까? –

+0

예, 아직 코드를 완성하지는 않았지만 별도의 캐시를 유지하기 위해 어딘가에 여분의 세션 인수가 있다고 생각했습니다. – Rabarberski

답변

27

이 같은 descriptor protocol를 사용하여 비슷한 것 같다

import functools 

class cacher(object): 

    def __init__(self, f): 
     self.f = f 
     self.cache = {} 

    def __call__(self, *args): 
     fname = self.f.__name__ 
     if (fname not in self.cache): 
      self.cache[fname] = self.f(self,*args) 
     else: 
      print "using cache" 
     return self.cache[fname] 

    def __get__(self, instance, instancetype): 
     """Implement the descriptor protocol to make decorating instance 
     method possible. 

     """ 

     # Return a partial function with the first argument is the instance 
     # of the class decorated. 
     return functools.partial(self.__call__, instance) 

편집 :이 작업의 방법

? 우리가 자체로 올바른 인스턴스와 장식 방법에 액세스 할 수 있습니다 데코레이터의 설명 프로토콜을 사용

, 어쩌면 몇 가지 코드가 더 도움이 될 수 있습니다

을 이제 우리가 할 때 :

class Session(p.Session): 
    ... 

    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 
상당에

: 그래서 지금 get_something

class Session(p.Session): 
    ... 

    def get_something(self): 
     print "get_something called with self = %s "% self 
     return self.pl.get_something() 

    get_something = cacher(get_something) 

는 cacher의 인스턴스입니다.우리가 get_something 메소드를 호출 할 때 그래서는 (때문에 기술자 프로토콜의)이 번역됩니다

session = Session() 
session.get_something 
# <==> 
session.get_something.__get__(get_something, session, <type ..>) 
# N.B: get_something is an instance of cacher class. 

때문에 : 희망

session.get_something(*args) 
# <==> 
get_something.__call__(session, *args) 

그래서

session.get_something.__get__(get_something, session, <type ..>) 
# return 
get_something.__call__(session, ...) # the partial function. 

이 의지를 작동 방식을 설명하십시오.

+0

정확히 무슨 일이 일어나는지 이해하기 위해 제공 한 링크의 설명 주위에 두뇌를 감싸야만하지만 실제로 실제로 그렇게합니다. 자동 ('f (self, * args)'에서)'self '는 자동'__get__' 호출을 통해 해결된다고 생각합니다. 그러나 그것은 다른 모든 경우에서 '자기'에 대한 잘못된 해결책을 제공하지 않습니다. 'self.cache [fname]'그리고'self.f' 도요? – Rabarberski

+0

@Rabarberski : 방금 설명을 포함하도록 내 대답을 편집 했으므로 유용 할 것입니다. – mouad

1

먼저, cacher ob 다음 줄에 같은 첫 번째 인수를 ject :

self.cache[fname] = self.f(self,*args) 

파이썬은 자동으로 메소드 만의 인수 목록에 self을 추가합니다. 이 함수는 클래스 네임 스페이스에 정의 된 함수 (예 : cacher 객체와 다른 호출 가능 객체는 아닙니다!)를 메소드로 변환합니다. 이러한 동작을 얻으려면 두 가지 방법이 있습니다.

  1. 클로저를 사용하여 함수를 반환하도록 데코레이터를 변경합니다.
  2. memoize decorator recipe에서와 같이 self 인수를 직접 전달하는 설명자 프로토콜을 구현합니다.
+0

해결책 (1)이 내 게시물에서 제공 한 인수에 대한 옵션이 아니라고 생각합니다. 아니면 내가 잘못한거야? 해결책 (2)는 특이성의 해답과 동일하다. 메모 링크를 보내 주셔서 감사합니다! – Rabarberski

+0

내가 볼 수있는 유일한 인수는 호출간에 상태를 유지하는 것입니다. 물론, 가변 인수 [s]를 통해 데코레이터 기능을 사용할 수 있습니다. –

3

설명자 프로토콜에 대해 신경 쓸 필요가 없기 때문에 클로저가 더 좋은 방법입니다. 변경 가능한 객체에 대한 참조는 nonlocal 키워드를 통해 처리하거나 단일 객체와 같은 변경 가능한 객체에 숨겨서 처리 할 수 ​​있으므로 클래스를 사용하는 것보다 변경 가능한 상태를 저장하는 것이 클래스보다 쉽습니다. 엔트리리스트).

#this is the decorator 
from functools import wraps 
def cacher(f): 
    # No point using a dict, since we only ever cache one value 
    # If you meant to create cache entries for different arguments 
    # check the memoise decorator linked in other answers 
    print("cacher called") 
    cache = [] 
    @wraps(f) 
    def wrapped(*args, **kwds): 
     print ("wrapped called") 
     if not cache: 
      print("calculating and caching result") 
      cache.append(f(*args, **kwds)) 
     return cache[0] 
    return wrapped 

class C: 
    @cacher 
    def get_something(self): 
     print "get_something called with self = %s "% self 

C().get_something() 
C().get_something() 

당신이 길을 폐쇄 작업에 완전히 익숙하지 않은 (내가 위에서 가지고) 더 인쇄 문을 추가하는 경우

는 설명 할 수있다. 함수가 정의되었으므로 cacher 만 호출되지만 메서드가 호출 될 때마다 wrapped이 호출됩니다.

이것은 메모 기법과 인스턴스 메소드에주의해야하는 방법을 강조합니다. self 값의 변경 사항을 고려하지 않으면 인스턴스간에 캐시 된 답변을 공유하게됩니다. 너가 원하는거야.

+0

흠, 나는 그것을 얻지 않는다. 캐시 변수 내용은 호출간에 보존되지 않습니다 (아니오). 나는'랩 '을 찾았지만, 과부하로 고통 받았다고 생각합니다. 부분적인() (이전 답으로부터의) 의미와 유용성이 서서히 흐릅니다.마침내, dict은'cacher '를 적용한 각 함수에 대해 다른 캐시 (키)를 원하기 때문에 필요합니다. – Rabarberski

+0

아니요. 'cacher'가 호출 될 때 캐시가 생성되기 때문에 함수 정의시에만 발생합니다. 'wrapped '가 동시에 생성되고,'wrapped'의 모든 호출은 같은 원래의'cache' 정의를 볼 것입니다. 'wraps()'는'__name__','__doc__' 및'f'에서'wrapped'까지 다른 내용을 복사하여 장식 된 함수가 원본과 비슷하게 보이도록 도와줍니다. – ncoghlan

+0

데코레이터가 일반적으로 어떻게 작동하는지에 대한이 설명은 도움이 될 수 있습니다. http://stackoverflow.com/questions/5481739/how-does-this-python-decorator-work – ncoghlan

관련 문제