Python @ DjangoSpin

Python: Replacing a method dynamically in a class

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 5 minutes

Replacing a method dynamically in Python

Replacing a method dynamically in Python

There are 3 ways to dynamically replace the contents of a method defined inside a class with contents of a function defined outside the class.

Setting the context:

Consider a class ClassOne, having a method methodOne. This method prints a statement on to the console.

class ClassOne:
    def __init__(self):
        pass

    def methodOne(self):
        print("This is the Default Method.")

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method. ####

We wish to replace the contents of the method methodOne with the contents of a function defined outside the class, say substituteFunction, so that when we call the method methodOne, the contents of the substituteFunction are executed.


# 1

We achieve the above stated purpose by passing the substituteFunction as an argument to the constructor of classOne while creating its object. The __init__ method of classOne, which is executed every time an object of classOne is created, accepts the substituteFunction graciously, and assigns it to methodOne. This will lead to the contents of substituteFunction being executed when methodOne is called. By making the replacementMethod a keyword argument and setting it to None, we are handling cases where the user wants default behavior of methodOne. You can make it a positional argument instead, I'll leave that to you. For the entirety of this post, we'll keep it as a keyword argument.

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = replacementMethod

    def methodOne(self):
        print("This is the Default Method.")

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method. ###

def substituteFunction():
    print("This is the Substitute Function.")

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function. ###

The above code does the trick, but the substituteFunction is static. If we need to use a reference to the current instance in the substituteFunction, then it needs the self argument passed to it. Right now, it does not get the self argument passed to it. To highlight this limitation, let's have the methods print the name of the class classOne.

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = replacementMethod

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()

### OUTPUT ###
Traceback (most recent call last):
    objectTwoOfClassOne.methodOne()
TypeError: substituteFunction() missing 1 required positional argument: 'objectReference'

This leads us to way # 2.


# 2

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            ClassOne.methodOne = replacementMethod

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

To overcome the limitation discussed earlier, we use the class name instead of self in the init method. This works, but why? Why does ClassOne.methodOne = replacementMethod work and not self.methodOne = replacementMethod? Let's take a detour to understand this.

Say that you have two methods, one that doesn't take any explicit argument, and the other takes 1 explicit argument. The word 'explicit' is important here, we'll see later. We can assign a 0-argument accepting method to a 1-argument accepting method. After the assignment, we can call the 1-argument accepting method in the same way as the 0-argument accepting method i.e. with 0 arguments. Let's call it case A, for future reference.

def takes1Arg(i): pass
def takes0Arg(): pass
takes1Arg = takes0Arg
takes1Arg() 							# executes body of takes0Arg successfully

Similarly, we can assign a 1-argument accepting method to a 0-argument accepting method. After the assignment, we can call the 0-argument accepting method in the same way as the 1-argument method i.e. with 1 argument. But now, you cannot call 0-argument accepting method with 0 number of arguments, this will raise a TypeError. Let's call it case B, for future reference.

def takes1Arg(i): pass
def takes0Arg(): pass
takes0Arg = takes1Arg
takes0Arg()								# attempt to execute body of takes1Arg, raises TypeError saying "takes1Arg() missing 1 required positional argument: 'i'"
takes0Arg(50)							# executes body of takes1Arg successfully

In the last section, we looked at method # 1 of dynamically replacing the contents of a method defined inside a class with contents of a method defined outside the class. We are essentially doing the following there

def methodOne(self): pass
def substituteFunction(): pass
self.methodOne = substituteFunction		# i.e. takes0Arg = takes0Arg
objectOfClassOne.methodOne()			# executes body of substituteFunction successfully

The reason why the above assignment is as good as takes0Arg = takes0Arg is that the first argument in all methods defined inside a class, is an implicit argument, one that refers to the current instance of the class. The convention is to call this argument 'self', but it can be named anything. 'self' is effectively a local variable of the class, which is used to pass around the particular instance of the class being operated on. This implicit argument is always there when we define methods inside a class, but we do not supply this argument while calling these methods. Likewise, we declare methodOne with a self argument, but don't supply it while calling methodOne. So, effectively, it is a method with no explicit arguments.

Now, let's look at the code in method # 1 where we highlighted the limitation that the substituteFunction is static:

def methodOne(self): pass
def substituteFunction(objectReference): pass
self.methodOne = substituteFunction		# i.e. takes0Arg = takes1Arg
objectOfClassOne.methodOne()			# attempts to execute body of substituteFunction by passing 0 number of arguments, raises a TypeError saying "substituteFunction() missing 1 required positional argument: 'objectReference'"

