Adding a Method to an Existing Object Instance

后端 未结 16 2994
夕颜
夕颜 2020-11-21 05:45

I\'ve read that it is possible to add a method to an existing object (i.e., not in the class definition) in Python.

I understand that it\'s not always good to do so

相关标签:
16条回答
  • 2020-11-21 05:51

    I think that the above answers missed the key point.

    Let's have a class with a method:

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

    Now, let's play with it in ipython:

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

    Ok, so m() somehow becomes an unbound method of A. But is it really like that?

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

    It turns out that m() is just a function, reference to which is added to A class dictionary - there's no magic. Then why A.m gives us an unbound method? It's because the dot is not translated to a simple dictionary lookup. It's de facto a call of 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
    

    Now, I'm not sure out of the top of my head why the last line is printed twice, but still it's clear what's going on there.

    Now, what the default __getattribute__ does is that it checks if the attribute is a so-called descriptor or not, i.e. if it implements a special __get__ method. If it implements that method, then what is returned is the result of calling that __get__ method. Going back to the first version of our A class, this is what we have:

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

    And because Python functions implement the descriptor protocol, if they are called on behalf of an object, they bind themselves to that object in their __get__ method.

    Ok, so how to add a method to an existing object? Assuming you don't mind patching class, it's as simple as:

    B.m = m
    

    Then B.m "becomes" an unbound method, thanks to the descriptor magic.

    And if you want to add a method just to a single object, then you have to emulate the machinery yourself, by using types.MethodType:

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

    By the way:

    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'>
    
    0 讨论(0)
  • 2020-11-21 05:51

    Since this question asked for non-Python versions, here's JavaScript:

    a.methodname = function () { console.log("Yay, a new method!") }
    
    0 讨论(0)
  • 2020-11-21 05:57

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

    see http://docs.python.org/library/new.html

    In the example below I've deliberately removed return value from patch_me() function. I think that giving return value may make one believe that patch returns a new object, which is not true - it modifies the incoming one. Probably this can facilitate a more disciplined use of 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'>
    
    0 讨论(0)
  • 2020-11-21 05:59

    In Python, there is a difference between functions and bound methods.

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

    Bound methods have been "bound" (how descriptive) to an instance, and that instance will be passed as the first argument whenever the method is called.

    Callables that are attributes of a class (as opposed to an instance) are still unbound, though, so you can modify the class definition whenever you want:

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

    Previously defined instances are updated as well (as long as they haven't overridden the attribute themselves):

    >>> a.fooFighters()
    fooFighters
    

    The problem comes when you want to attach a method to a single instance:

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

    The function is not automatically bound when it's attached directly to an instance:

    >>> a.barFighters
    <function barFighters at 0x00A98EF0>
    

    To bind it, we can use the 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
    

    This time other instances of the class have not been affected:

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

    More information can be found by reading about descriptors and metaclass programming.

    0 讨论(0)
  • 2020-11-21 06:00

    This question was opened years ago, but hey, there's an easy way to simulate the binding of a function to a class instance using decorators:

    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 讨论(0)
  • 2020-11-21 06:02

    In Python monkeypatching generally works by overwriting a class or function's signature with your own. Below is an example from the Zope Wiki:

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

    This code will overwrite/create a method called peak in the class. In Jeff Atwood's recent post on monkey patching, he showed an example in C# 3.0 which is the current language I use for work.

    0 讨论(0)
提交回复
热议问题