[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>

이를 바인딩하기 위해 types 모듈에서 MethodType 함수를 사용할 수 있습니다 .

>>> 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'

디스크립터메타 클래스 프로그래밍대한 자세한 내용은 자세한 내용을 참조하십시오 .


답변

새로운 모듈 은 파이썬 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'>


답변

서문-호환성에 대한 참고 사항 : 다른 답변은 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)

메소드 naught (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

용법:

>>> 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를 클래스에 추가 할 때와 같은 방법으로 추가하려고하면 인스턴스에서 바인딩되지 않으며 암시 적 자체를 첫 번째 인수로 사용하지 않습니다.

>>> 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

결론

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


답변

위의 답변이 요점을 놓쳤다 고 생각합니다.

메소드가있는 클래스를 보자.

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은 () 에 추가 참조되는 단지 기능입니다 마술은 없다 – 클래스 사전. 그렇다면 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'>


답변

파이썬에서 원숭이 패치는 일반적으로 자신의 클래스 또는 함수 서명을 덮어 쓰면 작동합니다. 다음은 Zope Wiki 의 예입니다 .

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

이 코드는 클래스에서 speak라는 메소드를 덮어 쓰거나 작성합니다. 원숭이 패치에 관한 Jeff Atwood의 최근 게시물에서 . 그는 현재 업무에 사용하는 언어 인 C # 3.0의 예를 보여줍니다.


답변

람다를 사용하여 메소드를 인스턴스에 바인딩 할 수 있습니다.

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

산출:

This is instance string


답변

메소드없이 인스턴스에 메소드를 첨부하는 방법은 두 가지 이상 있습니다 types.MethodType.

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>>
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2 :

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

유용한 링크 :
데이터 모델-디스크립터 호출 설명자
사용법 안내서-디스크립터 호출