Відповіді:
У 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'
Більше інформації можна отримати, прочитавши про дескриптори та програмування метакласу .
descriptor protocol
vs, створюючи MethodType
осторонь, можливо, бути трохи читабельнішим.
classmethod
і staticmethod
та інші дескриптори теж. Це дозволяє уникнути захаращення простору імен ще одним імпортом.
a.barFighters = barFighters.__get__(a)
Модуль new застарілий з 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'>
Передмова - примітка про сумісність: інші відповіді можуть працювати лише в 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
модуля (який ми імпортували вище).
Підпис аргументу для типів.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 таким же чином, як і ми можемо додати його до класу, він не пов'язаний з екземпляром, і не приймає неявну самості як перший аргумент.
>>> 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
Тепер ви знаєте кілька способів, як ви могли це зробити, але з усією серйозністю - не робіть цього.
__get__
Метод також необхідний клас в якості наступного параметра: sample_method.__get__(foo, Foo)
.
Я думаю, що наведені відповіді пропустили ключовий момент.
Давайте мати клас із методом:
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>
Виявляється, m () - це лише функція, посилання на яку додається до словника класу A - ніякої магії немає. Тоді чому Ам дає нам незв'язаний метод? Це тому, що крапка не перекладається на простий пошук словника. Це фактично виклик класу 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__ робить за замовчуванням, це те, що він перевіряє, чи атрибут є так званим дескриптором чи ні, тобто, якщо він реалізує спеціальний метод __get__. Якщо він реалізує цей метод, то те, що повертається, є результатом виклику цього методу __get__. Повертаючись до першої версії нашого класу A , це те, що ми маємо:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
А оскільки функції Python реалізують протокол дескриптора, якщо вони викликаються від імені об'єкта, вони прив'язують себе до цього об'єкта у своєму методі __get__.
Гаразд, так як додати метод до існуючого об'єкта? Якщо припустити, що ви не проти класу виправлення, це так просто, як:
B.m = m
Тоді Bm "стає" незв'язаним методом, завдяки магії дескриптора.
І якщо ви хочете додати метод лише до одного об’єкта, вам доведеться самостійно емулювати техніку, використовуючи type.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 мавпа виправлення, як правило, працює, замінивши підпис класу або функцій власноруч. Нижче наведено приклад із Zope Wiki :
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Цей код замінить / створить метод, який називається говорить на уроці. У недавньому дописі Джеффа Етвуда про виправлення мавп . Він показує приклад на C # 3.0, який є поточною мовою, яку я використовую для роботи.
Ви можете використовувати 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
Є принаймні два способи приєднання методу до екземпляра без 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>>
Корисні посилання:
Модель даних - виклик дескрипторів
Дескриптор HowTo Guide - виклик дескрипторів
Те, що ви шукаєте, 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
?
Оскільки це запитання задано для не-Python версій, ось JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
Консолідація відповідей Джейсона Пратта та вікі спільноти з поглядом на результати різних методів зв'язування:
Особливо зверніть увагу , як додавання функції прив'язки в якості методу класу робіт , але посилання сфери невірна.
#!/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 )
вирішує проблему
Хоча відповідь Ясона працює, вона працює лише в тому випадку, якщо хочеться додати функцію до класу. Мені це не вийшло, коли я спробував перезавантажити вже існуючий метод із файлу вихідного коду .py.
Мені знадобилося віки, щоб знайти вирішення, але хитрість здається простою ... 1. імпортувати код з файлу вихідного коду 2. і змусити перезавантажити 3.rd use types.FunctionType (...) для перетворення імпортований і прив'язаний метод до функції, ви також можете передавати поточні глобальні змінні, оскільки перезавантажений метод знаходився б в іншому просторі імен 4. тепер ви можете продовжувати так, як запропонував "Джейсон Пратт", використовуючи types.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
Якщо це може допомогти, я нещодавно випустив бібліотеку 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 ()
Там, коли ви передасте функцію та екземпляр декоратору сполучень, вона створить нову функцію з тим самим об'єктом коду, що і перша. Потім даний екземпляр класу зберігається в атрибуті новоствореної функції. Декоратор повертає (третю) функцію, яка автоматично викликає скопійовану функцію, надаючи екземпляр в якості першого параметра.
На закінчення ви отримуєте функцію, що імітує прив'язку до екземпляра класу. Залишаючи вихідну функцію незмінною.
Те, що розмістив Джейсон Пратт, є правильним.
>>> 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 () відмінним від (). У Python усі методи - це лише змінні, які бувають функціями.
Test
, а не його екземпляр.
Мені здається дивним, що ніхто не згадував, що всі перераховані вище методи створюють посилання на цикл між доданим методом та екземпляром, викликаючи стійкість об'єкта до збору сміття. Старий фокус додав дескриптор шляхом розширення класу об'єкта:
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)) )
За допомогою цього ви можете використовувати вказівник самоврядування
MethodType
, виклик протоколу дескриптора вручну, і функція виробляє ваш екземпляр:barFighters.__get__(a)
виробляє прив'язаний метод дляbarFighters
прив'язки доa
.