Adding a Method to an Existing Object Instance

后端 未结 16 2958
夕颜
夕颜 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 06:14

    What you're looking for is setattr I believe. Use this to set an attribute on an object.

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

    Preface - a note on compatibility: other answers may only work in Python 2 - this answer should work perfectly well in Python 2 and 3. If writing Python 3 only, you might leave out explicitly inheriting from object, but otherwise the code should remain the same.

    Adding a Method to an Existing Object Instance

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

    I understand that it's not always a good decision to do so. But, how might one do this?

    Yes, it is possible - But not recommended

    I don't recommend this. This is a bad idea. Don't do it.

    Here's a couple of reasons:

    • You'll add a bound object to every instance you do this to. If you do this a lot, you'll probably waste a lot of memory. Bound methods are typically only created for the short duration of their call, and they then cease to exist when automatically garbage collected. If you do this manually, you'll have a name binding referencing the bound method - which will prevent its garbage collection on usage.
    • Object instances of a given type generally have its methods on all objects of that type. If you add methods elsewhere, some instances will have those methods and others will not. Programmers will not expect this, and you risk violating the rule of least surprise.
    • Since there are other really good reasons not to do this, you'll additionally give yourself a poor reputation if you do it.

    Thus, I suggest that you not do this unless you have a really good reason. It is far better to define the correct method in the class definition or less preferably to monkey-patch the class directly, like this:

    Foo.sample_method = sample_method
    

    Since it's instructive, however, I'm going to show you some ways of doing this.

    How it can be done

    Here's some setup code. We need a class definition. It could be imported, but it really doesn't matter.

    class Foo(object):
        '''An empty class to demonstrate adding a method to an instance'''
    

    Create an instance:

    foo = Foo()
    

    Create a method to add to it:

    def sample_method(self, bar, baz):
        print(bar + baz)
    

    Method nought (0) - use the descriptor method, __get__

    Dotted lookups on functions call the __get__ method of the function with the instance, binding the object to the method and thus creating a "bound method."

    foo.sample_method = sample_method.__get__(foo)
    

    and now:

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

    Method one - types.MethodType

    First, import types, from which we'll get the method constructor:

    import types
    

    Now we add the method to the instance. To do this, we require the MethodType constructor from the types module (which we imported above).

    The argument signature for types.MethodType is (function, instance, class):

    foo.sample_method = types.MethodType(sample_method, foo, Foo)
    

    and usage:

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

    Method two: lexical binding

    First, we create a wrapper function that binds the method to the instance:

    def bind(instance, method):
        def binding_scope_fn(*args, **kwargs): 
            return method(instance, *args, **kwargs)
        return binding_scope_fn
    

    usage:

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

    Method three: functools.partial

    A partial function applies the first argument(s) to a function (and optionally keyword arguments), and can later be called with the remaining arguments (and overriding keyword arguments). Thus:

    >>> from functools import partial
    >>> foo.sample_method = partial(sample_method, foo)
    >>> foo.sample_method(1,2)
    3    
    

    This makes sense when you consider that bound methods are partial functions of the instance.

    Unbound function as an object attribute - why this doesn't work:

    If we try to add the sample_method in the same way as we might add it to the class, it is unbound from the instance, and doesn't take the implicit self as the first argument.

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

    We can make the unbound function work by explicitly passing the instance (or anything, since this method doesn't actually use the self argument variable), but it would not be consistent with the expected signature of other instances (if we're monkey-patching this instance):

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

    Conclusion

    You now know several ways you could do this, but in all seriousness - don't do this.

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

    If it can be of any help, I recently released a Python library named Gorilla to make the process of monkey patching more convenient.

    Using a function needle() to patch a module named guineapig goes as follows:

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

    But it also takes care of more interesting use cases as shown in the FAQ from the documentation.

    The code is available on GitHub.

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

    You can use lambda to bind a method to an instance:

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

    Output:

    This is instance string
    
    0 讨论(0)
提交回复
热议问题