Notes about Design Patterns in Python
Last Updated: November 10, 2020 by Pepe Sandoval
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...
Design patterns are common architectural approaches that people have observed as they've been designing object oriented software and people decided to make a catalog out of the most common ones
The opposites of pattern is an anti-pattern
self) to chain several callsdeep.copy()
Patterns concerned with the structure of the classes. Concerned with
These type of patterns are usually wrapper patterns so they are all about the idea of making the interface has convenient to use as possible (Make objects usable and makes API is usable for other people)

If you have a class that class should have primary responsibility and should not take on other responsibilities.
Do NOT overload your classes with too many responsibilities
For example adding the responsibility of storing its data in a persistent way (the responsibility of persistence) to a class that is supposed to be an abstraction for a user, a student, a journal, etc. It would be better to have a PersitanceManager which will be the one responsible of saving an object to a file
It enforces this idea that a class should have a single reason to change and that change should be somehow related to its primary responsibility.
The god object is an anti-pattern where you add all the functionality in a class ending up with a massive class
Different classes should handle different independent tasks/problems
OCP = open for extension but closed for modification
In a strict sense the OCP suggests that when you add new functionality you add ONLY via extension not via modification
At the implementation level the OCP suggest that after you've written and tested a particular class or code you should not modify it instead you should extend it.
If we continue to modify a class adding and adding functionality we could cause what is called state space explosion meaning we have a class with too many methods or functionality
class Specification:
def is_satisfied(self, item):
pass
# and operator makes life easier
def __and__(self, other):
return AndSpecification(self, other)
class Filter:
def filter(self, items, spec):
pass
class ColorSpecification(Specification):
def __init__(self, color):
self.color = color
def is_satisfied(self, item):
return item.color == self.color
class SizeSpecification(Specification):
def __init__(self, size):
self.size = size
def is_satisfied(self, item):
return item.size == self.size
class AndSpecification(Specification):
def __init__(self, *args):
self.args = args
def is_satisfied(self, item):
return all(map(
lambda spec: spec.is_satisfied(item), self.args))
class BetterFilter(Filter):
def filter(self, items, spec):
for item in items:
if spec.is_satisfied(item):
yield item
apple = Product('Apple', Color.GREEN, Size.SMALL)
tree = Product('Tree', Color.GREEN, Size.LARGE)
house = Product('House', Color.BLUE, Size.LARGE)
products = [apple, tree, house]
bf = BetterFilter()
print('Green products (new):')
green = ColorSpecification(Color.GREEN)
for p in bf.filter(products, green):
print(f' - {p.name} is green')
print('Large products:')
large = SizeSpecification(Size.LARGE)
for p in bf.filter(products, large):
print(f' - {p.name} is large')
print('Large blue items:')
""" large_blue = AndSpecification(large, ColorSpecification(Color.BLUE)) """
large_blue = large & ColorSpecification(Color.BLUE)
for p in bf.filter(products, large_blue):
print(f' - {p.name} is large and blue')
LSP states that if you have some interface that takes some sort of base class you should be able to stick a derived class in there and everything should work.
When you have a function or interface that takes as input a base class you should be able to stick in any of its inheritor. e.g. if you have a move() function that takes as input any object of the class Animal if you put there any object that inherits from Animal it should work, like Lion, Tiger, Dog, etc.
you should be able to substitute a base type for a derivative type
The ISP basically says" Don't put too much into an interface, split it into separate interfaces
We don't want to put to much into a single interface because YAGNI (You aren't gonna need it)
The idea is that you don't really want to stick to many elements (too many methods) into an interface.
Making interfaces which feature too many elements is not a good idea because you're forcing your clients to define methods which they might not even need.
Instead of having one large interface with several members/methods you have separate interfaces that people can implement. Split them into the smallest interfaces you can build so that people don't have to implement more than they need to.
class Printer:
@abstractmethod
def print(self, document): pass
class Scanner:
@abstractmethod
def scan(self, document): pass
class MyPrinter(Printer):
def print(self, document):
print(document)
class Photocopier(Printer, Scanner):
def print(self, document):
print(document)
def scan(self, document):
pass # something meaningful
class MultiFunctionDevice(Printer, Scanner): # , Fax, etc
@abstractmethod
def print(self, document):
pass
@abstractmethod
def scan(self, document):
pass
class MultiFunctionMachine(MultiFunctionDevice):
def __init__(self, printer, scanner):
self.printer = printer
self.scanner = scanner
def print(self, document):
self.printer.print(document)
def scan(self, document):
self.scanner.scan(document)
DIP states that high-level modules should not depend upon low-level ones, use abstractions instead
In other words High level classes or high level modules in your code should not use directly low level modules instead they should depend on abstractions (Abstract class or class with abstract methods)
Essentially you just want to depend on interfaces so you can swap them at the low level without needing to change high-level module's code
A high-level module is a modules that uses others modules (low-level ones) which can be much closer to the hardware
Builder definition: When piecewise object construction is complicated provide an API for doing it succinctly (precisely and clearly)
The Builder pattern is used when we need to create object that requires a lot steps to initialize or a lot of arguments because it can be configured in many ways
Attempts to breakdown the whole initialization of an object into methods of a special component called the Builder
The Builder provides an API (set of functions or methods) for constructing an object in a clear and explicit way
This patterns tries to make the implementation of the construction of an object a piecewise construction instead of a having a massive call to a initializer that does everything
Required when you have several processes needed to construct something
Usually a builder will at some point call the constructor of another class to build/create an object and also store a reference to this object so the operations are perform through the builder even if they are defined in another class
class HtmlElement:
indent_size = 2
def __init__(self, name="", text=""):
self.name = name
self.text = text
self.elements = []
def __str__(self, indent):
lines = []
i = ' ' * (indent * self.indent_size)
lines.append(f'{i}<{self.name}>')
if self.text:
i1 = ' ' * ((indent + 1) * self.indent_size)
lines.append(f'{i1}{self.text}')
for e in self.elements:
lines.append(e.__str(indent + 1))
lines.append(f'{i}{self.name}>')
return '\n'.join(lines)
def __str__(self):
return self.__str(0)
@staticmethod
def create(name):
return HtmlBuilder(name)
class HtmlBuilder:
__root = HtmlElement()
def __init__(self, root_name):
self.root_name = root_name
self.__root.name = root_name
def add_child(self, child_name, child_text): # not fluent
self.__root.elements.append(
HtmlElement(child_name, child_text)
)
def add_child_fluent(self, child_name, child_text): # fluent
self.__root.elements.append(
HtmlElement(child_name, child_text)
)
return self
def clear(self):
self.__root = HtmlElement(name=self.root_name)
def __str__(self):
return str(self.__root)
""" ordinary non-fluent builder """
""" builder = HtmlBuilder('ul') """
builder = HtmlElement.create('ul')
builder.add_child('li', 'hello')
builder.add_child('li', 'world')
print('Ordinary builder:')
print(builder)
""" fluent builder """
builder.clear()
builder.add_child_fluent('li', 'hello').add_child_fluent('li', 'world')
print('Fluent builder:')
print(builder)
If you have an object that is very complicated to build you may need more than one builder or in other words multiple builders
For a multiple builders implementation we usually have a base builder that creates and stores an empty object that can be subsequently build up or in other words initialized
class Person:
def __init__(self):
print('Creating an instance of Person')
# address
self.street_address = None
self.postcode = None
self.city = None
# employment info
self.company_name = None
self.position = None
self.annual_income = None
def __str__(self) -> str:
ret = f'Address: {self.street_address}, '
ret += f'{self.postcode}, '
ret += f'{self.city}\n'
ret += f'Employed at {self.company_name} '
ret += f'as a {self.position} '
ret += f'earning {self.annual_income}'
return ret
class PersonBuilder: # facade
def __init__(self, person=None):
if person is None:
self.person = Person()
else:
self.person = person
@property
def lives(self):
return PersonAddressBuilder(self.person)
@property
def works(self):
return PersonJobBuilder(self.person)
def build(self):
return self.person
class PersonJobBuilder(PersonBuilder):
def __init__(self, person):
super().__init__(person)
def at(self, company_name):
self.person.company_name = company_name
return self
def as_a(self, position):
self.person.position = position
return self
def earning(self, annual_income):
self.person.annual_income = annual_income
return self
class PersonAddressBuilder(PersonBuilder):
def __init__(self, person):
super().__init__(person)
def at(self, street_address):
self.person.street_address = street_address
return self
def with_postcode(self, postcode):
self.person.postcode = postcode
return self
def in_city(self, city):
self.person.city = city
return self
if __name__ == '__main__':
pb = PersonBuilder()
p = pb\
.lives\
.at('123 London Road')\
.in_city('London')\
.with_postcode('SW12BC')\
.works\
.at('Fabrikam')\
.as_a('Engineer')\
.earning(123000)\
.build()
print(p)
p2 = PersonBuilder()\
.lives\
.at('1234 Coco')\
.in_city('Guadalajara')\
.with_postcode('44865')\
.works.at('FSL MX')\
.as_a('Engineer')\
.earning(10000)\
.build()
print(p2)
class Person:
def __init__(self):
self.name = None
self.position = None
self.date_of_birth = None
def __str__(self):
return f'{self.name} born on {self.date_of_birth} works as a {self.position}'
class PersonBuilder:
def __init__(self):
self.person = Person()
def build(self):
return self.person
class PersonInfoBuilder(PersonBuilder):
def called(self, name):
self.person.name = name
return self
class PersonJobBuilder(PersonInfoBuilder):
def works_as_a(self, position):
self.person.position = position
return self
class PersonBirthDateBuilder(PersonJobBuilder):
def born(self, date_of_birth):
self.person.date_of_birth = date_of_birth
return self
if __name__ == '__main__':
pb = PersonBirthDateBuilder()
me = pb\
.called('Pepe')\
.works_as_a('coder')\
.born('1/1/1991')\
.build() # this does NOT work in C#/C++/Java/...
print(me)
Have the intention to make initializer/constructor descriptive, explicit and avoid optional parameter hell
A Factory is a component responsible solely for the complete creation of objects (not necessarily its initialization)
Types of factory implementation:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f'x: {self.x}, y: {self.y}'
@staticmethod
def new_cartesian_point(x, y):
return Point(x, y)
@staticmethod
def new_polar_point(rho, theta):
return Point(rho * sin(theta), rho * cos(theta))
# take out factory methods to a separate class
class PointFactory:
@staticmethod
def new_cartesian_point(x, y):
return Point(x, y)
@staticmethod
def new_polar_point(rho, theta):
return Point(rho * sin(theta), rho * cos(theta))
from abc import ABC
from enum import Enum, auto
class HotDrink(ABC):
def consume(self):
pass
class Tea(HotDrink):
def consume(self):
print('This tea is nice but I\'d prefer it with milk')
class Coffee(HotDrink):
def consume(self):
print('This coffee is delicious')
class HotDrinkFactory(ABC):
def prepare(self, amount):
pass
class TeaFactory(HotDrinkFactory):
def prepare(self, amount):
print(f'Put in tea bag, boil water, pour {amount}ml, enjoy!')
return Tea()
class CoffeeFactory(HotDrinkFactory):
def prepare(self, amount):
print(f'Grind some beans, boil water, pour {amount}ml, enjoy!')
return Coffee()
class HotDrinkMachine:
factories = {}
initialized = False
# being Strict this violates OCP but
# we only need to change this enum for extension
class AvailableDrink(Enum):
COFFEE = auto()
TEA = auto()
def __init__(self):
if not self.initialized:
self.initialized = True
for d in self.AvailableDrink:
name = d.name[0] + d.name[1:].lower()
factory_name = name + 'Factory'
factory_instance = eval(factory_name)()
self.factories[name.lower()] = factory_instance
def make_drink(self):
print('Available drinks:')
for f in self.factories:
print(f)
name = str(input(f'Please pick drink name: ')).lower()
amount = int(input(f'Specify amount: '))
return self.factories[name].prepare(amount)
if __name__ == '__main__':
hdm = HotDrinkMachine()
drink = hdm.make_drink()
drink.consume()
Comes from the idea that we don't start things from scratch we instead copy and modify or in other words we re-iterate existing designs, we look at what other people have already done and try to improve upon the existing constructs
It is a pattern that is used when there is an already existent design (can be partially or fully constructed) that you want to modify, extend, customize or complete
A Prototype is a partially or fully initialized object that you copy (clone) and make use of
Prototype pattern requires to do deep copy of objects copy.deepcopy()
You create a partially constructed object, store it somewhere, then deep copy that object, customize the copy and return this resulting instance
import copy
class Address:
def __init__(self, street_address, suite, city):
self.suite = suite
self.city = city
self.street_address = street_address
def __str__(self):
return f'{self.street_address}, Suite #{self.suite}, {self.city}'
class Employee:
def __init__(self, name, address):
self.address = address
self.name = name
def __str__(self):
return f'{self.name} works at {self.address}'
class EmployeeFactory:
main_office_employee = Employee("", Address("123 East Dr", 0, "London"))
aux_office_employee = Employee("", Address("123B East Dr", 0, "London"))
@staticmethod
def __new_employee(proto, name, suite):
result = copy.deepcopy(proto)
result.name = name
result.address.suite = suite
return result
@staticmethod
def new_main_office_employee(name, suite):
return EmployeeFactory.__new_employee(
EmployeeFactory.main_office_employee,
name, suite
)
@staticmethod
def new_aux_office_employee(name, suite):
return EmployeeFactory.__new_employee(
EmployeeFactory.aux_office_employee,
name, suite
)
pepe = EmployeeFactory.new_main_office_employee("Pepe", 700)
jane = EmployeeFactory.new_aux_office_employee("Jane", 200)
print(pepe)
print(jane)
Used for components that only makes sense to have ONLY one in the system and it makes sense to initialize them ONLY once. e.g. DB repository, the initializer call is expensive
This pattern requires that the implementation must:
A Singleton is a component/class which is instantiated only once and everybody who tries to use/access this object basically gets to work with that one instance.
import random
def singleton(class_):
instances = {}
def get_instance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return get_instance
@singleton
class Database:
def __init__(self):
print('Loading database. Generated an id of', random.randint(1,101))
if __name__ == '__main__':
d1 = Database()
d2 = Database()
print(d1 == d2)
import random
class Singleton(type):
""" Metaclass that creates a Singleton base type when called. """
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls)\
.__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=Singleton):
def __init__(self, my_id):
print('Loading database {}. Generated rand of {}'.format(my_id, random.randint(1,101)))
if __name__ == '__main__':
d1 = Database(1)
d2 = Database(2)
print(d1 == d2)
import unittest
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=Singleton):
def __init__(self):
self.population = {}
f = open('capitals.txt', 'r')
lines = f.readlines()
for i in range(0, len(lines), 2):
self.population[lines[i].strip()] = int(lines[i + 1].strip())
f.close()
class SingletonRecordFinder:
def total_population(self, cities):
result = 0
for c in cities:
result += Database().population[c]
return result
class ConfigurableRecordFinder:
def __init__(self, db):
self.db = db
def total_population(self, cities):
result = 0
for c in cities:
result += self.db.population[c]
return result
class DummyDatabase:
population = {
'alpha': 1,
'beta': 2,
'gamma': 3
}
def get_population(self, name):
return self.population[name]
class SingletonTests(unittest.TestCase):
""" These test on a live database :( """
"""
# require capitals.txt to work
def test_is_singleton(self):
db = Database()
db2 = Database()
self.assertEqual(db, db2)
def test_singleton_total_population(self):
rf = SingletonRecordFinder()
names = ['Seoul', 'Mexico City']
tp = rf.total_population(names)
self.assertEqual(tp, 17500000 + 17400000) # what if these change?
"""
def test_dependent_total_population(self):
ddb = DummyDatabase()
crf = ConfigurableRecordFinder(ddb)
self.assertEqual(crf.total_population(['alpha', 'beta']), 3)
if __name__ == '__main__':
unittest.main()
The Adapter pattern tries to adapt the interface that you are given to the interface that you actually need/want.
The adapter works as a component that give us the interface we require from the interface we have
Adapter is a SW construct that adapts existent interface X to conform to the required interface Y
The adapter is an in-between, but separate, component/class to interface two components (classes)
To implement an adapter first identify the API/class you have and the API/class/method/function you need, then create a component, the adapter, that has a reference to the API/class you have, the adaptee, it wraps the API/class you have and uses it, finally in your system or application you use the adapter which internally will end up using the adaptee
class Point:
def __init__(self, x, y):
self.y = y
self.x = x
def __str__(self):
return f"(x={self.x},y={self.y})"
def draw_point(p):
print('*', end='')
""" you are given this API and you cannot change it """
class Line:
def __init__(self, start, end):
self.end = end
self.start = start
class Rectangle(list):
""" Represented as a list of lines. """
def __init__(self, x, y, width, height):
super().__init__()
self.append(Line(Point(x, y), Point(x + width, y)))
self.append(Line(Point(x + width, y), Point(x + width, y + height)))
self.append(Line(Point(x, y), Point(x, y + height)))
self.append(Line(Point(x, y + height), Point(x + width, y + height)))
class LineToPointAdapter(list):
count = 0
def __init__(self, line):
left = min(line.start.x, line.end.x)
right = max(line.start.x, line.end.x)
top = max(line.start.y, line.end.y)
bottom = min(line.start.y, line.end.y)
LineToPointAdapter.count += 1
out = f'{self.count}: Generating points for line '
out += f'[{line.start.x},{line.start.y}]→'
out += f'[{line.end.x},{line.end.y}] '
out += f'left: {left} ; right: {right} '
out += f'top: {top} ; bottom: {bottom} '
out += '| points for line: '
if right == left:
for y in range(bottom, top+1):
p = Point(left, y)
out += str(p) + " "
self.append(p)
elif top == bottom :
for x in range(left, right+1):
p = Point(x, top)
out += str(p) + " "
self.append(p)
print(out)
def draw(rcs):
print("\n\n--- Drawing some stuff ---\n")
for rc in rcs:
for line in rc:
# No cache = We create repeated objects
adapter = LineToPointAdapter(line)
for p in adapter:
draw_point(p)
print("")
if __name__ == '__main__':
rs = [Rectangle(1, 1, 10, 10), Rectangle(3, 3, 6, 6)]
draw(rs)
draw(rs)
class Point:
def __init__(self, x, y):
self.y = y
self.x = x
def __str__(self):
return f"(x={self.x},y={self.y})"
def draw_point(p):
print('.', end='')
class Line:
def __init__(self, start, end):
self.end = end
self.start = start
class Rectangle(list):
""" Represented as a list of lines. """
def __init__(self, x, y, width, height):
super().__init__()
self.append(Line(Point(x, y), Point(x + width, y)))
self.append(Line(Point(x + width, y), Point(x + width, y + height)))
self.append(Line(Point(x, y), Point(x, y + height)))
self.append(Line(Point(x, y + height), Point(x + width, y + height)))
class LineToPointAdapter:
count = 0
cache = {}
def __init__(self, line):
self.h = hash(line)
if self.h in LineToPointAdapter.cache:
return
super().__init__()
points = []
left = min(line.start.x, line.end.x)
right = max(line.start.x, line.end.x)
top = max(line.start.y, line.end.y)
bottom = min(line.start.y, line.end.y)
LineToPointAdapter.count += 1
out = f'{LineToPointAdapter.count}: Generating points for line '
out += f'[{line.start.x},{line.start.y}]→'
out += f'[{line.end.x},{line.end.y}] '
out += f'left: {left} ; right: {right} '
out += f'top: {top} ; bottom: {bottom} '
out += '| points for line: '
if right == left:
for y in range(bottom, top+1):
p = Point(left, y)
out += str(p) + " "
points.append(p)
elif top == bottom :
for x in range(left, right+1):
p = Point(x, top)
out += str(p) + " "
points.append(p)
print(out)
LineToPointAdapter.cache[self.h] = points
def __iter__(self):
return iter(LineToPointAdapter.cache[self.h])
def draw(rcs):
print('Drawing some rectangles...')
for rc in rcs:
for line in rc:
adapter = LineToPointAdapter(line)
for p in adapter:
draw_point(p)
print('')
print('\n')
if __name__ == '__main__':
rs = [
Rectangle(1, 1, 10, 10),
Rectangle(3, 3, 6, 6)
]
draw(rs)
draw(rs)
# can define your own hashes or use the defaults
#print(hash(Line(Point(1, 1), Point(10, 10))))
Used to connect components through abstractions
This pattern is used to prevent Entity Explosion or Cartesian Product Complexity Explosion which happens when you only rely on inheritance and if you have 2 variables that can be combined you end up with 4 classes e.g. a preemptive and non-preemptive scheduler that can be implemented differently in Linux and Windows so you end up with 4 classes
The Bridge patterns used inheritance plus aggregation to decouple the interface from the actual implementation
The bridge helps connect to class hierarchies by storing a reference to the base of one of the hierarchies in the other one and use it in the derivative classes, when we create these derivatives we will need to pass an derivative object that will be stored through the reference we mentioned before
For scaling we don't need to implement new classes for each combination however we do need to implement a new class and extend the methods/functionality on the hierarchy of classes that is stored as a reference
# Circles and squares
# Each can be rendered in vector or raster form
class Renderer():
def render_circle(self, radius):
pass
def render_square(self, side):
pass
# For new class we need to extend this
class VectorRenderer(Renderer):
def render_circle(self, radius):
print(f'Drawing a circle of radius {radius}')
def render_square(self, side):
print(f'Drawing a square of side {side}')
# Also extend here
class RasterRenderer(Renderer):
def render_circle(self, radius):
print(f'Drawing pixels for circle of radius {radius}')
def render_square(self, side):
print(f'Drawing pixels for square of side {side}')
# And extend here
class Shape:
def __init__(self, renderer):
self.renderer = renderer
def draw(self): pass
def resize(self, factor): pass
class Circle(Shape):
def __init__(self, renderer, radius):
super().__init__(renderer)
self.radius = radius
def draw(self):
self.renderer.render_circle(self.radius)
def resize(self, factor):
self.radius *= factor
class Square(Shape):
def __init__(self, renderer, side):
super().__init__(renderer)
self.side = side
def draw(self):
self.renderer.render_square(self.side)
def resize(self, factor):
self.side *= factor
# We will need to create a new class
if __name__ == '__main__':
raster = RasterRenderer()
vector = VectorRenderer()
circle = Circle(vector, 5)
circle.draw()
circle.resize(2)
The goal of the composite design patterns is to treat individual component and groups of objects in a uniform way so to provide and identical interface for both aggregates and individual components
The composite looks to treat individual components and aggregate objects in the same way, for example if you have a drawing app that allows the user to move a Shape most likely the user will want to move a group of Shape objects so at the implementation level we will want to use the same interface/method/mechanism to do this
The composite design pattern is used to treat both single (scalar) and composite objects (groups of objects) in exactly the same way
It is a mechanism for treating individual objects and groups of objects in a uniform manner
Used for cases where composed and singular objects actually need to provide similar or identical behavior. In cases where you want to be able to treat a group of objects using exactly the same interface as a single object we use the composite pattern
class GraphicObject:
def __init__(self, color=None):
self.color = color
self.children = []
self._name = 'Group'
@property
def name(self):
return self._name
def _print(self, items, depth):
items.append('*' * depth)
if self.color:
items.append(self.color)
items.append(f'{self.name}\n')
for child in self.children:
child._print(items, depth + 1)
def __str__(self):
items = []
self._print(items, 0)
return ''.join(items)
class Circle(GraphicObject):
@property
def name(self):
return 'Circle'
class Square(GraphicObject):
@property
def name(self):
return 'Square'
if __name__ == '__main__':
drawing = GraphicObject()
drawing._name = 'My Drawing'
drawing.children.append(Square('Red'))
drawing.children.append(Circle('Yellow'))
group = GraphicObject() # no name
group.children.append(Circle('Blue'))
group.children.append(Square('Blue'))
drawing.children.append(group)
print(drawing)
from abc import ABC
from collections.abc import Iterable
class Connectable(Iterable, ABC):
def connect_to(self, other):
if self == other:
return
for s in self:
for o in other:
s.outputs.append(o)
o.inputs.append(s)
class Neuron(Connectable):
def __init__(self, name):
self.name = name
self.inputs = []
self.outputs = []
def __iter__(self):
yield self # This turns a scalar object into something that's iterable
def __str__(self):
return 'name: {}. {} inputs: ({}) ; {} outputs: ({}) '.format(self.name, len(self.inputs), [n.name for n in self.inputs], len(self.outputs), [n.name for n in self.outputs])
class NeuronLayer(list, Connectable):
def __init__(self, name, count):
super().__init__()
self.name = name
for x in range(0, count):
self.append(Neuron(f'{name}-{x}'))
def __str__(self):
return '{} with {} neurons:\n {}'.format(self.name, len(self), "\n ".join([str(n) for n in self]))
if __name__ == '__main__':
neuron1 = Neuron('n1')
neuron2 = Neuron('n2')
layer1 = NeuronLayer('L1', 3)
layer2 = NeuronLayer('L2', 4)
neuron1.connect_to(neuron2)
neuron1.connect_to(layer1)
layer1.connect_to(neuron2)
layer1.connect_to(layer2)
print(neuron1)
print(neuron2)
print(layer1)
print(layer2)
The Decorator design pattern exists so that you can add additional behaviors to particular classes or functions without either modifying the classes themselves or indeed inheriting from them.
It is used when you want to augment a class with extra features but don't want to rewrite or override existing code (as you would do through inheritance)
To interact with existent structures a decorator references the decorated object(s) or function(s), so you have a reference and offer additional operations on top of that
A decorators facilitates the addition of behavior to individual objects without inheriting from them
A decorator keeps a reference to the decorated object(s), the objects it actually decorates, then it adds a certain utility methods and attributes to increase the objects functionality
The implementation of decorator inside python is what is known as a functional decorator which is very specific implementation of the general purpose decorator
The functional decorator are used for performing something around a function. So you take a function or indeed a method and what you do is you use the decorators to actually perform some initialization code, perform some termination code and even store some values if you want.
Python provides special syntax to apply a functional decorator using the @ symbol
import time
def time_it(func):
def wrapper():
start = time.time()
result = func()
end = time.time()
print(f'{func.__name__} took {int((end-start)*1000)}ms')
return wrapper
def some_op_without_at():
print('Starting op')
time.sleep(1)
print('We are done')
return 123
@time_it
def some_op_with_at():
print('Starting op')
time.sleep(1)
print('We are done')
return 123
if __name__ == '__main__':
# Without using @
time_it(some_op_without_at)()
print("")
# Using @ special python notation
some_op_with_at()
You built a class that augments the functionality of the existing class and keep a reference to the class you are augmenting (NOT inherit from the class you augment)
Classical decorators is a class which takes the decorated object as an argument, usually also takes some additional values and it provides the extra functionality
from abc import ABC
class Shape(ABC):
def __str__(self):
return ''
class Circle(Shape):
def __init__(self, radius=0.0):
self.radius = radius
def resize(self, factor):
self.radius *= factor
def __str__(self):
return f'A circle of radius {self.radius}'
class Square(Shape):
def __init__(self, side):
self.side = side
def __str__(self):
return f'A square with side {self.side}'
class ColoredShape(Shape):
def __init__(self, shape, color):
if isinstance(shape, ColoredShape):
raise Exception('Cannot apply ColoredDecorator twice')
self.shape = shape
self.color = color
def __str__(self):
return f'{self.shape} has the color {self.color}'
class TransparentShape(Shape):
def __init__(self, shape, transparency):
self.shape = shape
self.transparency = transparency
def __str__(self):
return f'{self.shape} has {self.transparency * 100.0}% transparency'
if __name__ == '__main__':
circle = Circle(2)
print(circle)
red_circle = ColoredShape(circle, "red")
print(red_circle)
# ColoredShape doesn't have resize()
# red_circle.resize(3) # We cannot do this
red_half_transparent_square = TransparentShape(red_circle, 0.5)
print(red_half_transparent_square)
# prevent double application at ColoredShape constructor
mixed = ColoredShape(ColoredShape(Circle(3), 'red'), 'blue')
print(mixed)
The idea of a dynamic decorator is to wrap an object (reference to an object), add new attributes and methods but also forward calls to the underlying object or stored reference to use the attributes and methods of this object when we want to access methods and attributes we don't define in the decorator
At the implementation level it means to have a reference to an object and override the methods __iter__, __next__, __getattr__, __setattr__, __delattr__, that way we can use all the methods and attributes from the reference when using the decorator
class FileWithLogging:
def __init__(self, file):
self.file = file
def writelines(self, strings):
self.file.writelines(strings)
print(f'wrote {len(strings)} lines')
def __iter__(self):
return self.file.__iter__()
def __next__(self):
return self.file.__next__()
def __getattr__(self, item):
return getattr(self.__dict__['file'], item)
def __setattr__(self, key, value):
if key == 'file':
self.__dict__[key] = value
else:
setattr(self.__dict__['file'], key)
def __delattr__(self, item):
delattr(self.__dict__['file'], item)
if __name__ == '__main__':
file = FileWithLogging(open('hello.txt', 'w'))
file.writelines(['hello', 'world'])
file.write('testing')
file.close()
The main idea is to expose several components through a single interface to balance complexity and presentation/usability
The Façade tries to provide a simple and easy to use API over a large and sophisticated body of code that could involve several classes and functions
The face tries to expose a single function or object that you call to have a lots of things happen underneath
A Facade is build to provide a simplified API over a set of classes/components, optionally expose internal through the facade
class Buffer:
def __init__(self, width=30, height=20):
self.width = width
self.height = height
self.buffer = [' '] * (width*height)
def __getitem__(self, item):
return self.buffer.__getitem__(item)
def write(self, text):
self.buffer += text
class Viewport:
def __init__(self, buffer=Buffer()):
self.buffer = buffer
self.offset = 0
def get_char_at(self, index):
return self.buffer[self.offset+index]
def append(self, text):
self.buffer += text
# this is the facade
class Console:
def __init__(self):
b = Buffer()
self.current_viewport = Viewport(b)
self.buffers = [b]
self.viewports = [self.current_viewport]
# high-level
def write(self, text):
self.current_viewport.buffer.write(text)
# low-level
def get_char_at(self, index):
return self.current_viewport.get_char_at(index)
if __name__ == '__main__':
c = Console()
c.write('hello')
ch = c.get_char_at(600)
print(ch)
The Flyweight is a space optimization technique which means it is a pattern that tries to save memory
The goal of the Flyweight design patterns is to avoid redundancy when storing data, in other words avoid duplication of data. The patterns instead will look to store only references to a data structure shared among many components instead of replicating and storing the data in different components. e.g. just store database ID where the data is stored
It is a space optimization technique that lets us use less memory by storing externally the data associated with similar components/objects
import random
import string
import sys
class User: # Will duplicate storage of strings
def __init__(self, name):
self.name = name
class UserFlyweight: # Won't duplicate storage of strings
strings = []
def __init__(self, full_name):
def get_or_add(s):
if s in self.strings:
return self.strings.index(s)
else:
self.strings.append(s)
return len(self.strings)-1
self.names = [get_or_add(x) for x in full_name.split(' ')]
print("Created user {} with indexes {} user: {}".format(id(self), ":".join([str(i) for i in self.names]), str(self)))
def __str__(self):
return ' '.join([self.strings[x] for x in self.names])
def random_string():
chars = string.ascii_lowercase
return ''.join([random.choice(chars) for x in range(8)])
if __name__ == '__main__':
USERS_NUM = 2
users = []
u2 = UserFlyweight('Jim Jones')
u3 = UserFlyweight('Frank Jones')
first_names = [random_string() for x in range(USERS_NUM//2)]
last_names = [random_string() for x in range(USERS_NUM//2)]
for first in first_names:
for last in last_names:
usr = UserFlyweight(f'{first} {last}')
users.append(usr)
print("Strings:", UserFlyweight.strings)
class FormattedText:
def __init__(self, plain_text):
self.plain_text = plain_text
self.caps = [False] * len(plain_text)
def capitalize(self, start, end):
for i in range(start, end):
self.caps[i] = True
def __str__(self):
result = []
for i in range(len(self.plain_text)):
c = self.plain_text[i]
result.append(c.upper() if self.caps[i] else c)
return ''.join(result)
class BetterFormattedText:
class TextRange: # This is the Flyweight
def __init__(self, start, end, capitalize=False, bold=False, italic=False):
self.end = end
self.bold = bold
self.capitalize = capitalize
self.italic = italic
self.start = start
def covers(self, position):
return self.start <= position <= self.end
def __init__(self, plain_text):
self.plain_text = plain_text
self.formatting = []
def get_range(self, start, end):
range = self.TextRange(start, end)
self.formatting.append(range)
return range # return the Flyweight
def __str__(self):
result = []
for i in range(len(self.plain_text)):
c = self.plain_text[i]
for r in self.formatting:
if r.covers(i) and r.capitalize:
c = c.upper()
result.append(c)
return ''.join(result)
if __name__ == '__main__':
ft = FormattedText('This is a brave new world')
ft.capitalize(10, 15)
print(ft)
bft = BetterFormattedText('This is a brave new world')
bft.get_range(16, 19).capitalize = True
bft.get_range(0, 3).capitalize = True
print(bft)
A Proxy is a class that functions as an interface to a particular resource. That resource could be other component that can be remote, expensive to construct, require some other functionality, etc
The intention of the proxy is to provide the same interface as the component it wraps but with an entirely different behavior or with added functionality, so to create proxy first replicate the existing interface of an object and then add relevant functionality
class Car:
def __init__(self, driver):
self.driver = driver
def drive(self):
print(f'Car being driven by {self.driver.name}')
class CarProxy:
def __init__(self, driver):
self.driver = driver
self.car = Car(driver)
def drive(self):
if self.driver.age >= 18:
self.car.drive()
else:
print('Driver too young')
class Driver:
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
car = CarProxy(Driver('John', 12))
car.drive()
It is a proxy that appears to be the underlying object. So it appears to be a fully initialized object but in actual fact it's not, it's just masquerading the underlying functionality
By virtual it means that for all intents and purposes it appears like the object it's supposed to represent but behind the scenes it can offer additional functionality and behave differently
class Bitmap:
def __init__(self, filename):
self.filename = filename
print(f'Loading image from {filename}')
def draw(self):
print(f'Drawing image {self.filename}')
class LazyBitmap: # This is the Virtual Proxy
def __init__(self, filename):
self.filename = filename
self.bitmap = None
def draw(self):
if not self.bitmap:
self.bitmap = Bitmap(self.filename)
self.bitmap.draw()
def draw_image(image):
print('About to draw image')
image.draw()
print('Done drawing image')
if __name__ == '__main__':
bmp = LazyBitmap('facepalm.jpg') # Bitmap
draw_image(bmp)
The proxy provides an identical interface to the object that its proxy things to (the objects is uses) whereas the decorator provides an enhanced interface.
A decorator may replicate some of its API s or indeed all of its API but it is designed to add additional operations or functionality
A decorator usually has a reference to the object that is decorating, taking that objects as a constructor argument the proxy usually doesn't take this reference as argument but constructs the object if is needed
Chain of Responsibility refers to a chain of components who all get a chance to process a command or query, optionally with as default processing implementation and ability yo terminate the processing chain
It is a design pattern used to describe what component should be responsible of certain processing when we have multiple components that have a relation to each other, usually in a hierarchy, for example a button inside a group of buttons inside a window
This can be implemented as a linked list of references typically references to methods/functions
Command-Query Separation (CQS) refers to the idea that whenever we operate on objects we separate invocations into query and command so we have separate means to send commands and queries
By using the CQS and the Chain of responsibility you can have listeners to the commands and queries to do something or even override the behaviors of the command or query
class Creature:
def __init__(self, name, attack, defense):
self.defense = defense
self.attack = attack
self.name = name
def __str__(self):
return f'{self.name} ({self.attack}/{self.defense})'
class CreatureModifier:
def __init__(self, creature):
self.creature = creature
self.next_modifier = None
def add_modifier(self, modifier):
if self.next_modifier:
self.next_modifier.add_modifier(modifier)
else:
self.next_modifier = modifier
def remove_modifier(self, modifier):
if self.next_modifier and modifier is self.next_modifier:
self.next_modifier = modifier.next_modifier
del(modifier)
elif self.next_modifier:
self.next_modifier.remove_modifier(modifier)
def handle(self):
if self.next_modifier:
self.next_modifier.handle()
class NoBonusesModifier(CreatureModifier):
def handle(self):
print('No bonuses for you!')
class DoubleAttackModifier(CreatureModifier):
def handle(self):
print(f"Doubling {self.creature.name}'s attack with modifier {id(self)}")
self.creature.attack *= 2
super().handle()
class DoubleDefenseModifier(CreatureModifier):
def handle(self):
print(f"Doubling {self.creature.name}'s defense with modifier {id(self)}")
self.creature.defense *= 2
super().handle()
if __name__ == '__main__':
goblin = Creature('Goblin', 1, 1)
print(goblin)
root = CreatureModifier(goblin)
# If you uncomment this will stop the chain of
# responsability by not calling base class handle()
#root.add_modifier(NoBonusesModifier(goblin))
root.add_modifier(DoubleAttackModifier(goblin))
root.add_modifier(DoubleAttackModifier(goblin))
to_rem = DoubleAttackModifier(goblin)
root.add_modifier(to_rem)
root.remove_modifier(to_rem)
root.add_modifier(DoubleDefenseModifier(goblin))
root.handle() # apply modifiers
print(goblin)
# 1) event broker
# 2) command-query separation (cqs)
# 3) observer
from abc import ABC
from enum import Enum
class Event(list):
def __call__(self, *args, **kwargs):
for item in self:
item(*args, **kwargs)
class WhatToQuery(Enum):
ATTACK = 1
DEFENSE = 2
class Query:
def __init__(self, creature_name, what_to_query, default_value):
self.value = default_value # bidirectional
self.what_to_query = what_to_query
self.creature_name = creature_name
class Game: # This is the event broker
def __init__(self):
self.queries = Event()
def perform_query(self, sender, query):
self.queries(sender, query)
class Creature:
def __init__(self, game, name, attack, defense):
self.initial_defense = defense
self.initial_attack = attack
self.name = name
self.game = game
@property
def attack(self):
q = Query(self.name, WhatToQuery.ATTACK, self.initial_attack)
self.game.perform_query(self, q)
return q.value
@property
def defense(self):
q = Query(self.name, WhatToQuery.DEFENSE, self.initial_attack)
self.game.perform_query(self, q)
return q.value
def __str__(self):
return f'{self.name} ({self.attack}/{self.defense})'
class CreatureModifier(ABC):
def __init__(self, game, creature):
self.creature = creature
self.game = game
self.game.queries.append(self.handle)
def handle(self, sender, query):
pass
def __enter__(self): # Executed when you enter a `with` block, what you return here is what is assigned to identifier after `as`
return self
def __exit__(self, exc_type, exc_val, exc_tb): # Executed when you exit a `with` block
self.game.queries.remove(self.handle)
class DoubleAttackModifier(CreatureModifier):
def handle(self, sender, query):
if (sender.name == self.creature.name and
query.what_to_query == WhatToQuery.ATTACK):
query.value *= 2
class IncreaseDefenseModifier(CreatureModifier):
def handle(self, sender, query):
if (sender.name == self.creature.name and
query.what_to_query == WhatToQuery.DEFENSE):
query.value += 3
if __name__ == '__main__':
game = Game()
goblin = Creature(game, 'Dark Goblin', 2, 2)
print(goblin)
dam = DoubleAttackModifier(game, goblin)
idm = IncreaseDefenseModifier(game, goblin)
print(goblin)
goblin2 = Creature(game, 'Light Goblin', 2, 2)
print(goblin2)
with DoubleAttackModifier(game, goblin2):
print(goblin2)
with IncreaseDefenseModifier(game, goblin2):
print(goblin2)
print(goblin2)
print(goblin2)
Used to define an object that represents an operation and record this operation actually took place
A Command is an object that represents an instruction/operation to perform a particular action. This object usually contains all the information necessary for the action to be taken
To implement this you encapsulate all the details of an operation in an object, then you define the instruction for applying the command invoke & undo which will call the methods to perform the actual operation and always use the instruction to perform operations
from abc import ABC
from enum import Enum
class BankAccount:
OVERDRAFT_LIMIT = -500
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f'Deposited {amount}, balance = {self.balance}')
def withdraw(self, amount):
if self.balance - amount >= BankAccount.OVERDRAFT_LIMIT:
self.balance -= amount
print(f'Withdrew {amount}, balance = {self.balance}')
return True
return False
def __str__(self):
return f'Balance = {self.balance}'
class Command(ABC):
def invoke(self):
pass
def undo(self):
pass
class BankAccountCommand(Command):
def __init__(self, account, action, amount):
self.amount = amount
self.action = action
self.account = account
self.success = None
class Action(Enum):
DEPOSIT = 0
WITHDRAW = 1
def invoke(self):
if self.action == self.Action.DEPOSIT:
self.account.deposit(self.amount)
self.success = True
elif self.action == self.Action.WITHDRAW:
self.success = self.account.withdraw(self.amount)
def undo(self):
if not self.success:
return
# strictly speaking this is not correct
# (you don't undo a deposit by withdrawing)
# but it works for this demo, so...
if self.action == self.Action.DEPOSIT:
self.account.withdraw(self.amount)
elif self.action == self.Action.WITHDRAW:
self.account.deposit(self.amount)
if __name__ == '__main__':
ba = BankAccount()
cmd = BankAccountCommand(ba, BankAccountCommand.Action.DEPOSIT, 100)
cmd.invoke()
print('After $100 deposit:', ba)
cmd.undo()
print('$100 deposit undone:', ba)
illegal_cmd = BankAccountCommand(ba, BankAccountCommand.Action.WITHDRAW, 1000)
illegal_cmd.invoke()
print('After impossible withdrawal:', ba)
illegal_cmd.undo()
print('After undo:', ba)
from abc import ABC
from enum import Enum
class BankAccount:
OVERDRAFT_LIMIT = 0
def __init__(self, balance=0, name_id=""):
self.balance = balance
self.name_id = name_id
def deposit(self, amount):
self.balance += amount
print(f'Deposited {amount} from account with id {self.name_id}, balance = {self.balance}')
def withdraw(self, amount):
if self.balance - amount >= BankAccount.OVERDRAFT_LIMIT:
self.balance -= amount
print(f'Withdrew {amount} from account with id {self.name_id}, balance = {self.balance}')
return True
return False
def __str__(self):
return f'Balance = {self.balance}'
class Command(ABC):
def __init__(self):
self.success = False
def invoke(self):
pass
def undo(self):
pass
class BankAccountCommand(Command):
def __init__(self, account, action, amount):
super().__init__()
self.amount = amount
self.action = action
self.account = account
class Action(Enum):
DEPOSIT = 0
WITHDRAW = 1
def invoke(self):
if self.action == self.Action.DEPOSIT:
self.account.deposit(self.amount)
self.success = True
elif self.action == self.Action.WITHDRAW:
self.success = self.account.withdraw(self.amount)
def undo(self):
if not self.success:
return
# strictly speaking this is not correct
# (you don't undo a deposit by withdrawing)
# but it works for this demo, so...
if self.action == self.Action.DEPOSIT:
self.account.withdraw(self.amount)
elif self.action == self.Action.WITHDRAW:
self.account.deposit(self.amount)
# try using this before using MoneyTransferCommand!
class CompositeBankAccountCommand(Command, list):
def __init__(self, items=[]):
super().__init__()
for i in items:
self.append(i)
def invoke(self):
for x in self:
x.invoke()
def undo(self):
for x in reversed(self):
x.undo()
class MoneyTransferCommand(CompositeBankAccountCommand):
def __init__(self, from_acct, to_acct, amount):
super().__init__([
BankAccountCommand(from_acct,
BankAccountCommand.Action.WITHDRAW,
amount),
BankAccountCommand(to_acct,
BankAccountCommand.Action.DEPOSIT,
amount)])
def invoke(self):
ok = True
for cmd in self:
if ok:
cmd.invoke()
ok = cmd.success
else:
cmd.success = False
self.success = ok
def undo(self):
ok = True
for cmd in reversed(self):
if ok:
cmd.undo()
ok = cmd.success
else:
cmd.success = False
self.success = ok
def test_composite_deposit():
ba = BankAccount()
deposit1 = BankAccountCommand(ba, BankAccountCommand.Action.DEPOSIT, 1000)
deposit2 = BankAccountCommand(ba, BankAccountCommand.Action.DEPOSIT, 1000)
composite = CompositeBankAccountCommand([deposit1, deposit2])
composite.invoke()
print(ba)
composite.undo()
print(ba)
def test_transfer_fail():
ba1 = BankAccount(100)
ba2 = BankAccount()
# composite isn't so good because of failure
amount = 1000 # try 1000: no transactions should happen
wc = BankAccountCommand(ba1, BankAccountCommand.Action.WITHDRAW, amount)
dc = BankAccountCommand(ba2, BankAccountCommand.Action.DEPOSIT, amount)
transfer = CompositeBankAccountCommand([wc, dc])
transfer.invoke()
print('ba1:', ba1, 'ba2:', ba2) # end up in incorrect state
transfer.undo()
print('ba1:', ba1, 'ba2:', ba2)
def test_better_tranfer():
ba1 = BankAccount(100, "BA1")
ba2 = BankAccount(0, "BA2")
amount = 10
transfer = MoneyTransferCommand(ba1, ba2, amount)
transfer.invoke()
print('ba1:', ba1, 'ba2:', ba2)
print(transfer.success)
transfer.undo()
print('ba1:', ba1, 'ba2:', ba2)
print(transfer.success)
if __name__ == '__main__':
test_better_tranfer()
Used to process/interpret textual input (e.g. process HTML, XML or simple math expression 3+4/5). It has the goal of turning strings or plain text into data structures
The Interpreter is a component that processes structured text data, it does this in two stages: lexing and parsing
from enum import Enum
class Token:
class Type(Enum):
INTEGER = 0
PLUS = 1
MINUS = 2
LPAREN = 3
RPAREN = 4
def __init__(self, type, text):
self.type = type
self.text = text
def __str__(self):
return f'`{self.text}`'
# ↓↓↓ lexing ↓↓↓
def lex(input):
result = []
i = 0
while i < len(input):
if input[i] == '+':
result.append(Token(Token.Type.PLUS, '+'))
elif input[i] == '-':
result.append(Token(Token.Type.MINUS, '-'))
elif input[i] == '(':
result.append(Token(Token.Type.LPAREN, '('))
elif input[i] == ')':
result.append(Token(Token.Type.RPAREN, ')'))
else: # must be a number
digits = [input[i]]
for j in range(i + 1, len(input)):
if input[j].isdigit():
digits.append(input[j])
i += 1
else:
result.append(Token(Token.Type.INTEGER,
''.join(digits)))
break
i += 1
return result
# ↓↓↓ parsing ↓↓↓
class Integer:
def __init__(self, value):
self.value = value
def __str__(self):
return f"Integer {self.value}"
class BinaryOperation:
class Type(Enum):
ADDITION = 0
SUBTRACTION = 1
def __init__(self):
self.type = None
self.left = None
self.right = None
@property
def value(self):
left = self.left.value
right = self.right.value
print(f"Calling value of Binary Op with ID {id(self)} left: {left} ; right: {right}")
if self.type == self.Type.ADDITION:
return left + right
elif self.type == self.Type.SUBTRACTION:
return left - right
def parse(tokens):
result = BinaryOperation()
have_lhs = False
i = 0
while i < len(tokens):
token = tokens[i]
if token.type == Token.Type.INTEGER:
integer = Integer(int(token.text))
if not have_lhs:
result.left = integer
have_lhs = True
else:
result.right = integer
elif token.type == Token.Type.PLUS:
result.type = BinaryOperation.Type.ADDITION
elif token.type == Token.Type.MINUS:
result.type = BinaryOperation.Type.SUBTRACTION
elif token.type == Token.Type.LPAREN: # note: no if for RPAREN
j = i
while j < len(tokens):
if tokens[j].type == Token.Type.RPAREN:
break
j += 1
# preprocess subexpression
subexpression = tokens[i + 1:j]
element = parse(subexpression)
if not have_lhs:
result.left = element
have_lhs = True
else:
result.right = element
i = j # advance
i += 1
return result
def my_eval(input):
tokens = lex(input)
print(' '.join(map(str, tokens)))
parsed = parse(tokens)
print(f'{input} = {parsed.value}')
if __name__ == '__main__':
my_eval('(13+4)-(12+1)')
my_eval('1+(3-4)')
# This won't work
print("Following evaluation will be wrong")
my_eval('1+2+(3-4)')
Iteration is a core functionality of various data structures
An Iterator is a class that facilitates a mechanism to traverse a data structure, it specified how to traverse an object. This is done through two other mechanism:
In Python if you want an object to be iterable it needs to have an __iter__ method, which needs to expose and iterator while the iterator need to have a __next__ method which returns each of the iterated elements, every time is called you return the current element until you have nothing to return (reach the end) in that case you raise a stop iteration (raise StopIteration)
__iter__ and __next__A Stateful iterator changes the current element it stores as reference so every time you call it changes this reference. These type of iterators cannot be recursive
class Node:
def __init__(self, value, left=None, right=None):
self.right = right
self.left = left
self.value = value
self.parent = None
if left:
self.left.parent = self
if right:
self.right.parent = self
def __iter__(self):
return InOrderIterator(self)
class InOrderIterator:
def __init__(self, root):
self.root = self.current = root
self.yielded_start = False
while self.current.left:
self.current = self.current.left
def __next__(self):
if not self.yielded_start:
self.yielded_start = True
return self.current
if self.current.right:
self.current = self.current.right
while self.current.left:
self.current = self.current.left
return self.current
else:
p = self.current.parent
while p and self.current == p.right:
self.current = p
p = p.parent
self.current = p
if self.current:
return self.current
else:
raise StopIteration
def traverse_in_order(root):
def traverse(current):
if current.left:
for left in traverse(current.left):
yield left
yield current
if current.right:
for right in traverse(current.right):
yield right
for node in traverse(root):
yield node
if __name__ == '__main__':
# 1
# / \
# 2 3
# in-order: 213
# preorder: 123
# postorder: 231
root = Node(1, Node(2), Node(3))
it = iter(root)
print([next(it).value for x in range(3)])
for x in root:
print(x.value)
for y in traverse_in_order(root):
print(y.value)
class Creature:
strength_index = 0
intelligence_index = 1
agility_index = 2
def __init__(self, name, strength=10, intelligence=10, agility=10):
self.stats = [strength, intelligence, agility]
self.name = name
@property
def strength(self):
return self.stats[Creature.strength_index]
@strength.setter
def strength(self, value):
self.stats[Creature.strength_index] = value
@property
def intelligence(self):
return self.stats[Creature.intelligence_index]
@intelligence.setter
def intelligence(self, value):
self.stats[Creature.intelligence_index] = value
@property
def agility(self):
return self.stats[Creature.agility_index]
@agility.setter
def agility(self, value):
self.stats[Creature.agility_index] = value
# Operational methods, could also be properties
def sum_stats(self):
return sum(self.stats)
def max_stats(self):
return max(self.stats)
def min_stats(self):
return min(self.stats)
def average_stats(self):
return self.sum_stats()/len(self.stats)
def __str__(self):
c_str = f'''{self.name} with
strength: {self.strength}
intelligence: {self.intelligence}
agility: {self.agility}
stats:
sum: {self.sum_stats()}
max: {self.max_stats()}
min: {self.min_stats()}
avg: {self.average_stats()}
'''
return c_str
if __name__ == '__main__':
c1 = Creature("Ork")
print(c1)
A Mediator is a component that facilitates communication between other componentts without them necessarily being aware of each other or having direct access/reference to each other
It used to facilitate communication between components, since components are always going in and out of systems the mediator functions as central component used for communication
To implement a mediator:
class Person:
def __init__(self, name):
self.name = name
self.chat_log = []
self.room = None
def receive(self, sender, message):
s = f'{sender}: {message}'
print(f'[{self.name}\'s chat session] {s}')
self.chat_log.append(s)
def say(self, message):
self.room.broadcast(source=self.name, message=message)
def private_message(self, who, message):
self.room.message(source=self.name, destination=who, message=message)
class ChatRoom: # This is the mediator
def __init__(self):
self.people = []
def broadcast(self, source, message):
for p in self.people:
if p.name != source:
p.receive(sender=source, message=message)
def join(self, person):
join_msg = f'{person.name} joins the chat'
self.broadcast(source='room', message=join_msg)
person.room = self
self.people.append(person)
def leave(self, person):
if person in self.people:
leave_msg = f'{person.name} leaves the chat'
person.room = None
self.people.remove(person)
self.broadcast(source='room', message=leave_msg)
def message(self, source, destination, message):
for p in self.people:
if p.name == destination:
p.receive(sender=source, message=message)
if __name__ == '__main__':
room = ChatRoom()
john = Person('John')
jane = Person('Jane')
room.join(john)
john.say('hi room') # This won't show since nobody else is in the room at this point
room.join(jane)
john.say('hi room again')
jane.say('oh, hey john')
simon = Person('Simon')
room.join(simon)
simon.say('hi everyone!')
jane.private_message('Simon', 'glad you could join us!')
room.leave(john)
class Event(list):
def __call__(self, *args, **kwargs):
for item in self:
item(*args, **kwargs)
class Game:
def __init__(self):
self.events = Event()
def fire(self, args):
self.events(args)
class GoalScoredInfo:
def __init__(self, who_scored, goals_scored):
self.goals_scored = goals_scored
self.who_scored = who_scored
class Player:
def __init__(self, name, game):
self.name = name
self.game = game
self.goals_scored = 0
def score(self):
self.goals_scored += 1
args = GoalScoredInfo(self.name, self.goals_scored)
self.game.fire(args)
class Coach:
def __init__(self, game):
game.events.append(self.celebrate_goal)
def celebrate_goal(self, args):
if isinstance(args, GoalScoredInfo):
if args.goals_scored == 3:
print(f'Coach says: You are on fire {args.who_scored}!')
elif args.goals_scored > 1:
print(f'Coach says: well done, {args.who_scored}!')
else:
pass # coach not impressed after more than 3 goals or just 1 goal
if __name__ == '__main__':
game = Game()
player = Player('Sam', game)
coach = Coach(game)
player.score() # ignored by coach
player.score() # Coach says: well done, Sam!
player.score() # Coach says: You are on fire Sam!
The Memento is a token or handle representing the system state at a point in time, it lets us roll back to this state and could expose state information. So it just a class that typically has no methods and just stores data
This pattern is used to keep a memento or snapshot of an object's state to return to that state or just preserve that information
To implement it the idea is that every time there is a change on the system you return a token of the current state so that you can restore the system back to the state contained in the token
class Memento:
def __init__(self, balance):
self.balance = balance
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
self.changes = [Memento(self.balance)]
self.current = 0
def deposit(self, amount):
self.balance += amount
m = Memento(self.balance)
self.changes.append(m)
self.current += 1
return m
def restore(self, memento):
if memento:
self.balance = memento.balance
self.changes.append(memento)
self.current = len(self.changes)-1
def undo(self):
if self.current > 0:
self.current -= 1
m = self.changes[self.current]
self.balance = m.balance
return m
return None
def redo(self):
if self.current + 1 < len(self.changes):
self.current += 1
m = self.changes[self.current]
self.balance = m.balance
return m
return None
def __str__(self):
return f'Balance = {self.balance}'
if __name__ == '__main__':
ba = BankAccount(100)
m50 = ba.deposit(50)
ba.deposit(25)
print(ba)
ba.undo()
print(f'Undo 1: {ba}')
ba.undo()
print(f'Undo 2: {ba}')
ba.redo()
print(f'Redo 1: {ba}')
ba.redo()
print(f'Redo 2: {ba}')
ba.restore(m50)
print(f'Restore: {ba}')
Used when we want to be informed when certain thing happen (e.g. when an object's property changes, when an object does something, external event happen, etc)
The Observer design pattern involves an observer (consumer) component which is an object that wishes to be informed about events happening in the system and one or more observable (producer) components which are the entities generating the events
The observer listens to events and gets a notification when they occur, these notifications usually include useful data, to potentially do something with them. It can also unsubscribe from events if it is no longer interested
class Event(list):
def __call__(self, *args, **kwargs):
for item in self:
item(*args, **kwargs)
class Person:
def __init__(self, name, address):
self.name = name
self.address = address
self.falls_ill = Event()
# Event definition
def catch_a_cold(self):
self.falls_ill(self.name, self.address)
# callback
def call_doctor(name, address):
print(f'A doctor has been called to {address}')
if __name__ == '__main__':
person = Person('Sherlock', '221B Baker St')
# Subscribe to Event
person.falls_ill.append(call_doctor)
# callback define in lambda
person.falls_ill.append(lambda name, addr: print(f'{name} is ill'))
# Event happens
person.catch_a_cold()
# And you can remove subscriptions too
person.falls_ill.remove(call_doctor)
person.catch_a_cold()
class Event(list):
def __call__(self, *args, **kwargs):
for item in self:
item(*args, **kwargs)
class PropertyObservable:
def __init__(self):
# this is the event which you need to subscribe
self.property_changed = Event()
class Person(PropertyObservable):
def __init__(self, age=0):
super().__init__()
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if self._age == value:
return
self._age = value
self.property_changed('age', value)
class TrafficAuthority:
def __init__(self, person):
self.person = person
# Subscribe here
person.property_changed.append(self.person_changed)
# This is the callback
def person_changed(self, name, value):
if name == 'age':
if value < 16:
print('Sorry, you still cannot drive')
else:
print('Okay, you can drive now')
# unsubscribe here
self.person.property_changed.remove(self.person_changed)
if __name__ == '__main__':
p = Person()
ta = TrafficAuthority(p)
for age in range(14, 20):
print(f'Setting age to {age}')
p.age = age
The State is a pattern in which the object's behavior is determined by its state. This implies the objects is transitioning from one state to another and something needs to trigger a transition
The state design pattern is designed to model cases where the things you can do with a component depend on the state of that component and the changes in state can be explicit, by doing something or implicit or in response to an event
A State Machine is a manager of states and transitions
In the classical OOP implementation of the state design pattern every state is a class and each class handles transitions from one state to another (it is the one that changes the state) however this classical state implementation is not the recommended implementation
from enum import Enum, auto
TRIGGER_INDEX = 0
NEXT_STATE_INDEX = 1
TRANSITIONS_KEY = "transitions"
FUNC_KEY = "function"
class State(Enum):
OFF_HOOK = auto()
CONNECTING = auto()
CONNECTED = auto()
ON_HOLD = auto()
ON_HOOK = auto()
class Trigger(Enum):
CALL_DIALED = auto()
HUNG_UP = auto()
CALL_CONNECTED = auto()
PLACED_ON_HOLD = auto()
TAKEN_OFF_HOLD = auto()
LEFT_MESSAGE = auto()
TAKE_OFF_HOOK = auto()
def simulate_state_function(*args):
print("State action with arguments: {}".format(args))
if __name__ == '__main__':
rules = {
State.OFF_HOOK: {
"transitions": [(Trigger.CALL_DIALED, State.CONNECTING)],
"function": simulate_state_function
},
State.CONNECTING: {
"transitions": [(Trigger.HUNG_UP, State.ON_HOOK), (Trigger.CALL_CONNECTED, State.CONNECTED)],
"function": simulate_state_function
},
State.CONNECTED: {
"transitions": [(Trigger.LEFT_MESSAGE, State.ON_HOOK), (Trigger.HUNG_UP, State.ON_HOOK), (Trigger.PLACED_ON_HOLD, State.ON_HOLD)],
"function": simulate_state_function
},
State.ON_HOLD: {
"transitions": [(Trigger.TAKEN_OFF_HOLD, State.CONNECTED), (Trigger.HUNG_UP, State.ON_HOOK)],
"function": simulate_state_function
},
State.ON_HOOK: {
"transitions": [(Trigger.TAKE_OFF_HOOK, State.OFF_HOOK)],
"function": simulate_state_function
}
}
state = State.OFF_HOOK
exit_state = State.ON_HOOK # Switch to None to make it an Infinite State Machine
while True:
print(f'The phone is currently {state}')
rules[state][FUNC_KEY](state)
if state == exit_state:
break
for i in range(len(rules[state][TRANSITIONS_KEY])):
t = rules[state][TRANSITIONS_KEY][i][TRIGGER_INDEX]
print(f'{i}: {t}')
idx = int(input('Select a trigger:'))
s = rules[state][TRANSITIONS_KEY][idx][NEXT_STATE_INDEX]
state = s
print('We are done using the phone.')
from enum import Enum, auto
class State(Enum):
LOCKED = auto()
FAILED = auto()
UNLOCKED = auto()
if __name__ == '__main__':
code = '1234'
state = State.LOCKED
entry = ''
while True:
if state == State.LOCKED:
entry += input("Give me a digit of combination: ")
if entry == code:
state = State.UNLOCKED
if not code.startswith(entry):
# the code is wrong
state = State.FAILED
elif state == State.FAILED:
#print('\nFAILED')
entry = ''
state = State.LOCKED
elif state == State.UNLOCKED:
print('\nUNLOCKED')
break
The Strategy design pattern refers to decomposing a system or algorithm into higher level, which refers to a high level or general description of a process that can be reused and lower level parts, which refers to actual implementation details and specific details of the process
The strategy design pattern comes from the idea that system and algorithms can be decomposed into common parts and specific and enables the exact behavior of a system to be selected at run-time
The idea of the strategy design pattern is that at run-time certain implementation details, that will be feed into a component, are specified and then a high level components consume this and use the low level strategy to actually do something
The strategy is usually a separate chunk of code or component that you can subsequently use inside another object that consumes it to perform an specific operation, the strategy must have a defined interface so that we can switch it
To implement the strategy pattern we rely on composition. First you need to define a high-level or general algorithm, then you define the interface you expect each strategy to follow, these are the methods that will be used in the high-level/general algorithm, finally provide a way for the general algorithm to use the strategies usually through composition, holding a reference(s) to one or more strategies
Yo have an object that takes a strategy object as parameter and uses that strategy methods
from abc import ABC
from enum import Enum, auto
class OutputFormat(Enum):
MARKDOWN = auto()
HTML = auto()
class ListStrategy(ABC):
def start(self, buffer): pass
def end(self, buffer): pass
def add_list_item(self, buffer, item): pass
class MarkdownListStrategy(ListStrategy):
def add_list_item(self, buffer, item):
buffer.append(f' * {item}\n')
class HtmlListStrategy(ListStrategy):
def start(self, buffer):
buffer.append('The Template Method design pattern comes from the same idea that algorithms can be decomposed into common parts and specific parts and implements this idea using inheritance
The Template Method allows us to define the skeleton of the algorithm and the concrete implementations details are defined in subclasses
To implement the template method pattern you define the overall algorithm in the base class and use 'abstract members' (methods and properties) which are also defined in the base class, then inheritors override these members providing actual implementations finally the template method gets invoked from the base class and all the overridden members are used to perform the specific operations needed
You have a template method which defines a skeleton algorithm but has a number of blanks which will be defined in the derivatives, you fill the blanks there then call the template method through the derived reference getting all the benefits of the skeleton algorithm and the overridden blanks
from abc import ABC
class Game(ABC):
def __init__(self, number_of_players):
self.number_of_players = number_of_players
self.current_player = 0
def run(self): # This is the template method
self.start()
while not self.have_winner:
self.take_turn()
print(f'Player {self.winning_player} wins!')
def start(self): pass
@property
def have_winner(self): pass
def take_turn(self): pass
@property
def winning_player(self): pass
class Chess(Game):
def __init__(self):
super().__init__(2)
self.max_turns = 10
self.turn = 1
def start(self):
print(f'Starting a game of chess with {self.number_of_players} players.')
@property
def have_winner(self):
return self.turn == self.max_turns
def take_turn(self):
print(f'Turn {self.turn} taken by player {self.current_player}')
self.turn += 1
self.current_player = 1 - self.current_player
@property
def winning_player(self):
return self.current_player
if __name__ == '__main__':
chess = Chess()
chess.run() # Call the template method form the derive
The Visitor is a component that knows how to travers a data structure that can be composed of different types, these types can or cannot be related to one another, they could also be in an inheritance tree or some types could contain other types
The visitor is a component that allows you to go over a complicated data structure and do appropriate operation on every single type of node it encounters on the data structure
Used when you need to define a new operation on a entire class hierarchy and don't want to keep modifying every class in the hierarchy, this means avoid explicit type checks
Intrusive Visitor: Modify the actual types that compose a complicated data structure
class DoubleExpression:
def __init__(self, value):
self.value = value
def print(self, buffer):
buffer.append(str(self.value))
def eval(self): return self.value
class AdditionExpression:
def __init__(self, left, right):
self.right = right
self.left = left
def print(self, buffer):
buffer.append('(')
self.left.print(buffer)
buffer.append('+')
self.right.print(buffer)
buffer.append(')')
def eval(self):
return self.left.eval() + self.right.eval()
if __name__ == '__main__':
# represents 1+(2+3)
e = AdditionExpression(
DoubleExpression(1),
AdditionExpression(
DoubleExpression(2),
DoubleExpression(3)
)
)
buffer = []
e.print(buffer)
print(''.join(buffer), '=', e.eval())
# breaks OCP: requires we modify the entire hierarchy
# what is more likely: new type or new operation?
The Checking of the type is called a reflection operation in some programming languages
from abc import ABC
class Expression(ABC):
def print(self, buffer): ExpressionPrinter.print(self, buffer)
class DoubleExpression(Expression):
def __init__(self, value):
self.value = value
class AdditionExpression(Expression):
def __init__(self, left, right):
self.right = right
self.left = left
class ExpressionPrinter:
@staticmethod
def print(e, buffer):
""" Will fail silently on a missing case. """
if isinstance(e, DoubleExpression):
buffer.append(str(e.value))
elif isinstance(e, AdditionExpression):
buffer.append('(')
ExpressionPrinter.print(e.left, buffer)
buffer.append('+')
ExpressionPrinter.print(e.right, buffer)
buffer.append(')')
# Equivalent to defined at the ABC level
# Expression.print = lambda self, b: ExpressionPrinter.print(self, b)
# still breaks OCP because new types require M×N modifications
if __name__ == '__main__':
# represents 1+(2+3)
e = AdditionExpression(
DoubleExpression(1),
AdditionExpression(
DoubleExpression(2),
DoubleExpression(3)
)
)
buffer = []
# ExpressionPrinter.print(e, buffer)
# IDE might complain here
e.print(buffer)
print(''.join(buffer))
visit method for each of the types that will be visited, then you call visit() and the entire structure gets traversed
# taken from https://tavianator.com/the-visitor-pattern-in-python/
def _qualname(obj):
"""Get the fully-qualified name of an object (including module)."""
return obj.__module__ + '.' + obj.__qualname__
def _declaring_class(obj):
"""Get the name of the class that declared an object."""
name = _qualname(obj)
return name[:name.rfind('.')]
# Stores the actual visitor methods
_methods = {}
# Delegating visitor implementation
def _visitor_impl(self, arg):
"""Actual visitor method implementation."""
method = _methods[(_qualname(type(self)), type(arg))]
return method(self, arg)
# The actual @visitor decorator
def visitor(arg_type):
"""Decorator that creates a visitor method."""
def decorator(fn):
declaring_class = _declaring_class(fn)
_methods[(declaring_class, arg_type)] = fn
# Replace all decorated methods with _visitor_impl
return _visitor_impl
return decorator
# ↑↑↑ LIBRARY CODE ↑↑↑
class DoubleExpression:
def __init__(self, value):
self.value = value
class AdditionExpression:
def __init__(self, left, right):
self.left = left
self.right = right
class ExpressionPrinter:
def __init__(self):
self.buffer = []
@visitor(DoubleExpression)
def visit(self, de):
self.buffer.append(str(de.value))
@visitor(AdditionExpression)
def visit(self, ae):
self.buffer.append('(')
self.visit(ae.left)
self.buffer.append('+')
self.visit(ae.right)
self.buffer.append(')')
def __str__(self):
return ''.join(self.buffer)
class ExpressionEvaluator:
def __init__(self):
self.value = None
@visitor(DoubleExpression)
def visit(self, de):
self.value = de.value
@visitor(AdditionExpression)
def visit(self, ae):
# ae.left.accept(self)
self.visit(ae.left)
temp = self.value # need to cache because ExpressionEvaluator is stateful
# ae.right.accept(self)
self.visit(ae.right)
self.value += temp
if __name__ == '__main__':
# represents 1+(2+3)
e = AdditionExpression(
DoubleExpression(1),
AdditionExpression(
DoubleExpression(2),
DoubleExpression(3)
)
)
printer = ExpressionPrinter()
printer.visit(e)
evaluator = ExpressionEvaluator()
evaluator.visit(e)
print(f'{printer} = {evaluator.value}')
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...