Often, when programming, we may want to change some already set behavior. This can be accomplished by sub-classing whatever classes we have and overriding those methods we are not happy with.
class Dog:def bark(self):print 'Woof!'class Husky(Dog):def bark(self)print 'Howl!'
While this works with our own code, what happens if we want to change third party code? Of course we can edit it so that it instantiates one of our sub-classes, this however, can create another whole set of problems. What we need to do then, is to figure out a way to replace an object's methods with our own.
Probably the easiest way of adding a new method to an object or replacing an existing one is by patching its class. Say we want to teach our Dog class from the previous example how to howl, we can easily do it by defining a new howl function and adding it to our class like so:
def newbark(self):print 'Wrooof!'def howl(self):print 'Howl!'# Replace an existing methodDog.bark = newbark# Add a new methodDog.howl = howl
While this is extremely easy to do, there's a couple of things we should be aware of. First of all, all instances of the modified class will be updated, meaning that not only new objects will have the new method definitions, but that all objects we created before patching our class will have them too (unless they haven't overridden the method themselves). Second, the new or modified methods will be bound, meaning that the first argument (i.e. 'self') will be the object being called.
Individual objects can also be patched without having to affect all other instances of its class. There is, however, a little gotcha when doing it. Lets look at the following example:
def herd(self, sheep):self.run()self.bark()self.run()border_collie = Dog()border_collie.herd = herd
Now, let's try calling our newly defined method:
border_collie.herd(sheep)TypeError: herd() takes exactly 2 arguments (1 given)
The problem with the previous code is that
herd is not a bound method, just take a look at the following code:
print border_collie.herd<function herd at 0x10427c2a8>
This means that the object being called is not passed as the first argument to the function, causing the error we previously saw. We can of course pass the instance ourselves, but that wouldn't work when replacing methods. The correct way of patching an object is by using the MethodType function in the types module like so:
import typesborder_collie = Dog()border_collie.herd = types.MethodType(herd, border_collie)print border_collie.herd<bound method ?.herd of <__main__.Dog instance at 0x10427e7e8>>
As we can see the method is now bound and we can safely call it.
Replacing or adding methods at run-time can be extremely useful. While it is often used (e.g. sometimes functions for communicating with external services are replaced when unit-testing) it's extremely important to keep code maintainability in mind before deciding to do so.