Giacomo Debidda

Strategy pattern in Python

January 28, 2017 | 4 min Read

Strategy (also known as Policy) is a behavioral design pattern that enables an algorithm’s behavior to be selected at runtime.

All implemented behaviors are either classes, methods, or functions, and they are usually called strategies. The portion of code that decides which strategy to adopt is called context.

Strategy follows two important principles:

  • Open/closed principle: software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
  • Inversion of Control principle: custom-written portions of a program (e.g. a method in a subclass) receive the flow of control from a generic framework (e.g. a base class).

Following these two principles is extremely useful when you want to design a common interface (the Closed part of the Open/Closed principle), but allow for changes in the implementation details (the Open part of the Open/Closed principle). Every time you want to program a new implementation, you pass it to the common interface without altering anything in the interface code, and you plug the client code to the interface. This way the client code is loosely coupled, namely it is coupled only with an abstraction (i.e. the common interface), not with the concrete implementations (i.e. the various strategies).

How to implement a Strategy pattern in Python?

In programming languages like Java you can implement the Strategy pattern by creating a common (abstract) interface and subclassing it with a new class for each strategy. You can do the same in Python, like it’s done here. However, you can also use a leaner approach: create a single Strategy class and replace a method of that class, at runtime, with a different function based on a given context.

Enough talking, let’s see some code!

The Strategy class

This class is the interface that the client code will use. It represents the Closed part of the Open/Closed principle, so it should not be modified.

If the client code does not provide a function func, Strategy will use the default algorithm, namely execute in this example.

In the __init__ method we are taking advantage of the fact that Python supports higher order functions, namely we can pass a function as an argument to another function, and that Python functions are first class objects, so they can be assigned to variables, or stored in data structures (e.g. a dict).

If the client provides a function func, this will be passed to the __init__ method and assigned to the execute method. This means that the execute method will be redefined when the Strategy class is instantiated.

class Strategy(object):

    def __init__(self, func=None):
        if func is not None:
            self.execute = func
            self.name = '{}_{}'.format(self.__class__.__name__, func.__name__)
        else:
            self.name = '{}_default'.format(self.__class__.__name__)

    def execute(self):
        print('Default method')
        print('{}\n'.format(self.name))

That’s cool, but there is a problem: func is just a function, it contains no reference to the instance it is bound to (like a Python method defined by using the @staticmethod decorator). Within the redefined execute you cannot access other methods or attributes of the instance.

However with some Python magic and the help of the types module you can convert a normal function into a bound method, namely a function that contains a reference to the instance it is bound to.

import types


class Strategy(object):

    def __init__(self, func=None):
        if func is not None:
            # take a function, bind it to this instance, and replace the default bound method 'execute' with this new bound method.
            self.execute = types.MethodType(func, self)
            self.name = '{}_{}'.format(self.__class__.__name__, func.__name__)
        else:
            self.name = '{}_default'.format(self.__class__.__name__)

    def execute(self):
        print('Default method')
        print('{}\n'.format(self.name))

Now it’s time to implement some strategies.

Implement the strategies

Let’s define a couple of replacement strategies for the default method execute. Don’t mind the self parameter, for now these ones are just regular functions. I decided to use self because I know that these functions, passed to the Strategy’s __init__ method, will be bound to an instance of Strategy (this is done when the line types.MethodType(func, self) is executed).

def execute_replacement1(self):
    print('Replacement1 method')
    print('{}\n'.format(self.name))


def execute_replacement2(self):
    print('Replacement2 method')
    print('{}\n'.format(self.name))

Select a strategy at runtime

In this simple example the part of the program which decides which strategy to use, namely the context, is the main function. As you can see, the three instances of Strategy are calling the same method.

def main():

    s0 = Strategy()
    s0.execute()

    s1 = Strategy(execute_replacement1)
    s1.execute()

    s2 = Strategy(execute_replacement2)
    s2.execute()

if __name__ == '__main__':
    main()

This is the output:

>>> Default method
>>> Strategy_default

>>> Replacement1 method
>>> Strategy_execute_replacement1

>>> Replacement2 method
>>> Strategy_execute_replacement2

An even simpler Strategy

Sometimes you don’t even have to create a class, and you can achieve a similar result by assigning a function to an object, and then later calling that object. For example, this is also a Strategy:

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

solve = add
solve(a, b)

solve = subtract
solve(a, b)

Strategy VS Template Method

Strategy and Template Method are very similar and follow the same principles. The main difference is that in Template Method an implementation is chosen at compile time by inheritance, while in Strategy is chosen at runtime by containment. See also here to understand the difference between these two behavioral design patterns.

You need the code? Grab it here!


Giacomo DebiddaWritten by Giacomo Debidda, Pythonista & JS lover (D3, React). You can find me on Twitter & Github