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






В Python monkeypatching обычно работает путем перезаписи сигнатуры класса или функции вашей собственной. Ниже приведен пример из Zope вики:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Этот код перезапишет / создаст метод под названием peak в классе. В недавний пост об исправлении обезьян Джеффа Этвуда он показал пример на C# 3.0, который в настоящее время является языком, который я использую в работе.
В 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.
@MartijnPieters какие-либо преимущества использования descriptor protocol по сравнению с созданием MethodType, кроме того, что он может быть немного более читаемым.
@EndermanAPM: Несколько: более вероятно, что он будет продолжать работать точно так же, как и доступ к атрибуту в экземпляре. Он будет работать и для classmethod, и для staticmethod, и для других дескрипторов. Это позволяет избежать загромождения пространства имен еще одним импортом.
Полный код предлагаемого дескрипторного подхода - a.barFighters = barFighters.__get__(a).
Просто примечание: в python3 нет несвязанного метода, поскольку разница между функцией и несвязанным методом довольно минимальна, Python 3 избавляется от этого различия.
Я считаю, что вы ищете 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.
Есть ли причина использовать setattr(A,'printme',printme) вместо просто A.printme = printme?
Это имеет смысл, если вы создаете имя метода во время выполнения.
То, что опубликовал Джейсон Пратт, правильно.
>>> 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, а не его экземпляр.
Вы добавляете метод к классу, а не к экземпляру объекта.
Модуль новый устарел, начиная с 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)
Я думаю, что в приведенных выше ответах упущен ключевой момент.
У нас есть класс с методом:
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 ) решает проблему
Поскольку этот вопрос задан для версий, отличных от 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)
__get__Пунктирный поиск в функциях вызывает метод __get__ функции с экземпляром, привязывая объект к методу и тем самым создавая «связанный метод».
foo.sample_method = sample_method.__get__(foo)
и сейчас:
>>> foo.sample_method(1,2)
3
Сначала импортируем типы, из которых мы получим конструктор метода:
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
Частичная функция применяет первый аргумент (ы) к функции (и, возможно, аргументы ключевого слова), и позже может быть вызвана с оставшимися аргументами (и переопределением аргументов ключевого слова). Таким образом:
>>> 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 Я подробно остановился на причинах, по которым этого нельзя делать во введении.
Метод __get__ также нуждается в классе в качестве следующего параметра: sample_method.__get__(foo, Foo).
@AidasBendoraitis Я бы не сказал, что он "нужен", это необязательный параметр, который предоставляется при применении протокола дескриптора, но функции python не используют аргумент: github.com/python/cpython/blob/master/Objects/funcobject.c#L 581
Мой комментарий был основан на этой ссылке: python-reference.readthedocs.io/en/latest/docs/dunderdsc/… То, что я теперь вижу из официальных документов, это необязательно: docs.python.org/3/howto/descriptor.html#descriptor-protocol
functools.partial хорошо работает с multiprocessing, другие - нет.
Хорошее объяснение и подход.
Хотя ответ Джейсона работает, он работает только в том случае, если кто-то хочет добавить функцию в класс. У меня не сработало, когда я попытался перезагрузить уже существующий метод из файла исходного кода .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'
Но это влияет на экземпляры класса все, а не только на один.