Let’s say it again: Python is a high-level programming language with dynamic typing and dynamic binding. I would describe it as a powerful, high-level dynamic language. Many developers are in love with Python because of its clear syntax, well structured modules and packages, and for its enormous flexibility and range of modern features.
In Python, nothing obliges you to write classes and instantiate objects from them. If you don’t need complex structures in your project, you can just write functions. Even better, you can write a flat script for executing some simple and quick task without structuring the code at all.
At the same time Python is a 100 percent object-oriented language. How’s that? Well, simply put, everything in Python is an object. Functions are objects, first class objects (whatever that means). This fact about functions being objects is important, so please remember it.
So, you can write simple scripts in Python, or just open the Python terminal and execute statements right there (that’s so useful!). But at the same time, you can create complex frameworks, applications, libraries and so on. You can do so much in Python. There are of course a number of limitations, but that’s not the topic of this article.
However, because Python is so powerful and flexible, we need some rules (or patterns) when programming in it. So, let see what patterns are, and how they relate to Python. We will also proceed to implement a few essential Python design patterns.
Why Is Python Good For Patterns?
Any programming language is good for patterns. In fact, patterns should be considered in the context of any given programming language. Both the patterns, language syntax and nature impose limitations on our programming. The limitations that come from the language syntax and language nature (dynamic, functional, object oriented, and the like) can differ, as can the reasons behind their existence. The limitations coming from patterns are there for a reason, they are purposeful. That’s the basic goal of patterns; to tell us how to do something and how not to do it. We’ll speak about patterns, and especially Python design patterns, later.
Python’s philosophy is built on top of the idea of well thought out best practices. Python is a dynamic language (did I already said that?) and as such, already implements, or makes it easy to implement, a number of popular design patterns with a few lines of code. Some design patterns are built into Python, so we use them even without knowing. Other patterns are not needed due of the nature of the language.
For example, Factory is a structural Python design pattern aimed at creating new objects, hiding the instantiation logic from the user. But creation of objects in Python is dynamic by design, so additions like Factory are not necessary. Of course, you are free to implement it if you want to. There might be cases where it would be really useful, but they’re an exception, not the norm.
What is so good about Python’s philosophy? Let’s start with this (explore it in the Python terminal):
import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
These might not be patterns in the traditional sense, but these are rules that define the “Pythonic” approach to programming in the most elegant and useful fashion.
We have also PEP-8 code guidelines that help structure our code. It’s a must for me, with some appropriate exceptions, of course. By the way, these exceptions are encouraged by PEP-8 itself:
“But most importantly: know when to be inconsistent – sometimes the style guide just doesn’t apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don’t hesitate to ask!”
Combine PEP-8 with The Zen of Python (also a PEP – PEP-20), and you’ll have a perfect foundation to create readable and maintainable code. Add Design Patterns and you are ready to create every kind of software system with consistency and evolvability.
Python Design Patterns
What Is A Design Pattern?
Everything starts with the Gang of Four (GOF). Do a quick online search if you are not familiar with the GOF.
Design patterns are a common way of solving well known problems. Two main principles are in the bases of the design patterns defined by the GOF:
- Program to an interface not an implementation.
- Favor object composition over inheritance.
Let’s take a closer look at these two principles from the perspective of Python programmers.
Program to an interface not an implementation
Think about Duck Typing. In Python we don’t like to define interfaces and program classes according these interfaces, do we? But, listen to me! This doesn’t mean we don’t think about interfaces, in fact with Duck Typing we do that all the time.
Let’s say some words about the infamous Duck Typing approach to see how it fits in this paradigm: program to an interface.
We don’t bother with the nature of the object, we don’t have to care what the object is; we just want to know if it’s able to do what we need (we are only interested in the interface of the object).
Can the object quack? So, let it quack!
try: bird.quack() except AttributeError: self.lol()
Did we define an interface for our duck? No! Did we program to the interface instead of the implementation? Yes! And, I find this so nice.
As Alex Martelli points out in his well known presentation about Design Patterns in Python, “Teaching the ducks to type takes a while, but saves you a lot of work afterwards!”
Favor object composition over inheritance
Now, that’s what I call a Pythonic principle! I have created fewer classes/subclasses compared to wrapping one class (or more often, several classes) in another class.
Instead of doing this:
class User(DbObject): pass
We can do something like this:
class User: _persist_methods = ['get', 'save', 'delete'] def __init__(self, persister): self._persister = persister def __getattr__(self, attribute): if attribute in self._persist_methods: return getattr(self._persister, attribute)
The advantages are obvious. We can restrict what methods of the wrapped class to expose. We can inject the persister instance in runtime! For example, today it’s a relational database, but tomorrow it could be whatever, with the interface we need (again those pesky ducks).
Composition is elegant and natural to Python.
Behavioural Patterns involve communication between objects, how objects interact and fulfil a given task. According to GOF principles, there are a total of 11 behavioral patterns in Python: Chain of responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor.
I find these patterns very useful, but this does not mean the other pattern groups are not.
Iterators are built into Python. This is one of the most powerful characteristics of the language. Years ago, I read somewhere that iterators make Python awesome, and I think this is still the case. Learn enough about Python iterators and generators and you’ll know everything you need about this particular Python pattern.
Chain of responsibility
This pattern gives us a way to treat a request using different methods, each one addressing a specific part of the request. You know, one of the best principles for good code is the Single Responsibility principle.
Every piece of code must do one, and only one, thing.
This principle is deeply integrated in this design pattern.
For example, if we want to filter some content we can implement different filters, each one doing one precise and clearly defined type of filtering. These filters could be used to filter offensive words, ads, unsuitable video content, and so on.
class ContentFilter(object): def __init__(self, filters=None): self._filters = list() if filters is not None: self._filters += filters def filter(self, content): for filter in self._filters: content = filter(content) return content filter = ContentFilter([ offensive_filter, ads_filter, porno_video_filter]) filtered_content = filter.filter(content)
This is one of the first Python design patterns I implemented as a programmer. That reminds me: Patterns are not invented, they are discovered. They exist, we just need to find and put them to use. I discovered this one for an amazing project we implemented many years ago: a special purpose WYSIWYM XML editor. After using this pattern intensively in the code, I read more about it on some sites.
The command pattern is handy in situations when, for some reason, we need to start by preparing what will be executed and then to execute it when needed. The advantage is that encapsulating actions in such a way enables Python developers to add additional functionalities related to the executed actions, such as undo/redo, or keeping a history of actions and the like.
Let’s see what a simple and frequently used example looks like:
class RenameFileCommand(object): def __init__(self, from_name, to_name): self._from = from_name self._to = to_name def execute(self): os.rename(self._from, self._to) def undo(self): os.rename(self._to, self._from) class History(object): def __init__(self): self._commands = list() def execute(self, command): self._commands.append(command) command.execute() def undo(self): self._commands.pop().undo() history = History() history.execute(RenameFileCommand('docs/cv.doc', 'docs/cv-en.doc')) history.execute(RenameFileCommand('docs/cv1.doc', 'docs/cv-bg.doc')) history.undo() history.undo()