Сведения о вопросе

PASHA

16:03, 1st July, 2020

Теги

python   oop   methods   monkeypatching    

Добавление метода к существующему экземпляру объекта

Просмотров: 584   Ответов: 17

Я читал, что можно добавить метод к существующему объекту (т. е. не в определении класса) в Python.

Я понимаю, что это не всегда хорошо делать. Но как это сделать?



  Сведения об ответе

PAGE

18:03, 1st July, 2020

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

Привязанные методы были "bound" (как описательные) к экземпляру, и этот экземпляр будет передаваться в качестве первого аргумента при каждом вызове метода.

Вызываемые объекты, являющиеся атрибутами класса (в отличие от экземпляра), по-прежнему не связаны, поэтому вы можете изменять определение класса в любое время:

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

Чтобы связать его, мы можем использовать функцию MethodType в модуле types :

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

Дополнительную информацию можно найти, прочитав о дескрипторах и программировании метаклассов .


  Сведения об ответе

padenie

18:03, 1st July, 2020

Новый модуль является устаревшим, поскольку python 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'>


  Сведения об ответе

P_S_S

18:03, 1st July, 2020

Предисловие-примечание по совместимости: другие ответы могут работать только в Python 2 - этот ответ должен отлично работать в Python 2 и 3. Если вы пишете только Python 3 , Вы можете оставить явное наследование от object, но в противном случае код должен оставаться тем же самым.

Добавление метода к существующему экземпляру объекта

Я читал, что можно добавить метод к существующему объекту (например, не в определении класса) в Python.

Я понимаю, что это не всегда хорошее решение. Но как это можно сделать?

Да, это возможно , но не рекомендуется

Я не рекомендую этого делать. Это плохая идея. Не делай этого.

Вот вам несколько причин:

  • Вы добавите привязанный объект к каждому экземпляру, с которым вы это сделаете. Если вы будете делать это часто,вы, вероятно, потеряете много памяти. Связанные методы обычно создаются только на короткое время их вызова, а затем они прекращают свое существование при автоматическом сборе мусора. Если вы сделаете это вручную, у вас будет привязка имени, ссылающаяся на связанный метод, что предотвратит его сборку мусора при использовании.
  • Экземпляры объектов данного типа обычно имеют свои методы для всех объектов этого типа. Если вы добавите методы в другом месте, некоторые экземпляры будут иметь эти методы, а другие-нет. Программисты этого не ожидают, и вы рискуете нарушить правило наименьшего удивления .
  • Поскольку есть и другие действительно веские причины не делать этого, вы дополнительно создадите себе плохую репутацию, если сделаете это.

Таким образом, я предлагаю вам не делать этого, если у вас нет действительно веской причины. Гораздо лучше определить правильный метод в определении класса или, что менее предпочтительно, непосредственно исправить класс, как это:

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)

Метод nought (0) - используйте метод дескриптора, __get__

Точечные поиски функций вызывают метод __get__ функции с экземпляром, привязывая объект к методу и таким образом создавая "bound method."

foo.sample_method = sample_method.__get__(foo)

а теперь ... :

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

Способ первый-types.MethodType

Во-первых, импортируйте типы, из которых мы получим конструктор метода:

import types

Теперь мы добавим метод к экземпляру. Для этого нам потребуется конструктор MethodType из модуля types (который мы импортировали выше).

Сигнатура аргумента для types.MethodType - это (function, instance, class) :

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

и использование:

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

Способ второй: лексическое связывание

Во-первых, мы создаем функцию-оболочку, которая связывает метод с экземпляром:

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

Метод третий: 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

Вывод

Теперь вы знаете несколько способов, которыми вы могли бы это сделать, но со всей серьезностью - не делайте этого.


  Сведения об ответе

repe

18:03, 1st July, 2020

Я думаю, что приведенные выше ответы упустили ключевой момент.

Давайте создадим класс с методом:

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.m дает нам несвязанный метод? Это потому, что точка не переводится в простой поиск по словарю. Это де-факто вызов 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

Теперь я не совсем понимаю, почему последняя строка напечатана дважды, но все равно ясно, что там происходит.

Теперь, что делает default _ _ getattribute__, так это проверяет, является ли атрибут так называемым дескриптором или нет, т. е. реализует ли он специальный метод __get__. Если он реализует этот метод,то возвращается результат вызова этого метода__ get__. Возвращаясь к первой версии нашего класса А, вот что мы имеем:

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

И поскольку функции Python реализуют протокол дескриптора, если они вызываются от имени объекта,они связывают себя с этим объектом в своем методе__ get__.

Итак, как добавить метод к существующему объекту? Предполагая, что вы не возражаете против исправления класса, это так же просто, как:

