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. But how might one do this?
Yes, it is possible - But not recommended Don't do it.
The normal way to add functionality (methods) to a class in Python is to define functions in the class body. There are many other ways to accomplish this that can be useful in different situations. The method can also be defined outside the scope of the class.
The __add__() method in Python is a special method that defines what happens when you add two objects with the + operator.
Python's object. __add__(self, other) method returns a new object that represents the sum of two objects. It implements the addition operator + in Python. We call this a “Dunder Method” for “Double Underscore Method” (also called “magic method”).
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.
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?
I don't recommend this. This is a bad idea. Don't do it.
Here's a couple of reasons:
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.
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)
__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
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 (in Python 3) is (function, instance)
:
foo.sample_method = types.MethodType(sample_method, foo)
and usage:
>>> foo.sample_method(1,2) 3
Parenthetically, in Python 2 the signature was (function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
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
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.
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
You now know several ways you could do this, but in all seriousness - don't do this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With