2014-09-29 2 views
3

메소드 호출을 객체 (래퍼)에서 객체의 멤버 변수 (wrappee)로 전달하는 방법을 찾고 있습니다. 잠재적으로 외 부화 될 필요가있는 많은 메소드가 있으므로 wrappee에 메소드를 추가 할 때 랩퍼의 인터페이스를 변경하지 않고이를 수행하는 f}이 도움이됩니다. 이 호 재 지정 (즉, 너무 많은 오버 헤드를 추가하지 직접 호출에 대해) "빠른"이면Python의 프록시 객체

class Wrapper(object) 
    def __init__(self, wrappee): 
    self.wrappee = wrappee 

    def foo(self): 
    return 42 

class Wrappee(object): 
    def bar(self): 
    return 12 

o2 = Wrappee() 
o1 = Wrapper(o2) 

o1.foo() # -> 42 
o1.bar() # -> 12 
o1.<any new function in Wrappee>() # call directed to this new function 

좋을 것이다.

+0

왜 상속되지 않습니까? –

+0

두 개체를 서로 다른 상황에서 격리하여 사용하기 때문에. – orange

답변

5

다소 우아한 해결책은 래퍼 클래스에 "속성 프록시"를 만드는 것입니다 : 모든 마법 메소드 또는 속성에 액세스하려고합니다 Wrapper 클래스의 __getattr__ 방법에서 발생

class Wrapper(object): 
    def __init__(self, wrappee): 
     self.wrappee = wrappee 

    def foo(self): 
     print 'foo' 

    def __getattr__(self, attr): 
     return getattr(self.wrappee, attr) 


class Wrappee(object): 
    def bar(self): 
     print 'bar' 

o2 = Wrappee() 
o1 = Wrapper(o2) 

o1.foo() 
o1.bar() 

Wrapper 인스턴스에 있고 존재하지 않으면 래핑 된 인스턴스에서 시도합니다. 당신이 중 하나의 클래스에 존재하지 않는 속성에 액세스하려고하면

, 당신이 얻을 것이다 : 당신이 정말이 빠르게해야하는 경우

o2.not_valid 
Traceback (most recent call last): 
    File "so.py", line 26, in <module> 
    o2.not_valid 
    File "so.py", line 15, in __getattr__ 
    raise e 
AttributeError: 'Wrappee' object has no attribute 'not_valid' 
+1

고마워, 나는이 해결책을 좋아한다. – orange

+0

나는 또한 내 목적을 잘 수행하는 this (http://code.activestate.com/recipes/496741-object-proxying/)를 발견했다. – orange

+0

'try'/except'에서 같은 예외를 발생시키는 것은 무엇입니까? 또한 왜'raise' 대신에'raise e'를 사용해야합니까? Python 버전에 따라 예외 기록을 잃어 버리거나 속도가 느려지거나 정확하게 동일 할 수 있지만 아무 것도 개선 할 수있는 방법이 없습니다. – abarnert

5

, 가장 빠른 옵션이 자신을 monkeypatch하는 것입니다 초기화시 :

def __init__(self, wrappee): 
    for name, value in inspect.getmembers(wrappee, callable): 
     if not hasattr(self, name): 
      setattr(self, name, value) 

이 정상적인 데이터 값을 가진 Wrappee 방법을 결합하는 특성을 당신의 Wrapper 인스턴스를 제공 할 것입니다. 그것은 엄청나게 빠르다. 그렇지?

class WrapperA(object): 
    def __init__(self, wrappee): 
     self.wrappee = wrappee 
     for name, value in inspect.getmembers(wrappee, callable): 
      if not hasattr(self, name): 
       setattr(self, name, value) 

class WrapperB(object): 
    def __init__(self, wrappee): 
     self.wrappee = wrappee 
    def __getattr__(self, name): 
     return getattr(self.wrappee, name) 

In [1]: %run wrapper 
In [2]: o2 = Wrappee() 
In [3]: o1a = WrapperA(o2) 
In [4]: o1b = WrapperB(o2) 
In [5]: %timeit o2.bar() 
10000000 loops, best of 3: 154 ns per loop 
In [6]: %timeit o1a.bar() 
10000000 loops, best of 3: 159 ns per loop 
In [7]: %timeit o1b.bar() 
1000000 loops, best of 3: 879 ns per loop 
In [8]: %timeit o1b.wrapper.bar() 
1000000 loops, best of 3: 220 ns per loop 

따라서 바인딩 된 메서드를 복사하는 데 3 %의 비용이 듭니다. 이보다 더 동적 인 것이면 최소 66 %의 오버 헤드가있는 self.wrapper에서 속성을 가져와야합니다. 평소 __getattr__ 솔루션은 471 %의 오버 헤드를 가지고 있습니다 (그리고 불필요한 여분의 물건을 추가하면 느려질 수 있습니다).

그렇다면 바운드 방식의 해킹에 대한 개방적이고 단호한 승리라고 할 수 있습니다. 맞습니까?

반드시 그렇지는 않습니다. 471 %의 오버 헤드는 여전히 700 나노초입니다. 실제로 코드에서 차이를 만들 예정입니까? 아마도 딱딱한 루프 안에서 사용되지 않는 한 아마 그렇지 않을 것입니다.이 경우 어쨌든 메소드를 로컬 변수에 복사하려고 할 것입니다.

그리고이 해킹의 많은 단점이 있습니다. 그것은 "그것을하는 한 가지 분명한 방법"이 아닙니다. 인스턴스 dict에서 조회되지 않는 특수 메소드에는 작동하지 않습니다. 정적으로 애트리뷰트를 o2에서 꺼내므로 나중에 새로운 애셋을 만들면 o1이 프록시가되지 않습니다. 이렇게하면 프록시의 동적 체인을 만들 수 있습니다. 프록시가 많은 경우 많은 메모리를 낭비합니다. __getattr__은 2.3에서 현재까지 똑같이 유지되고있는 반면에 (그리고 만약 당신이 inspect에 의존한다면 2.x와 3.x 시리즈 내에서조차도) 파이썬 2.x와 3.x에서 약간 다릅니다 다른 파이썬 구현에서). 등등.

속도가 정말로 필요한 경우 프록시 방법을 캐시하는 하이브리드 : __getattr__ 메서드를 고려할 수 있습니다. 한 번 호출되는 무언가, 클래스 속성에 언 바운드 메소드를 캐시하고 즉시 바인딩 할 수 있습니다. 그런 다음 반복적으로 호출되는 경우 바인딩 된 메서드를 인스턴스 특성에 캐시합니다.

+0

+1. 마지막 코멘트에 일종의 메모를하는 것처럼 들리십니까? – orange

+0

@orange : 정확합니다. 메모 작성을 사용하면 유연성이 떨어지거나 (일부) 메모리 비용이 들지 않고 바인딩 된 메서드를 숨길 수있는 속도를 얻을 수 있습니다. 또한 메서드를 호출 할 때와 거의 비슷한 빈도로 개체를 만드는 경우 시작 비용이 발생할 수 있습니다. 그러나 이것이 실제로 성과 핫스팟 인 경우에만; 제가 말했듯이, 대개는 그렇지 않을 것입니다. 그렇다면 어쨌든 (호출 지점에서 로컬에 바인딩 된 메서드를 복사하는) 대개 다른 최적화를 원합니다. – abarnert

관련 문제