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

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

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

Ответов (17)

Решение

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

Чтобы связать его, мы можем использовать функцию 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'

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

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

У нас есть класс с методом:

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

Теперь поиграем с ним в ipython:

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

Итак, м () какой - то образом становится несвязанным методом А . Но так ли это на самом деле?

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>

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

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

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

#!/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>>

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

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

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

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

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

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

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

Предисловие - примечание о совместимости: другие ответы могут работать только в 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)

Method naught (0) - использовать метод дескриптора, __get__

Пунктирный поиск в функциях вызывает __get__ метод функции с экземпляром, привязывая объект к методу и, таким образом, создавая «связанный метод».

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

Заключение

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

На самом деле это дополнение к ответу "Джейсона Пратта"

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

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

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

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

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

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

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

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

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

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

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

Полезные ссылки:
Модель данных - вызов дескрипторов
Descriptor HowTo Guide - вызов дескрипторов

Помимо того, что говорили другие, я обнаружил, что __repr__ и __str__ методы не могут быть исправлены на уровне объекта, потому что repr() и str() используют методы класса, а не методы объекта с локальным ограничением:

# Instance monkeypatch
[ins] In [55]: x.__str__ = show.__get__(x)                                                                 

[ins] In [56]: x                                                                                           
Out[56]: <__main__.X at 0x7fc207180c10>

[ins] In [57]: str(x)                                                                                      
Out[57]: '<__main__.X object at 0x7fc207180c10>'

[ins] In [58]: x.__str__()                                                                                 
Nice object!

# Class monkeypatch
[ins] In [62]: X.__str__ = lambda _: "From class"                                                          

[ins] In [63]: str(x)                                                                                      
Out[63]: 'From class'
from types import MethodType

def method(self):
   print 'hi!'


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

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

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

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

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

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>

То, что опубликовал Джейсон Пратт, правильно.

>>> 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 все методы - это просто переменные, которые оказываются функциями.

Модуль new устарел с версии python 2.6 и удален в версии 3.0, используйте типы

см. http://docs.python.org/library/new.html

В приведенном ниже примере я намеренно удалил возвращаемое значение из patch_me() функции. Я думаю, что указание возвращаемого значения может заставить поверить в то, что patch возвращает новый объект, что не соответствует действительности - он изменяет входящий. Вероятно, это может способствовать более дисциплинированному использованию обезьяньего отряда.

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