Я читал, что возможно добавить метод к существующему объекту (то есть, не в определении класса) в Python.
Я понимаю, что это не всегда хорошо. Но как можно это сделать?
Я читал, что возможно добавить метод к существующему объекту (то есть, не в определении класса) в 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 в модуле типов:
>>> 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.
Модуль новый устарел с 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'>
Предисловие - примечание о совместимости: другие ответы могут работать только в 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)
__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
Это имеет смысл, если учесть, что связанные методы являются частичными функциями экземпляра.
Если мы попытаемся добавить образец_метода таким же образом, как мы могли бы добавить его к классу, он не связан с экземпляром и не принимает неявное "я" в качестве первого аргумента.
>>> 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
Теперь вы знаете несколько способов сделать это, но со всей серьезностью - не делайте этого.
Я думаю, что вышеупомянутые ответы пропустили ключевой момент.
Пусть класс с методом:
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'>
В Python исправление обезьян обычно работает путем перезаписи сигнатуры класса или функции вашей собственной. Ниже приведен пример из Zope Wiki:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Этот код перезапишет/создаст метод с именем talk в классе. В недавнем посте Джеффа Этвуда об исправлении обезьян. Он показывает пример в С# 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>>
Полезные ссылки:
Модель данных - вызов дескрипторов
Descriptor HowTo Guide - вызывать дескрипторы
Вы можете использовать лямбду, чтобы привязать метод к экземпляру:
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!") }
Консолидация 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>>
Вы, ребята, должны действительно смотреть на запрещенный плод, это библиотека python, которая обеспечивает поддержку обезьян, патчирующих любой класс python, даже строки.
Несмотря на то, что ответ 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
То, что написал Джейсон Пратт, верно.
>>> 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, чтобы сделать процесс исправления обезьян более удобным.
Использование функции needle()
для исправления модуля с именем guineapig
выполняется следующим образом:
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
Но он также заботится о более интересных случаях использования, как показано в FAQ из документация.
Код доступен на GitHub.
Этот вопрос был открыт много лет назад, но, эй, есть простой способ имитировать привязку функции к экземпляру класса с помощью декораторов:
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)) )
С помощью этого вы можете использовать указатель self
Я не знаю синтаксиса Python, но я знаю, что Ruby может это сделать, и это довольно тривиально. Предположим, вы хотите добавить метод в массив, который печатает длину до стандартного:
class Array
def print_length
puts length
end
end
Если вы не хотите изменять весь класс, вы можете просто добавить метод к одному экземпляру массива, и никакие другие массивы не будут иметь метод:
array = [1, 2, 3]
def array.print_length
puts length
end
Просто помните о проблемах, связанных с использованием этой функции. Джефф Этвуд фактически написал об этом не так давно.