This is analogous to case B. So, we know why this fails. After the assignment takes0Arg = takes1Arg, we can call the 0-argument accepting method in the same way as the 1-argument method i.e. with 1 argument. But now, you cannot call 0-argument accepting method with 0 number of arguments, this will raise a TypeError.

Now, coming to our grand finale. Let's write the code in method # 2 in simple terms:

def methodOne(self): pass
def substituteFunction(objectReference): pass
ClassOne.methodOne = substituteFunction	# i.e. takes1Arg = takes1Arg
objectOfClassOne.methodOne()			# executes body of substituteFunction successfully

But wait, how does ClassOne.methodOne take 1 explicit argument? The following code snippet will make this clear.

>>> class ClassOne:
    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

        
>>> ClassOne.methodOne()
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    ClassOne.methodOne()
TypeError: methodOne() missing 1 required positional argument: 'self'

>>> ClassOne.methodOne('hi')
This is the Default Method in str.

>>> objectOfClassOne = ClassOne()
>>> ClassOne.methodOne(objectOfClassOne)
This is the Default Method in ClassOne.

So, ClassOne.methodOne requires one explicit argument, an object. And the 'objectReference' argument of method substituteFunction caters to this. Eventually, this 'objectReference' becomes the implicit first argument of the method methodOne. Remember, that we can call the first implicit argument to methods (inside classes) anything, it doesn't really matter. Python will always treat the first argument as a reference to the current object of the class, no matter what you call it.

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            ClassOne.methodOne = replacementMethod

    def methodOne(anotherObjectReference):
        print("This is the Default Method in {}.".format(anotherObjectReference.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

# 3

There is another way to dynamically replace contents of a method defined inside a class with contents of a function defined outside the class. This time, we will use the MethodType function of the types module. Here's the finished code. We'll discuss how the MethodType does the trick after the following code snippet.

import types
import sys

if sys.version_info[0] > 2:  									# Python 3
    createBoundMethod = types.MethodType
else:											# Python 2
    def createBoundMethod(func, obj):
        return types.MethodType(func, obj, obj.__class__)

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = createBoundMethod(replacementMethod, self)

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

The MethodType of module types binds a function defined outside the class to a single instance of a class, or to all instances of the class. This approach is not only capable of replacing the contents of a method inside the class with contents of function outside it, but is also capable of adding a method to the class definition. The MethodType takes two arguments in Python 3, first, the method to be bound, second, the instance to which the method is to be bound. In Python 2, there is third positional argument, which seeks the class to which the instance passed as second argument, belongs.

# Binding a method to a single instance of a class
# In the following example, the function being bound (functionOne) is being added to the instance, and not replacing any of its methods.

import types

class ClassOne:
    def __init__(self):
        pass

def functionOne(objectReference):
    print("This is Function One in {}.".format(objectReference.__class__.__name__))
    
obectOneOfClassOne = ClassOne()
obectOneOfClassOne.functionOne = types.MethodType(functionOne, obectOneOfClassOne)
obectOneOfClassOne.functionOne()                          ### OUTPUT: This is Function One in ClassOne. ###

obectTwoOfClassOne = ClassOne()
obectTwoOfClassOne.functionOne = types.MethodType(functionOne, obectTwoOfClassOne)
obectTwoOfClassOne.functionOne()                          ### OUTPUT: This is Function One in ClassOne. ###

obectThreeOfClassOne = ClassOne()
obectThreeOfClassOne.functionOne()

### OUTPUT ###
##Traceback (most recent call last):
##    obectThreeOfClassOne.functionOne()
##AttributeError: 'ClassOne' object has no attribute 'functionOne'
# Binding a method to all instances of a class
# In the following example, the function being bound (functionOne) is being added to each instance, and not replacing any of its methods. This function is being added by the name of methodOne.

import types

class ClassOne:
    def __init__(self, replacementMethod):
        self.methodOne = types.MethodType(replacementMethod, self)

def functionOne(objectReference):
    print("This is Function One in {}.".format(objectReference.__class__.__name__))
    
obectOneOfClassOne = ClassOne(functionOne)
obectOneOfClassOne.methodOne()                          ### OUTPUT: This is Function One in ClassOne. ###

obectTwoOfClassOne = ClassOne(functionOne)
obectTwoOfClassOne.methodOne()                          ### OUTPUT: This is Function One in ClassOne. ###

obectThreeOfClassOne = ClassOne(functionOne)
obectThreeOfClassOne.methodOne()                        ### OUTPUT: This is Function One in ClassOne. ###
# Binding a method to all instances of a class
# In the following example, the function being bound (functionOne) is replacing the contents of methodOne.

import types

class ClassOne:
    def __init__(self, replacementMethod):
        self.methodOne = types.MethodType(replacementMethod, self)

    def methodOne(self):
        print("This is Method One in {}.".format(self.__class__.__name__))

def functionOne(objectReference):
    print("This is Function One in {}.".format(objectReference.__class__.__name__))
    
obectOneOfClassOne = ClassOne(functionOne)
obectOneOfClassOne.methodOne()                          ### OUTPUT: This is Function One in ClassOne. ###

obectTwoOfClassOne = ClassOne(functionOne)
obectTwoOfClassOne.methodOne()                          ### OUTPUT: This is Function One in ClassOne. ###

ALTERNATIVELY, to bind a method to all instances of a class, you can use a simple assignment outside the class: ClassName.methodName = functionName.

class ClassOne:
    def methodOne(self):
        print("This is Method One in {}.".format(self.__class__.__name__))

def functionOne(objectReference):
    print("This is Function One in {}.".format(objectReference.__class__.__name__))

ClassOne.methodTwo = functionOne
    
obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodTwo()                          ### OUTPUT: This is Function One in ClassOne. ###

obectTwoOfClassOne = ClassOne()
obectTwoOfClassOne.methodTwo()                          ### OUTPUT: This is Function One in ClassOne. ###

obectThreeOfClassOne = ClassOne()
obectThreeOfClassOne.methodTwo()                        ### OUTPUT: This is Function One in ClassOne. ###

This approach does not use the ModuleType method of the types module, because we don't need it here. All functions in the class definition become methods and receive the self argument by default. We are not using this approach in method # 3 because we are making the replacement inside the class in its init() method, not outside the class.

Coming back to way # 3, this is how it is shaping up so far:

import types

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = types.MethodType(replacementMethod, self)

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

The above code doesn't is not Python 2 compatible, since types.MethodType in Python 2 requires 3 positional arguments. We need to add the capability of picking the right amount of arguments depending on the Python version. This can be facilitated by using version_info attribute of the sys module, which returns a tuple-like object (it's actually an object of class sys.version_info), whose first element represents the version of Python installed on the user's system.

>>> import sys
>>> type(sys.version_info)
<class 'sys.version_info'>
>>> sys.version_info
sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0)
>>> sys.version_info[0]
3

