首页 文章

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

提问于
浏览
523

我已经读过可以在Python中向现有对象(即不在类定义中)添加方法 .

我知道这样做并不总是好事 . 但是人们怎么可能这样做呢?

17 回答

  • 9

    模块 new 从python 2.6开始不推荐使用,在3.0中删除,使用 types

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

    前言 - 关于兼容性的说明:其他答案可能只适用于Python 2 - 这个答案应该在Python 2和3中运行得很好 . 如果只编写Python 3,你可能会遗漏明确继承自 object ,但是否则代码应该保留在相同 .

    向现有对象实例添加方法我已经读过可以在Python中向现有对象(例如,不在类定义中)添加方法 . 我知道这样做并不总是一个好的决定 . 但是,一个人怎么可能这样做?

    是的,有可能 - 但不推荐

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

    这有几个原因:

    • 你'll add a bound object to every instance you do this to. If you do this a lot, you' ll可能会浪费大量内存 . 绑定方法通常仅在其调用的短持续时间内创建,然后在自动垃圾收集时停止存在 . 如果您手动执行此操作,您将具有引用绑定方法的名称绑定 - 这将阻止其在使用时进行垃圾回收 .

    • 给定类型的对象实例通常在该类型的所有对象上都有其方法 . 如果在其他地方添加方法,则某些实例将具有这些方法,而其他实例则不会 . 程序员不会期望这样,你冒着违反rule of least surprise的风险 .

    • 由于还有其他非常好的理由不这样做,如果你这样做,你还会给自己一个不好的声誉 .

    因此,我建议你不要这样做,除非你有充分的理由 . It is far better to define the correct method in the class definition 或者更不用说直接修补这个类,如下所示:

    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)
    

    方法无效(0) - 使用描述符方法__get__

    函数上的虚线查找使用实例调用函数的 __get__ 方法,将对象绑定到方法,从而创建"bound method."

    foo.sample_method = sample_method.__get__(foo)
    

    现在:

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

    方法一 - types.MethodType

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

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

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

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

    结论

    你现在知道几种方法可以做到这一点,但严肃地说 - 不要这样做 .

  • 786

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

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

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

    但它也需要更多有趣的用例如documentation中所示FAQ .

    该代码可在GitHub上找到 .

  • 3

    您可以使用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()
    

    这是实例字符串

    进程以退出代码0结束

  • 31

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

    a.methodname = function () { console.log("Yay, a new method!") }
    
  • 7

    你们应该真正看看forbidden fruit,它是一个python库,它为猴子修补任何python类提供支持,甚至是字符串 .

  • 85
    from types import MethodType
    
    def method(self):
       print 'hi!'
    
    
    setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )
    

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

  • 15

    整合Jason Pratt和社区维基的答案,看看不同绑定方法的结果:

    特别注意如何将绑定函数添加为类方法,但引用范围不正确 .

    #!/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>>
    
  • 2

    这个问题在几年前就开始了,但是,嘿,有一个简单的方法来模拟使用装饰器将函数绑定到类实例:

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

    在那里,当你将函数和实例传递给binder装饰器时,它将创建一个新函数,其代码对象与第一个相同 . 然后,类的给定实例存储在新创建的函数的属性中 . 装饰器返回一个(第三个)函数,自动调用复制的函数,将实例作为第一个参数 .

    总之,你得到一个模拟它绑定到类实例的函数 . 让原始功能保持不变 .

  • 1

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

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

    该代码将覆盖/创建一个名为speak的方法 . 在杰夫阿特伍德的recent post on monkey patching . 他在C#3.0中展示了一个例子,它是我用于工作的当前语言 .

  • 2

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

    尽管Jasons的答案有效,但只有在想要向类添加函数时它才有效 . 当我尝试从.py源代码文件重新加载现有方法时,它对我不起作用 .

    我花了很长时间才找到一个解决方法,但技巧似乎很简单...... 1.从源代码文件导入代码2.nd强制重新加载3.rd使用types.FunctionType(...)来转换函数的导入和绑定方法也可以传递给当前的全局变量,因为重新加载的方法将位于不同的命名空间中 . 现在你可以按照“Jason Pratt”的建议继续使用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
    
  • 5

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

    让我们有一个方法的类:

    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给我们一个未绑定的方法呢?它's because the dot is not translated to a simple dictionary lookup. It'事实上调用了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__所做的是它检查该属性是否是所谓的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 "becomes"一个未绑定的方法,多亏了描述符魔术 .

    如果你想只为一个对象添加一个方法,那么你必须使用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'>
    
  • 5

    你在寻找的是 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>
    
  • 59

    我觉得很奇怪,没有人提到上面列出的所有方法都在添加的方法和实例之间创建了一个循环引用,导致对象持久化直到垃圾收集 . 有一个老技巧通过扩展对象的类来添加描述符:

    def addmethod(obj, name, func):
        klass = obj.__class__
        subclass = type(klass.__name__, (klass,), {})
        setattr(subclass, name, func)
        obj.__class__ = subclass
    
  • 6

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

    >>> 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中,所有方法都只是恰好是函数的变量 .

  • 6

    在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 function in the types module

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

    阅读有关descriptorsmetaclass programming的更多信息 .

  • 5

    在没有 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>>
    

    有用的链接:
    Data model - invoking descriptors
    Descriptor HowTo Guide - invoking descriptors

相关问题