python - 파이썬 - 기존 객체 인스턴스에 메소드 추가




파이썬__ dict__ (11)

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

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


이것은 실제로 "Jason Pratt"의 답변에 대한 애드온입니다

Jason의 답변은 작동하지만 클래스에 함수를 추가하려는 경우에만 작동합니다. .py 소스 코드 파일에서 이미 기존 메소드를 다시로드하려고 시도했을 때 작동하지 않았습니다.

해결 방법을 찾는 데 오랜 시간이 걸렸지 만 트릭은 간단 해 보입니다 ... 1. 소스 코드 파일에서 코드를 가져옵니다 2.nd reload 3.rd use types.FunctionType (...) 재로드 된 메소드가 다른 네임 스페이스에 있기 때문에 현재 전역 변수를 전달할 수있는 함수에 가져 오기 및 바인딩 된 메소드도 있습니다. 이제는 "Jason Pratt"에 의해 types.MethodType (... )

예:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code

Jason Pratt가 게시 한 내용이 정확합니다.

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

보시다시피, 파이썬은 b ()를 a ()와 다른 것으로 간주하지 않습니다. 파이썬에서 모든 메소드는 함수가되는 변수 일뿐입니다.


당신이 찾고있는 것은 내가 믿는 setattr 입니다. 이것을 사용하여 객체의 속성을 설정하십시오.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>

도움이 될 수 있다면 최근 고릴라라는 파이썬 라이브러리를 출시하여 원숭이 패치 프로세스를보다 편리하게 만들었습니다.

함수 needle() 을 사용하여 guineapig 라는 모듈을 패치하면 다음과 같습니다.

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

또한 FAQ FAQ 에서 볼 수 있듯이보다 흥미로운 사용 사례도 처리 documentation .

코드는 GitHub 사용할 수 GitHub .


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

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

결론

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


위에서 언급 한 모든 메소드가 추가 된 메소드와 인스턴스 사이에 사이클 참조를 생성하여 가비지 수집까지 객체가 지속된다고 언급 한 사람은 아무도 없습니다. 객체의 클래스를 확장하여 설명자를 추가하는 오래된 트릭이있었습니다.

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass

이 질문은 몇 년 전에 시작되었지만 데코레이터를 사용하여 클래스 인스턴스에 함수의 바인딩을 시뮬레이션하는 쉬운 방법이 있습니다.

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

함수와 인스턴스를 바인더 데코레이터에 전달하면 첫 번째 함수와 동일한 코드 객체를 사용하여 새 함수가 생성됩니다. 그런 다음 지정된 클래스 인스턴스가 새로 작성된 함수의 속성에 저장됩니다. 데코레이터는 복사 된 함수를 자동으로 호출하는 (세번째) 함수를 반환하여 인스턴스를 첫 번째 매개 변수로 제공합니다.

결론적으로 클래스 인스턴스에 바인딩하는 것을 시뮬레이트하는 함수를 얻습니다. 원래 기능을 그대로 유지


이 질문은 비 Python 버전을 요청했기 때문에 JavaScript는 다음과 같습니다.

a.methodname = function () { console.log("Yay, a new method!") }

파이썬에서는 함수와 바운드 메소드간에 차이가 있습니다.

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

descriptorsmetaclass programmingdescriptors 자세한 내용은 자세한 내용을 참조하십시오.


형식이없는 인스턴스에 메서드를 연결하는 데는 두 가지 방법이 있습니다.

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

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


from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

이것으로, 당신은 자기 포인터를 사용할 수 있습니다





monkeypatching