B.m = m

Затем B.m "becomes" несвязанный метод, благодаря магии дескриптора.

А если вы хотите добавить метод только к одному объекту, то вам придется эмулировать механизм самостоятельно, используя 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'>


  Сведения об ответе

PIRLO

18:03, 1st July, 2020

В Python monkey patching обычно работает путем перезаписи сигнатуры класса или функции своей собственной. Ниже приведен пример из Zope Wiki :

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

Этот код будет перезаписывать / создавать метод с именем speak в классе. В недавнем посте Джеффа Этвуда о латании обезьян . Он показывает пример в C# 3.0, который является текущим языком, который я использую для работы.


  Сведения об ответе

dumai

18:03, 1st July, 2020

Существует по крайней мере два способа прикрепить метод к экземпляру без 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>

Один:

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

Два:

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

Полезные ссылки:
Модель данных-вызывающие дескрипторы
Дескриптор HowTo руководство-вызов дескрипторов


  Сведения об ответе

SSESION

18:03, 1st July, 2020

Вы можете использовать lambda для привязки метода к экземпляру:

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


  Сведения об ответе

$DOLLAR

18:03, 1st July, 2020

То, что вы ищете, это 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>


  Сведения об ответе

darknet

18:03, 1st July, 2020

Поскольку этот вопрос был задан для версий, отличных от Python, вот JavaScript:

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


  Сведения об ответе

DO__IT

18:03, 1st July, 2020

Консолидация ответов Джейсона Пратта и сообщества wiki, с учетом результатов различных методов привязки:

Особенно обратите внимание на то , как работает добавление функции привязки в качестве метода класса, но область ссылки неверна.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

Лично я предпочитаю внешний маршрут функции ADDMETHOD, поскольку он позволяет мне динамически назначать новые имена методов в итераторе.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>


  Сведения об ответе

pumpa

18:03, 1st July, 2020

Вам, ребята, действительно стоит посмотреть на запретный плод , это библиотека python, которая обеспечивает поддержку класса monkey patching ANY python, даже строк.


  Сведения об ответе

FAriza

18:03, 1st July, 2020

Это фактически аддон к ответу "Jason Pratt"

Хотя ответ Jasons работает, он работает только в том случае, если кто-то хочет добавить функцию в класс. Это не сработало для меня, когда я попытался перезагрузить уже существующий метод из файла исходного кода .py.

Мне потребовалась целая вечность, чтобы найти обходной путь, но трюк кажется простым... 1.st импорт кода из файла исходного кода 2.nd принудительная перезарядка 3.rd используйте types.FunctionType(...) для преобразования импортированного и связанного метода в функцию вы также можете передать текущие глобальные переменные, так как перегруженный метод будет находиться в другом пространстве имен 4.th теперь вы можете продолжить, как предложил "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

  Сведения об ответе

qwerty101

18:03, 1st July, 2020

То, что написал Джейсон Пратт, совершенно верно.

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

Как вы можете видеть, Python не считает b() чем-то отличным от a(). В Python все методы - это просто переменные, которые случайно оказываются функциями.


  Сведения об ответе

piter

18:03, 1st July, 2020

Если это может быть чем-то полезным, я недавно выпустил библиотеку Python под названием Gorilla, чтобы сделать процесс исправления обезьян более удобным.

Использование функции needle() для исправления модуля с именем guineapig происходит следующим образом:

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

Но он также заботится о более интересных случаях использования, как показано в FAQ из документации .

Код доступен на GitHub .


  Сведения об ответе

DAAA

18:03, 1st July, 2020

Этот вопрос был открыт много лет назад, но есть простой способ имитировать привязку функции к экземпляру класса с помощью декораторов:

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

Там, когда вы передадите функцию и экземпляр декоратору binder, он создаст новую функцию с тем же объектом кода, что и первый. Затем данный экземпляр класса сохраняется в атрибуте вновь созданной функции. Декоратор возвращает (третью) функцию, Автоматически вызывающую скопированную функцию, предоставляя экземпляр в качестве первого параметра.

В заключение вы получаете функцию, имитирующую ее привязку к экземпляру класса. Пусть исходная функция остается неизменной.


  Сведения об ответе

pumpa

18:03, 1st July, 2020

Я нахожу странным, что никто не упомянул, что все перечисленные выше методы создают циклическую ссылку между добавленным методом и экземпляром, заставляя объект быть постоянным до сборки мусора. Был старый трюк добавление дескриптора путем расширения класса объекта:

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


  Сведения об ответе

LAST

18:03, 1st July, 2020

from types import MethodType

def method(self):
   print 'hi!'


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

При этом вы можете использовать указатель self


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться