将方法添加到现有对象实例

我读过可以在Python中将方法添加到现有对象(即不在类定义中)。

我明白这样做并不总是好事。但是,如何做到这一点?

0
额外 编辑
意见: 1

18 答案

我不知道Python的语法,但我知道Ruby可以做到,而且它相当简单。比方说,你想添加一个方法到阵列打印标准输出的长度:

class Array
  def print_length
    puts length
  end
end

如果您不想修改整个类,那么您可以将该方法添加到数组的单个实例中,而其他数组将不具有该方法:

array = [1, 2, 3]
def array.print_length
  puts length
end

请注意使用此功能所涉及的问题。实际上,Jeff Atwood 不久前写过关于它的文章

0
额外

你所寻找的是 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>
0
额外
是否有理由使用 setattr(A,'printme',printme)来代替简单的 A.printme = printme
额外 作者 Tobias Kienzler,
这是修补类 A ,而不是实例 a
额外 作者 Ethan Furman,
在运行时构造方法名是有意义的。
额外 作者 rr-,

将方法添加到现有对象实例

     

我读过可以在Python中为现有对象添加一个方法(例如不在类定义中)。

     我知道这并不总是一个好的决定。 但是,如何做到这一点?

是的,这是可能的 - 但不推荐

我不推荐这个。这是一个坏主意。不要这样做。

这有几个原因:

  • 您将为每个实例添加一个绑定对象。如果你这么做,你可能会浪费很多内存。绑定方法通常只会在短时间内创建,然后在自动收集垃圾时停止存在。如果您手动执行此操作,您将拥有引用绑定方法的名称绑定 - 这将阻止其使用中的垃圾回收。
  • 给定类型的对象实例通常在该类型的所有对象上都有其方法。如果您在其他地方添加方法,某些实例将具有这些方法,而其他方法则不会。程序员不会期望这一点,并且您冒着违反规则的风险最小的风险。
  • 由于还有其他很好的理由不这样做,如果你这样做,你还会给自己一个糟糕的声誉。

因此,我建议你不要这样做,除非你有一个很好的理由。 在类定义less 中定义正确的方法好得多,最好是直接对类进行猴子修补,如下所示:

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 nought (0) - use the descriptor method, __get__

对函数的虚线查找调用带有实例的函数的 __ get __ 方法,将该对象绑定到该方法,从而创建一个“绑定方法”。

foo.sample_method = sample_method.__get__(foo)

现在:

>>> foo.sample_method(1,2)
3

方法一 - 类型。方法类型

首先,导入类型,我们将从中获取方法构造函数:

import types

现在我们将该方法添加到实例中。为此,我们需要 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

方法三: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 "", line 1, in 
TypeError: sample_method() takes exactly 3 arguments (2 given)

我们可以通过显式地传递实例(或者任何东西,因为这个方法实际上并不使用 self 参数变量)来使未绑定的函数工作,但它不会与其他实例的预期签名一致(如果我们正在修补这个实例):

>>> foo.sample_method(foo, 1, 2)
3

结论

你现在知道几种方法可以做到这一点,但是非常认真 - 不要这样做。

0
额外
我的评论是基于这个参考: python-reference.readthedocs。 io / en / latest / docs / dunderdsc /&hellip; 现在我从官方文档看到,它是可选的: docs.python.org/3/howto/descriptor.html#descriptor-protocol
额外 作者 Aidas Bendoraitis,
__ get __ 方法也需要该类作为下一个参数: sample_method .__ get __(foo,Foo)
额外 作者 Aidas Bendoraitis,
@AidasBendoraitis我不会说它“需要”它,它是应用描述符协议时提供的可选参数 - 但是python函数不使用参数: github.com/python/cpython/blob/master/Objects/funcobject.c#L‌ 581
额外 作者 Aaron Hall,
@Atcold我已经扩展了原因,以避免在介绍中做到这一点。
额外 作者 Aaron Hall,
声明是我想知道的。方法定义只是嵌套在类定义中的函数。
额外 作者 Atcold,

Module new is deprecated since python 2.6 and removed in 3.0, use types

see 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 
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
<__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo

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
<__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod

>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<__main__.A instance at 0x973ec6c>>

Useful links:
Data model - invoking descriptors
Descriptor HowTo Guide - invoking descriptors

0
额外

由于这个问题需要非Python版本,所以这里是JavaScript:

a.methodname = function() { console.log("Yay, a new method!") }
0
额外

我认为上述答案错过了关键点。

让我们有一个方法的类:

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

现在,让我们在ipython中使用它:

In [2]: A.m
Out[2]: 

好的,所以 m()不知何故成为 A 的未绑定方法。但它真的是这样吗?

In [5]: A.__dict__['m']
Out[5]: 

事实证明, 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
 - m
 - m

现在,我不知道为什么最后一行打印两次,但仍然清楚发生了什么。

现在,默认的__getattribute__所做的是检查属性是否为所谓的描述符,也就是说,如果它实现了一个特殊的__get__方法。如果它实现了该方法,那么返回的是调用__get__方法的结果。回到我们的 A 类的第一个版本,这就是我们所拥有的:

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

而且由于Python函数实现描述符协议,如果它们是代表对象调用的,则它们将自己绑定到__get__方法中的该对象。

好的,那么如何将一个方法添加到现有对象?假设你不介意修补课程,它就像下面这样简单:

B.m = m

然后 B.m “变成”一个未绑定的方法,这要归功于描述符魔术。

如果你想只为一个对象添加一个方法,那么你必须使用types.MethodType自己模拟机器:

b.m = types.MethodType(m, b)

顺便一提:

In [2]: A.m
Out[2]: 

In [59]: type(A.m)
Out[59]: 

In [60]: type(b.m)
Out[60]: 

In [61]: types.MethodType
Out[61]: 
0
额外

这个问题在几年前就已经打开了,但是,嘿,有一种简单的方法可以用decorator来模拟函数与类实例的绑定:

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

There, when you pass the function and the instance to the binder decorator, it will create a new function, with the same code object as the first one. Then, the given instance of the class is stored in an attribute of the newly created function. The decorator return a (third) function calling automatically the copied function, giving the instance as the first parameter.

In conclusion you get a function simulating it's binding to the class instance. Letting the original function unchanged.

0
额外

综合Jason Pratt's和社区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'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__ <__main__.C instance at 0x000000000230FD88>>
# addmethod <__main__.C instance at 0x000000000230FD88>>
# f0 <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <__main__.C instance at 0x000000000230FE08>>
# addmethod <__main__.C instance at 0x000000000230FE08>>
# f0 <__main__.C instance at 0x000000000230FE08>>
# f1 >
# f2 <__main__.C instance at 0x000000000230FE08>>
# f3 <__main__.C instance at 0x000000000230FE08>>
# f4 <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <__main__.C instance at 0x0000000002313108>>
# addmethod <__main__.C instance at 0x0000000002313108>>
# f0 <__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__ <__main__.C instance at 0x0000000002303508>>
# addmethod <__main__.C instance at 0x0000000002303508>>
# f0 <__main__.C instance at 0x0000000002303508>>
# f1 <__main__.C instance at 0x0000000002303508>>
# f2 <__main__.C instance at 0x0000000002303508>>
# f3 <__main__.C instance at 0x0000000002303508>>
# f4 <__main__.C instance at 0x0000000002303508>>
0
额外
addmethod 用以下方式重写 def addmethod(self,method,name):self .__ dict __ [name] = types.MethodType(method,self)解决了问题
额外 作者 Antony Hatchkins,

在Python中,猴子修补通常通过用自己的覆盖类或函数签名来工作。以下是来自 Zope Wiki 的示例:

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

该代码将覆盖/创建一个名为讲话的方法。在Jeff Atwood的猴子补丁最近的帖子。他在C#3.0中展示了一个例子,它是我用于工作的当前语言。

0
额外
但它影响班级的所有实例,而不仅仅是一个实例。
额外 作者 glglgl,

在Python中,函数和绑定方法是有区别的。

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo

>>> a.bar
<__main__.A instance at 0x00A9BC88>>
>>>

绑定方法已经被绑定(如何描述)到一个实例,并且该实例将在调用该方法时作为第一个参数传递。

然而,作为类的属性(而不是实例)的可调参数仍然是未绑定的,因此您可以随时修改类定义:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<__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 "", line 1, in 
TypeError: barFighters() takes exactly 1 argument (0 given)

该函数直接附加到实例时不会自动绑定:

>>> a.barFighters

要绑定它,我们可以在类型模块中使用 MethodType函数

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

这次这个班的其他班级没有受到影响:

>>> a2.barFighters()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: A instance has no attribute 'barFighters'

更多信息可以通过阅读描述符metaclass programming

0
额外
Python中的绑定方法类似于C#中的扩展方法吗?
额外 作者 Andy,
@EndermanAPM:几个:它更可能继续工作,就像访问实例上的属性一样。它可以用于 classmethodstaticmethod 以及其他描述符。它避免了使用另一个导入混淆名称空间。
额外 作者 Martijn Pieters,
建议描述符方法的完整代码是 a.barFighters = barFighters .__ get __(a)
额外 作者 eqzx,
@MartijnPieters使用描述符协议与创建 MethodType 之间的任何优点都可能更具可读性。
额外 作者 EndermanAPM,

Jason Pratt发布的内容是正确的。

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)

>>> type(Test.a)

>>> type(Test.b)

正如你所看到的,Python不认为b()与()不同。在Python中,所有方法都只是变量,而这些变量恰好是函数。

0
额外
您正在修补类 Test ,而不是它的一个实例。
额外 作者 Ethan Furman,

你们真的应该看禁果,它是一个python库,为猴子修补ANY python类提供支持,甚至是弦乐。

0
额外
通常,链接到工具或库应附带使用说明或示例代码,或者如果可能的话。但是,这至少要符合链接文章中的最低标准,方法是对链接资源如何适用于该问题进行具体说明。
额外 作者 Nathan Tuggy,

如果它可以提供任何帮助,我最近发布了一个名为Gorilla的Python库,以使猴子修补过程更加方便。

使用函数 needle()修补名为 guineapig 的模块如下所示:

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

但它也会处理更有趣的用例,如常见问题解答中所示。 文档

该代码可在 GitHub 上获得。

0
额外

您可以使用lambda将方法绑定到实例:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "这是实例字符串"

a = A()
a.run = lambda: run(a)
a.run()

这是实例字符串

进程使用退出码0结束

0
额外

这实际上是“Jason Pratt”的答案

虽然Jasons回答了作品,但只有在想要为课堂添加功能时才能起作用。 当我尝试从.py源代码文件重新加载已有的方法时,它不适用于我。

我花了很长时间才找到解决方法,但这个技巧似乎很简单。 1.st从源代码文件导入代码 2.强制重新加载 3.使用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
0
额外

我感到奇怪的是,没有人提到上面列出的所有方法都会在添加的方法和实例之间创建一个循环引用,导致该对象在垃圾收集之前保持持久性。有一个老技巧通过扩展对象的类来添加一个描述符:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
0
额外
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

有了这个,你可以使用自指针

0
额外