Policy-based design in python
This blog is part of a series dedicated to policy-based and mixins approaches in python. In the Series page you can find all the posts of the series. |
- Policy-based design
- Policy and host classes
- Strategy and Dependency Injection
- Elements of policy-based designed
- An example in a more realistic application
- Final remarks
- Update
- References
Policy-based design
Policy-based design is an approach1 pioneer (as far as I know) in the book Modern C++ Design by Andrei Alexandrescu2. Most people believe that this idiom can only be implemented in C++ because it looks like as a compile-time analog of Strategy pattern that works at runtime3. As a result, this pattern may not exist for interpreted languages such as python4. Before I move forward with this post, I need to clarify what I call policy-based design.
For me, the goal of policy-based design is to provide a collection of classes where their inheritance relationships are not entirely defined in advance. It is developer responsibility to assign those relationships based on the project requirements. A developer mostly establishes those relationships at compile or run time depending on the language. I am using the expression at coding time to express any of the two previous scenarios, i.e. coding time = compile or run time.
In the following section, I am providing the definitions to accomplish this in Python. When possible, I will show what I think is the closest equivalent on C++ as reference.
Policy and host classes
Under this pattern, classes are divided between policy classes and host classes. Policy classes implement modifications to the behavior or to the interface of a host class by inheritance. For example, here it is the Python code for the HelloWorld example found in the Wikipedia entry5.
Note: find full example in my github.
The EnglishLanguage and the FinnishLanguage are two policy classes that implement the “Hello World!” messages in different languages. The class PrintOutput is for now the only class that implements an output policy for the message. Each of these policy classes affects the host class HelloWorld throughout inheritance at coding time. It is possible, therefore, for a developer to customize the HelloWorld class for English or Finnish, and also for a particular output class based on the project requirements.
Strategy and Dependency Injection
The previous code can be implemented using a Strategy or Dependency Injection pattern in Python6 if we are only interesting of changing the behavior of the HelloWorld:
I think that the main reason people do not explore the use of the policy-based design in Python is due to this perceived equivalence with these runtime patterns. However, I would like to argue that policy-based design is a more general idiom that can express Strategy or Dependency Injection patterns as particular cases.
Using the policy-based design I can change both the behavior and also the interface of the HelloWorld class! Using the first example I can define a new SaveOutput policy class:
Note: find full example in my github.
The nature of the class HelloWorldEnglish is not the same as before, because the class has now a new member function set_filename inherited from SaveOutput. For this particular case, it might be possible to write a program that does the same using dependency injection and functors. However, the main issue is not about behavior, but rather what you need to express is a new class:
Policy-based design allows us to create new classes (not only new functionalities) using as building block predefine host and policy classes.
Elements of policy-based designed
Multiple inheritance
Multiple inheritance is a feature in both C++ and Python, so no need to say more.
Postpone inheritance
Policy-based design requires postponing the definition of the inheritance relationship between host and policy classes until coding time. You can accomplish this in C++ using templates.
In Python, the same can be done using a factory method, as shown below.
The function HostClass is a factory of the “class _”. I am using the class name “_” to signal that the name of the class inside of the factory is irrelevant. I define the structure of the host class inside of the factory. However, only when calling the factory function, the host class inherits from the policy classes.
Class instantiation
I say that the host class is instantiated when inherits from policy classes. This instantiation is done in C++ directly when coding or by using typedef.
In Python, I do the class instantiation by calling the factory function and passing the policy classes as arguments.
A policy can be an instantiated host class
An instantiated host class can also be a policy class. As a result, it is possible to create new policies by the composition of multiple successions host and policy classes, as shown below.
Note: find full example in my github.
An example in a more realistic application
One example I can show when I am applying policy-based design is my implementation of an interval tree. An interval tree is a specialized binary tree that supports operation in a dynamic set of closed intervals7. In particular, this tree needs an extra function in the interface that I called interval(…). This function takes as input an interval and returns a node in the tree representing overlapping interval or none otherwise.
In principle, any self-balanced tree can be used to build this class, resulting in the following options:
- define an interval tree using one type of tree implementation, or
- define several interval trees as a child of all and each available tree implementation, or
- set interval tree-like class that get the tree implementation by object composition, or
- define interval tree as a host class and inherence the tree implementations as a policy!
I opted to use the last approach because it clearly expresses that the interval tree is a tree (because of inherence the full tree interface from the policy). The host class implement the small addition to the tree interface is needed to query for overlapping intervals:
Note: find full example in my pyds/avl_internal_tree.
If you suspect that interval values are inserted somewhat randomly, then you might want to avoid the use of self-balanced tree altogether!
Note: find full example in my pyds/simple_internal_tree.
Final remarks
I hope I was able to convince you that it is possible to have a policy-based design in Python. However, likely, this approach is not as attractive as it is in C++ due to the fact Python is an interpreted language, and there might be powerful runtime alternatives. There is also a certain amount of controversy in the C++ community about this approach (do not take my word for it, just google it).
Therefore, remember to think twice if you need this approach in Python because as always with great power comes great responsibility!8
Update
Find my entry in stackoverflow with the question:
What are the measures to call a Python code a policy-based design?
I will be soon answer (my own question) with a summary of all I found about this issue. However, before that I am planning to write a accompanying post as a follow up of this blog.
References
-
Modern C++ Design: Generic Programming and Design Patterns Applied ↩
-
Example in C++ example in Wikipedia: Modern C++ Design, Policy-based design. In the Python example, I am using the Python convention that one underscore implies a protected object variable. I am also doing the example in Finnish instead of German just for fun! ↩
-
Interval trees in Chapter 14, page 348 of Introduction to Algorithms, 3rd edition. ↩
Note of a interdiciplinarian by Victor E. Bazterra is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.