기존 개체 인스턴스에 메서드 추가

python oop methods monkeypatching


파이썬에서 기존 객체 (즉, 클래스 정의가 아닌)에 메서드를 추가 할 수 있다는 것을 읽었습니다.

그렇게하는 것이 항상 좋은 것은 아니라는 것을 이해합니다. 그러나 어떻게 이것을 할 수 있습니까?




Answer 1 Jason Pratt


Python에서는 함수와 바인딩 된 메서드 사이에 차이가 있습니다.

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

바인딩 된 메서드는 인스턴스에 "바인드"되어 있으며 (설명 적) 메서드가 호출 될 때마다 해당 인스턴스가 첫 번째 인수로 전달됩니다.

클래스의 속성 인 콜 러블 (인스턴스와 반대)은 여전히 ​​바인딩되지 않으므로 원할 때마다 클래스 정의를 수정할 수 있습니다.

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

이전에 정의 된 인스턴스도 업데이트됩니다 (속성 자체를 재정의하지 않은 경우).

>>> a.fooFighters()
fooFighters

문제는 단일 인스턴스에 메서드를 연결하려는 경우 발생합니다.

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

이 함수는 인스턴스에 직접 연결될 때 자동으로 바인딩되지 않습니다.

>>> a.barFighters
<function barFighters at 0x00A98EF0>

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

이번에는 클래스의 다른 인스턴스가 영향을받지 않았습니다.

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'




Answer 2 Evgeny


새로운 모듈 은 파이썬 2.6 이후로 사용되지 않으며 3.0에서 제거되었습니다. 유형을 사용하십시오.

참조 http://docs.python.org/library/new.html를

아래 예제에서는 patch_me() 함수 에서 반환 값을 의도적으로 제거했습니다 . 반환 값을 제공하면 패치가 참이 아닌 새 객체를 반환한다고 믿을 수 있다고 생각합니다. 이는 들어오는 객체를 수정합니다. 아마도 이것은 몽키 패칭의보다 체계적인 사용을 용이하게 할 수 있습니다.

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>



Answer 3 Aaron Hall


서문-호환성에 대한 참고 사항 : 다른 답변은 Python 2에서만 작동 할 수 있습니다.이 답변은 Python 2와 3에서 완벽하게 작동합니다. Python 3 만 작성하는 경우 명시 적으로 object 에서 상속 하지 않을 수 있지만 그렇지 않으면 코드는 그대로 유지되어야합니다. 같은.

기존 개체 인스턴스에 메서드 추가

파이썬에서 기존 객체 (예 : 클래스 정의가 아님)에 메서드를 추가 할 수 있다는 것을 읽었습니다.

그렇게하는 것이 항상 좋은 결정은 아니라는 것을 이해합니다. 그러나 어떻게 이것을 할 수 있습니까?

예, 가능합니다.하지만 권장하지 않습니다.

나는 이것을 권장하지 않습니다. 이것은 나쁜 생각입니다. 하지마.

몇 가지 이유가 있습니다.

  • 이 작업을 수행하는 모든 인스턴스에 바인딩 된 개체를 추가합니다. 이 작업을 많이 수행하면 아마도 많은 메모리를 낭비하게 될 것입니다. 바인딩 된 메서드는 일반적으로 호출의 짧은 기간 동안 만 생성되며 자동으로 가비지 수집되면 존재하지 않습니다. 이 작업을 수동으로 수행하면 바인딩 된 메서드를 참조하는 이름 바인딩이 있으므로 사용시 가비지 수집이 방지됩니다.
  • 주어진 유형의 개체 인스턴스는 일반적으로 해당 유형의 모든 개체에 대한 메서드를 갖습니다. 다른 곳에 메서드를 추가하면 일부 인스턴스에는 해당 메서드가 있고 다른 인스턴스에는 없습니다. 프로그래머는 이것을 기대하지 않을 것이며, 최소한 놀라움 의 규칙을 위반할 위험이 있습니다 .
  • 이렇게하지 말아야 할 다른 정말 좋은 이유가 있기 때문에 그렇게하면 자신에게 더 나쁜 평판을 줄 수 있습니다.

따라서 정말 좋은 이유가 없으면 이렇게하지 않는 것이 좋습니다. 클래스 정의에서 올바른 메서드를 정의하는 것이 훨씬 낫 거나 바람직하게는 다음과 같이 클래스를 직접 몽키 패치하는 것이 좋습니다.

Foo.sample_method = sample_method

그러나 그것은 유익하기 때문에 이것을하는 몇 가지 방법을 보여줄 것입니다.

수행 방법

