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

python oop methods monkeypatching


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

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




Answer 1 Jason Pratt


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

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




Answer 2 Evgeny


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

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

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

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



Answer 3 Aaron Hall


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

Метод первый-типы.Метод первый-типы.

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

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

usage:

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

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

Conclusion

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




Answer 4 Tomasz Zieliński


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

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

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

А теперь давай поиграем с ним на Ипитоне:

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

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

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

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

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

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



Answer 5 John Downey


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