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

StackOverflow https://stackoverflow.com/questions/972

Вопрос

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

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

Это было полезно?

Решение

В 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 в модуле 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'

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

Другие советы

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

видишь http://docs.python.org/library/new.html

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

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

Способ третий:функциональные средства.частичные

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

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

Заключение

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

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

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

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

Теперь давайте поиграем с этим в ipython:

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

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

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

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

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

И если вы хотите добавить метод только к одному объекту, тогда вам придется эмулировать механизм самостоятельно, используя типы.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 обычно работает путем перезаписи сигнатуры класса или функций вашей собственной.Ниже приведен пример из Zope - Вики:

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

Этот код перезапишет / создаст в классе метод с именем speak.У Джеффа Этвуда недавний пост о исправлении обезьян.Он показывает пример на 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>>

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

Вы можете использовать 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

То, что вы ищете, это 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 route, поскольку она позволяет мне динамически назначать имена новых методов и внутри итератора.

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, которая обеспечивает поддержку исправления monkey ЛЮБОГО класса python, даже строк.

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

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

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

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

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

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

Код доступен на сайте ГитХаб.

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

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, он создаст новую функцию с тем же объектом code, что и первый.Затем данный экземпляр класса сохраняется в атрибуте вновь созданной функции.Декоратор возвращает (третью) функцию, автоматически вызывающую скопированную функцию, предоставляя экземпляр в качестве первого параметра.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top