다음은 몇 가지 설정 코드입니다. 클래스 정의가 필요합니다. 가져올 수는 있지만 실제로는 중요하지 않습니다.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

인스턴스를 만듭니다.

foo = Foo()

추가 할 메서드를 만듭니다.

def sample_method(self, bar, baz):
    print(bar + baz)

메서드 없음 (0)-설명자 메서드 __get__ 사용

함수에 대한 점선 조회 는 인스턴스와 함께 함수 의 __get__ 메서드를 호출하여 객체를 메서드에 바인딩하여 "바운드 메서드"를 만듭니다.

foo.sample_method = sample_method.__get__(foo)

그리고 지금:

>>> foo.sample_method(1,2)
3

방법 1-types.MethodType

먼저, 메소드 생성자를 가져올 유형을 가져옵니다.

import types

이제 인스턴스에 메서드를 추가합니다. 이를 위해 위에서 가져온 types 모듈 의 MethodType 생성자가 필요합니다 .

types.MethodType의 인수 시그니처는 (function, instance, class) .

foo.sample_method = types.MethodType(sample_method, foo, Foo)

그리고 사용법 :

>>> foo.sample_method(1,2)
3

방법 2 : 어휘 바인딩

먼저 메서드를 인스턴스에 바인딩하는 래퍼 함수를 ​​만듭니다.

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

usage:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

방법 3 : functools.partial

부분 함수는 첫 번째 인수를 함수 (및 선택적으로 키워드 인수)에 적용하고 나중에 나머지 인수 (및 키워드 인수 재정의)로 호출 할 수 있습니다. 그러므로:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

이는 바인딩 된 메서드가 인스턴스의 부분 함수라고 생각할 때 의미가 있습니다.

객체 속성으로서의 언 바운드 기능-이것이 작동하지 않는 이유 :

sample_method를 클래스에 추가하는 것과 같은 방식으로 추가하려고하면 인스턴스에서 바인딩 해제되고 암시 적 self를 첫 번째 인수로 사용하지 않습니다.

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

인스턴스 (또는이 메서드가 실제로 self 인수 변수를 사용하지 않기 때문에)를 명시 적으로 전달하여 바인딩되지 않은 함수가 작동하도록 만들 수 있지만 다른 인스턴스의 예상 서명과 일치하지 않습니다 (원숭이 인 경우). 이 인스턴스 패치) :

>>> foo.sample_method(foo, 1, 2)
3

Conclusion

이제 당신 은 이것을 할 있는 몇 가지 방법을 알고 있지만, 진지하게-이것을하지 마십시오.




Answer 4 Tomasz Zieliński


위의 답변은 요점을 놓친 것 같습니다.

메서드가있는 클래스를 만들어 보겠습니다.

class A(object):
    def m(self):
        pass

이제 ipython에서 놀아 보겠습니다.

In [2]: A.m
Out[2]: <unbound method A.m>

그래서 m ()은 어떻게 든 A 의 언 바운드 메서드가 됩니다. 하지만 정말 그런가요?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

m ()A 클래스 사전에 추가되는 참조 인 함수일 뿐이라는 것이 밝혀졌습니다 . 마법은 없습니다. 그렇다면 왜 Am 은 우리에게 언 바운드 방법을 제공합니까? 점이 간단한 사전 조회로 번역되지 않았기 때문입니다. 사실상 A .__ class __.__ getattribute __ (A, 'm')의 호출입니다.

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

이제 마지막 줄이 두 번 인쇄되는 이유가 머릿속에서 확실하지 않지만 여전히 거기에서 무슨 일이 일어나고 있는지 분명합니다.

이제 기본 __getattribute__이하는 일은 속성이 소위 설명자 인지 아닌지, 즉 특수한 __get__ 메소드를 구현하는지 확인하는 것입니다. 해당 메서드를 구현하면 반환되는 것은 해당 __get__ 메서드를 호출 한 결과입니다. A 클래스 의 첫 번째 버전으로 돌아 가면 다음과 같습니다.

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

그리고 파이썬 함수는 설명자 프로토콜을 구현하기 때문에 객체 대신 호출되면 __get__ 메서드에서 해당 객체에 자신을 바인딩합니다.

좋아, 기존 객체에 메소드를 추가하는 방법은 무엇입니까? 클래스 패치에 신경 쓰지 않는다고 가정하면 다음과 같이 간단합니다.

B.m = m

그런 다음 Bm 은 설명자 마법 덕분에 언 바운드 메서드가됩니다.

단일 객체에만 메서드를 추가하려면 types.MethodType을 사용하여 직접 기계를 에뮬레이션해야합니다.

b.m = types.MethodType(m, b)

그건 그렇고 :

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>



Answer 5 John Downey


from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak