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

Я читал, что в 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'

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


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

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

в приведенном ниже примере я намеренно удалил возвращаемое значение из


предисловие-примечание о совместимости: другие ответы могут работать только в 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__ метод функции с экземпляром, привязка объекта к методу и, таким образом, создание "связанного метода."

foo.sample_method = sample_method.__get__(foo)

и так:

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

метод один-типы.MethodType

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

import types

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

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

>>> 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 () - это просто функция, ссылка на который добавляется к A словарь класса-нет никакой магии. Тогда почему?--15-->А. М дает нам несвязанный метод? Это потому, что точка не переводится в простой поиск словаря. Это де-факто призыв А.__класс__.__ 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

затем Б. М "становится" несвязанным методом, благодаря магии дескриптора.

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

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

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

этот код перезапишет / создаст метод, называемый speak в классе. У Джеффа Этвуда!--6-->недавнее сообщение о патчинге обезьяны. Он показывает пример в C# 3.0, который является текущим языком, который я использую для работы.


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

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


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

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

это строка экземпляра

процесс завершен с кодом выхода 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>

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

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

консолидация ответов Джейсона Пратта и сообщества 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>>

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


это на самом деле аддон к ответу "Jason Pratt"

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

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

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

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


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

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

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

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

код доступен на сайте GitHub.


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

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 addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass

from types import MethodType

def method(self):
   print 'hi!'


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

С этим вы можете использовать self pointer