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

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

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

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
699
0
268 822
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

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

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

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

Но это влияет на экземпляры класса все, а не только на один.

glglgl 03.06.2014 14:06
Ответ принят как подходящий

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

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

Вместо того, чтобы вручную создавать MethodType, вызовите протокол дескриптора вручную и попросите функцию создать ваш экземпляр: barFighters.__get__(a) создает связанный метод для barFighters, привязанный к a.

Martijn Pieters 16.09.2016 16:13

@MartijnPieters какие-либо преимущества использования descriptor protocol по сравнению с созданием MethodType, кроме того, что он может быть немного более читаемым.

EndermanAPM 10.05.2017 10:57

@EndermanAPM: Несколько: более вероятно, что он будет продолжать работать точно так же, как и доступ к атрибуту в экземпляре. Он будет работать и для classmethod, и для staticmethod, и для других дескрипторов. Это позволяет избежать загромождения пространства имен еще одним импортом.

Martijn Pieters 10.05.2017 11:10

Полный код предлагаемого дескрипторного подхода - a.barFighters = barFighters.__get__(a).

eqzx 14.09.2017 20:45

Просто примечание: в python3 нет несвязанного метода, поскольку разница между функцией и несвязанным методом довольно минимальна, Python 3 избавляется от этого различия.

oeter 01.02.2021 12:21

Я считаю, что вы ищете 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>

Это исправление класса A, а не экземпляра a.

Ethan Furman 25.09.2011 00:04

Есть ли причина использовать setattr(A,'printme',printme) вместо просто A.printme = printme?

Tobias Kienzler 09.08.2013 14:04

Это имеет смысл, если вы создаете имя метода во время выполнения.

rr- 02.04.2016 10:35

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

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

Вы исправляете класс Test, а не его экземпляр.

Ethan Furman 25.09.2011 00:05

Вы добавляете метод к классу, а не к экземпляру объекта.

TomSawyer 29.08.2019 12:27

Модуль новый устарел, начиная с 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'>

Как это будет работать, если добавляемый метод должен ссылаться на себя? Моя первая попытка привела к синтаксической ошибке, но добавление self в определение метода, похоже, не работает. типы импорта class A (object): # но, похоже, работает и для объектов старого стиля ax = 'ax' pass def patch_me (target): def method (target, x): print (self.ax) print ("x =" , x) print ("вызывается из", target) target.method = types.MethodType (method, target) # добавить при необходимости больше a = A () print (a.ax)

WesR 31.01.2020 09:36

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

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

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>

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

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

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

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

B.m = m

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

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

addmethod переписан следующим образом def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self ) решает проблему

Antony Hatchkins 05.04.2015 22:00

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

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

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

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

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

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

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

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

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

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

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

Adding a Method to an Existing Object Instance

I've read that it is possible to add a method to an existing object (e.g. not in the class definition) in Python.

I understand that it's not always a good decision to do so. But, how might one do this?

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

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

Вот пара причин:

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

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

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

Вывод

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

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

Atcold 18.01.2018 22:06

@Atcold Я подробно остановился на причинах, по которым этого нельзя делать во введении.

Aaron Hall 18.01.2018 22:22

Метод __get__ также нуждается в классе в качестве следующего параметра: sample_method.__get__(foo, Foo).

Aidas Bendoraitis 31.01.2018 16:36

@AidasBendoraitis Я бы не сказал, что он "нужен", это необязательный параметр, который предоставляется при применении протокола дескриптора, но функции python не используют аргумент: github.com/python/cpython/blob/master/Objects/funcobject.c#L‌ 581

Aaron Hall 31.01.2018 16:50

Мой комментарий был основан на этой ссылке: python-reference.readthedocs.io/en/latest/docs/dunderdsc/… То, что я теперь вижу из официальных документов, это необязательно: docs.python.org/3/howto/descriptor.html#descriptor-protocol

Aidas Bendoraitis 01.02.2018 04:37

functools.partial хорошо работает с multiprocessing, другие - нет.

Nyxynyx 22.01.2020 09:09

Хорошее объяснение и подход.

Carl Boneri 28.01.2021 02:06

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

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

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

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

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

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

Помимо того, что говорили другие, я обнаружил, что методы __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'

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