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

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

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

Ответ 1

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

Более подробную информацию можно найти, прочитав дескрипторы и metaclass programming.

Ответ 2

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

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

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

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

Ответ 3

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

Метод 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    

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

Несвязанная функция как атрибут объекта - почему это не работает:

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

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

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

Заключение

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

Ответ 4

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

Пусть класс с методом:

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 - там нет магии. Тогда почему A.m дает нам несвязанный метод? Это потому, что точка не переведена на простой поиск словаря. Это де-факто вызов класса A.__ __.__ 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__, является то, что он проверяет, является ли атрибут так называемым descriptor или нет, т.е. если он реализует специальный метод __get__. Если он реализует этот метод, то возвращаемое является результатом вызова этого метода __get__. Возвращаясь к первой версии нашего класса A, это то, что у нас есть:

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

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

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

B.m = m

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

И если вы хотите добавить метод только к одному объекту, тогда вы должны сами эмулировать оборудование, используя типы. Метод: Тип:

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

Консолидация Jason Pratt и ответы сообщества 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 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 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 .__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 .__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 have a look at each instance 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>>

Ответ 11

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

Ответ 12

Это на самом деле аддон к ответу "Джейсон Пратт"

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

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

Ответ 13

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

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

Ответ 14

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

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

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

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

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

Ответ 15

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

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

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

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

Ответ 16

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

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

Ответ 17

from types import MethodType

def method(self):
   print 'hi!'


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

С помощью этого вы можете использовать указатель self

Ответ 18

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

class Array
  def print_length
    puts length
  end
end

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

array = [1, 2, 3]
def array.print_length
  puts length
end

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