2013-01-02 2 views
4

파이썬에서 관련 메소드 데코레이터의 패밀리를 만드는 법을 배우기 위해 나는 좋은 파이썬의 방법을 생각하는 데 어려움을 겪고있다.파이썬에서 서브 클래 싱 메소드 데코레이터

서로 일치하지 않는 목표는 데코 레이팅 된 메서드가 바인딩 된 인스턴스의 데코레이터 특성과 특성에 모두 액세스 할 수 있기를 원한다는 것 같습니다. 여기에 내가의 의미는 다음과 같습니다

from functools import wraps 

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(decorator_self, *args, **kwargs) 

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

if __name__ == "__main__": 
    u = UsefulObject("balloons") 
    u.red() 

물론 물론 항상이 작업을 얻을 수있는 방법이 있다는 것을

My apartment was infested with koalas... 
AttributeError: 'SillyDecorator' object has no attribute 'noun' 

주를 생산한다. 예를 들어, 충분한 인수를 가진 팩토리를 사용하면 SillyDecorator의 일부 생성 된 인스턴스에 메소드를 연결할 수 있습니다. 그러나 상속으로이를 수행 할 수있는 합리적인 방법이 있는지 궁금해합니다.

답변

2

@miku는 설명자 프로토콜을 사용하는 핵심 아이디어를 가졌습니다. 데코레이터 객체를 "유용한 객체"와 분리하여 유지하는 세련미가 있습니다. 기본 객체에 데코레이터 정보를 저장하지 않습니다. 당신이 장식 된 방법 및 바인드 된 오브젝트 모두에 대한 액세스를 제공하는 일이기 때문에

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, obj_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(obj_self, *args, **kwargs) 

    def __get__(decorator_self, obj_self, objtype): 
     return functools.partial(decorator_self.__call__, obj_self)  

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
My apartment was infested with koalas... 
red balloons 

기술자 프로토콜, 여기에 열쇠이다. __get__ 내부에서 유용한 객체 ID (obj_self)를 추출하여 __call__ 메소드로 전달할 수 있습니다.

의 속성으로 obj_self을 저장하는 것이 아니라 functools.partial (또는 일부 메커니즘)을 사용하는 것이 중요하다는 점에 유의하십시오. 데코 레이팅 된 메서드가 클래스에 있으므로 SillyDecorator 인스턴스가 하나만 존재합니다. 이 SillyDecorator 인스턴스를 사용하여 유용한 객체 인스턴스 특정 정보를 저장할 수 없습니다. 즉, 여러 개의 UsefulObject를 작성하고 즉시 호출하지 않고 데코 레이팅 된 메소드에 액세스하면 이상한 오류가 발생할 수 있습니다.

더 쉬운 방법이있을 수 있습니다. 귀하의 예에서는 데코레이터에 소량의 정보 만 저장하므로 나중에 변경할 필요가 없습니다. 그렇다면 decorator-maker 함수를 사용하는 것이 더 간단 할 것입니다. 하나 이상의 인수 (또는 인수)를 사용하고 데코레이터를 반환하는 함수입니다.이 함수의 동작은 해당 인수에 의존 할 수 있습니다. 다음은 예입니다 :

def decoMaker(msg): 
    def deco(func): 
     @wraps(func) 
     def wrapper(*args, **kwargs): 
      print msg 
      return func(*args, **kwargs) 
     return wrapper 
    return deco 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @decoMaker('koalas...') 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
koalas... 
red balloons 

당신이 장식하게 메시지를 매번 다시 입력하지 않으려면, 나중에 다시 사용하기 위해 장식을 만들기 위해 미리의 decoMaker를 사용할 수 있습니다

sillyDecorator = decoMaker("Some really long message about koalas that you don't want to type over and over") 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @sillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
Some really long message about koalas that you don't want to type over and over 
red balloons 

다른 종류의 decoratort에 대해 전체 클래스 상속 트리를 작성하는 것보다 훨씬 덜 장황 함을 알 수 있습니다.모든 종류의 내부 상태 (어쨌든 혼란 스러울 수 있음)를 저장하는 매우 복잡한 데코레이터를 작성하지 않는 한,이 데코레이터 제조업체 접근 방식은 더 쉬운 방법 일 수 있습니다.

+0

대단하군요! 나는 기술자들에 대해 머리 글자를 더 잘 감쌀 필요가 있다고 생각한다. 데코레이터 제작자에 관해서는 : 그렇습니다. 그것이 제가 끝내 준 것입니다. 문제는 제가 인수로 전달한 것이 함수라는 것입니다. 데코레이터의 각 버전마다 여러 함수 정의가있었습니다. 그래서 최상위 레벨에 정의 된 생성자 메서드 정의를 공급하고 내가 논리적으로 매우 많은 상속을 받았을 때 생성자에 연결하는 해킹을 느꼈습니다. –

2

적응 : http://metapython.blogspot.de/2010/11/python-instance-methods-how-are-they.html. 이 변형은 대상 인스턴스의 속성을 설정하므로 검사를하지 않아도 대상 인스턴스 속성을 덮어 쓸 수 있습니다. 아래 코드는이 경우에 대한 검사를 포함하지 않습니다.

또한이 예에서는 punctuation 특성을 명시 적으로 설정합니다. 보다 일반적인 클래스는 속성을 자동 발견 할 수 있습니다.

from types import MethodType 

class AbstractDecorator(object): 
    """Designed to work as function or method decorator """ 
    def __init__(self, function): 
     self.func = function 
     self.punctuation = '...' 
    def __call__(self, *args, **kw): 
     self.setup() 
     return self.func(*args, **kw) 
    def __get__(self, instance, owner): 
     # TODO: protect against 'overwrites' 
     setattr(instance, 'punctuation', self.punctuation) 
     return MethodType(self, instance, owner) 

class SillyDecorator(AbstractDecorator): 
    def setup(self): 
     print('[setup] silly init %s' % self.punctuation) 

class UsefulObject(object): 
    def __init__(self, noun='cat'): 
     self.noun = noun 

    @SillyDecorator 
    def d(self): 
     print('Hello %s %s' % (self.noun, self.punctuation)) 

obj = UsefulObject() 
obj.d() 

# [setup] silly init ... 
# Hello cat ... 
+0

이것은 인스턴스에 속성을 설정하므로 (즉, 해당 이름의 기존 속성을 덮어 쓸 수 있기 때문에) 완전히 동일하지는 않습니다. 그러나 설명자 프로토콜은 확실히 갈 길이 멀다. – BrenBarn

+0

@BrenBarn, 네, 그 사실을 지적하고 그것을 지적 해 주셔서 감사합니다. 게시물에 짧은 메모를 추가했습니다. – miku

+0

그 제한을 피할 수 있습니다, 내 대답을 참조하십시오. – BrenBarn