Let's add another conditional statement in the init() method to incorporate this:

import types
import sys

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            if sys.version_info[0] < 3:
                self.methodOne = types.MethodType(replacementMethod, self, self.__class__)
            else:
                self.methodOne = types.MethodType(replacementMethod, self)

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

The code can be simplified by moving this if construct outside the class. We can create a function createBoundMethod that returns the correct version of the MethodType in the following manner:

if sys.version_info[0] > 2:  									# Python 3
    createBoundMethod = types.MethodType
else:															# Python 2
    def createBoundMethod(func, obj):
        return types.MethodType(func, obj, obj.__class__)

Then, we can use the createBoundMethod in the init() method with replacementMethod and self as its arguments. This rounds up our way # 3 to dynamically replace the contents of a method defined inside a class with contents of a function defined outside the class.

import types
import sys

if sys.version_info[0] > 2:  									# Python 3
    createBoundMethod = types.MethodType
else:											# Python 2
    def createBoundMethod(func, obj):
        return types.MethodType(func, obj, obj.__class__)

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = createBoundMethod(replacementMethod, self)

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###
FYI: The types.MethodType method is particularly useful while implementing Strategy Pattern & while writing Unit Tests.

SUMMARY:

Here is a recap of the three methods we discussed.

# 1
class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = replacementMethod

    def methodOne(self):
        print("This is the Default Method.")

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method. ###

def substituteFunction():
    print("This is the Substitute Function.")

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function. ###
# 2
class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            ClassOne.methodOne = replacementMethod

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###
# 3
import types
import sys

if sys.version_info[0] > 2:  									# Python 3
    createBoundMethod = types.MethodType
else:											# Python 2
    def createBoundMethod(func, obj):
        return types.MethodType(func, obj, obj.__class__)

class ClassOne:
    def __init__(self, replacementMethod = None):
        if replacementMethod:
            self.methodOne = createBoundMethod(replacementMethod, self)

    def methodOne(self):
        print("This is the Default Method in {}.".format(self.__class__.__name__))

obectOneOfClassOne = ClassOne()
obectOneOfClassOne.methodOne()                                             ### OUTPUT: This is the Default Method in ClassOne. ###

def substituteFunction(objectReference):
    print("This is the Substitute Function in {}.".format(objectReference.__class__.__name__))

objectTwoOfClassOne = ClassOne(substituteFunction)
objectTwoOfClassOne.methodOne()                                            ### OUTPUT: This is the Substitute Function in ClassOne. ###

See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply