Python Unlocked (2015)
Chapter 5. Elegance with Design Patterns
In this chapter, we are going to learn some design patterns that will help us in writing better software, which is reusable and tidy. But, the biggest help is that they let developers think on an architectural level. They are solutions to recurring problems. While learning them is very helpful for compiled languages such as C and C++ because they are actually solutions to problems, in Python, developers often "just write code" without needing any design pattern due to the dynamism in the language and conciseness of code. This is largely true for developers whose first language is Python. My advice is to learn design patterns to be able to process information and design at an architectural level rather than function and classes.
In this chapter, we will cover the following topics:
· Observer pattern
· Strategy pattern
· Singleton pattern
· Template pattern
· Adaptor pattern
· Facade pattern
· Flyweight pattern
· Command pattern
· Abstract factory
· Registry pattern
· State pattern
Observer pattern
Key 1: Spreading information to all listeners.
This is the basic pattern in which an object tells other objects about something interesting. It is very useful in GUI applications, pub/sub applications, and those applications where we need to notify a lot of loosely-coupled application components about a change occurring at one source node. In the following code, Subject is the object to which other objects register themselves for events via register_observer. The observer objects are the listening objects. The observers start observing the function that registers the observersobject to Subject object. Whenever there is an event to Subject it cascades the event to all observers:
import weakref
class Subject(object):
"""Provider of notifications to other objects
"""
def __init__(self, name):
self.name = name
self._observers = weakref.WeakSet()
def register_observer(self, observer):
"""attach the observing object for this subject
"""
self._observers.add(observer)
print("observer {0} now listening on {1}".format(
observer.name, self.name))
def notify_observers(self, msg):
"""transmit event to all interested observers
"""
print("subject notifying observers about {}".format(msg,))
for observer in self._observers:
observer.notify(self, msg)
class Observer(object):
def __init__(self, name):
self.name = name
def start_observing(self, subject):
"""register for getting event for a subject
"""
subject.register_observer(self)
def notify(self, subject, msg):
"""notify all observers
"""
print("{0} got msg from {1} that {2}".format(
self.name, subject.name, msg))
class_homework = Subject("class homework")
student1 = Observer("student 1")
student2 = Observer("student 2")
student1.start_observing(class_homework)
student2.start_observing(class_homework)
class_homework.notify_observers("result is out")
del student2
class_homework.notify_observers("20/20 passed this sem")
The output for the preceding code is as follows:
(tag)[ ch5 ] $ python codes/B04885_05_code_01.py
observer student 1 now listening on class homework
observer student 2 now listening on class homework
subject notifying observers about result is out
student 1 got msg from class homework that result is out
student 2 got msg from class homework that result is out
subject notifying observers about 20/20 passed this sem
student 1 got msg from class homework that 20/20 passed this sem
Strategy pattern
Key 2: Changing the behavior of an algorithm.
Sometimes, the same piece of code must have different behavior for different invocation by different clients. For example, time-conversion for all countries must handle daylight-savings time in some countries and change their strategy in these cases. The main use is to switch the implementation. In this pattern, algorithm's behavior is selected on runtime. As Python is a dynamic language, it is trivial to assign functions to variables and change them on runtime. Similar to the following code segment, there are two implementations to calculate tax, namely, tax_simple, and tax_actual. For the following code snippet, tax_cal references clients that are used. The implementation can be changed by changing reference to the implementing function:
TAX_PERCENT = .12
def tax_simple(billamount):
return billamount * TAX_PERCENT
def tax_actual(billamount):
if billamount < 500:
return billamount * (TAX_PERCENT//2)
else:
return billamount * TAX_PERCENT
tax_cal = tax_simple
print(tax_cal(400),tax_cal(700))
tax_cal = tax_actual
print(tax_cal(400),tax_cal(700))
The output of the preceding code snippet is as follows:
48.0 84.0
0.0 84.0
But the issue with the preceding implementation is that at one time all clients will see the same strategy for tax calculation. We can improve this using a class that selects the implementation based on request parameters. In the following example, in theTaxCalculator class's instance, the strategy is determined for each call to it on runtime. If the request is for India IN, Tax is calculated as per the Indian standard, and if request is for US, it is calculated as per the US standard:
TAX_PERCENT = .12
class TaxIN(object):
def __init__(self,):
self.country_code = "IN"
def __call__(self, billamount):
return billamount * TAX_PERCENT
class TaxUS(object):
def __init__(self,):
self.country_code = "US"
def __call__(self,billamount):
if billamount < 500:
return billamount * (TAX_PERCENT//2)
else:
return billamount * TAX_PERCENT
class TaxCalculator(object):
def __init__(self):
self._impls = [TaxIN(),TaxUS()]
def __call__(self, country, billamount):
"""select the strategy based on country parameter
"""
for impl in self._impls:
if impl.country_code == country:
return impl(billamount)
else:
return None
tax_cal = TaxCalculator()
print(tax_cal("IN", 400), tax_cal("IN", 700))
print(tax_cal("US", 400), tax_cal("US", 700))
The output of the preceding code is as follows:
48.0 84.0
0.0 84.0
Singleton pattern
Key 3: Providing the same view to all.
The singleton pattern maintains the same state for all instances of a class. When we change an attribute at one place in a program, it is reflected in all references to this instance. As modules are globally shared, we can use them as singleton methods, and the variables defined in them are the same everywhere. But, there are similar issues in that as the module is reloaded, there may be more singleton classes that are needed. We can also create a singleton pattern using metaclasses in the following manner. The six is a third-party library to help in writing the same code that is runnable on Python 2 and Python 3.
In the following code, Singleton metaclass has a registry dictionary where the instance corresponding to each new class is stored. When any class asks for a new instance, this class is searched for in the registry, and if found, the old instance is passed. Otherwise, a new instance is created, stored in registry, and returned. This can be seen in the following code:
from six import with_metaclass
class Singleton(type):
_registry = {}
def __call__(cls, *args, **kwargs):
print(cls, args, kwargs)
if cls not in Singleton._registry:
Singleton._registry[cls] = type.__call__(cls, *args, **kwargs)
return Singleton._registry[cls]
class Me(with_metaclass(Singleton, object)):
def __init__(self, data):
print("init ran", data)
self.data = data
m = Me(2)
n = Me(3)
print(m.data, n.data)
The following is the output of the preceding code:
<class '__main__.Me'> (2,) {}
init ran 2
<class '__main__.Me'> (3,) {}
2 2
Template pattern
Key 4: Refining algorithm to use case.
In this pattern, we define the skeleton of an algorithm in a method called the template method, which defers some of its steps to subclasses. How we do this is as follows, we analyze the procedure, and break it down to logical steps, which are different for different use cases. Now, we may or may not implement the default implementation of these steps in the main class. The subclasses of the main class will implement the steps that are not implemented in the main class, and they may skip some generic steps implementation. In the following example, AlooDish is base class with the cook template method. It applies to normal Aloo fried dishes, which have a common cooking procedure. Each recipe is a bit different in ingredients, time to cook, and so on. Two variants,AlooMatar, and AlooPyaz, define some set of steps differently than others:
import six
class AlooDish(object):
def get_ingredients(self,):
self.ingredients = {}
def prepare_vegetables(self,):
for item in six.iteritems(self.ingredients):
print("take {0} {1} and cut into smaller pieces".format(item[0],item[1]))
print("cut all vegetables in small pieces")
def fry(self,):
print("fry for 5 minutes")
def serve(self,):
print("Dish is ready to be served")
def cook(self,):
self.get_ingredients()
self.prepare_vegetables()
self.fry()
self.serve()
class AlooMatar(AlooDish):
def get_ingredients(self,):
self.ingredients = {'aloo':"1 Kg",'matar':"1/2 kg"}
def fry(self,):
print("wait 10 min")
class AlooPyaz(AlooDish):
def get_ingredients(self):
self.ingredients = {'aloo':"1 Kg",'pyaz':"1/2 kg"}
aloomatar = AlooMatar()
aloopyaz = AlooPyaz()
print("******************* aloomatar cook")
aloomatar.cook()
print("******************* aloopyaz cook")
aloopyaz.cook()
The following is the output of the preceding example code:
******************* aloomatar cook
take matar 1/2 kg and cut into smaller pieces
take aloo 1 Kg and cut into smaller pieces
cut all vegetables in small pieces
wait 10 min
Dish is ready to be served
******************* aloopyaz cook
take pyaz 1/2 kg and cut into smaller pieces
take aloo 1 Kg and cut into smaller pieces
cut all vegetables in small pieces
fry for 5 minutes
Dish is ready to be served
Adaptor pattern
Key 5: Bridging class interfaces.
This pattern is used to adapt a given class to a new interface. It solves the problem for an interface mismatch. To demonstrate this, let's assume that we have an API function that creates a competition to run different animals. Animals should have a running_speedfunction, which tells their speed to compare them. Cat is one such class. Now, if we have a Fish class in a different library, which also wants to participate in the function, it must be able to know its running_speed function. As changing the implementation of Fish is not good option, we can create an adaptor class that can adapt the Fish class to run by providing the necessary bridge:
def running_competition(*list_of_animals):
if len(list_of_animals)<1:
print("No one Running")
return
fastest_animal = list_of_animals[0]
maxspeed = fastest_animal.running_speed()
for animal in list_of_animals[1:]:
runspeed = animal.running_speed()
if runspeed > maxspeed:
fastest_animal = animal
maxspeed = runspeed
print("winner is {0} with {1} Km/h".format(fastest_animal.name,maxspeed))
class Cat(object):
def __init__(self, name, legs):
self.name = name
self.legs = legs
def running_speed(self,):
if self.legs>4 :
return 20
else:
return 40
running_competition(Cat('cat_a',4),Cat('cat_b',3))
class Fish(object):
def __init__(self, name, age):
self.name = name
self.age = age
def swim_speed(self):
if self.age < 2:
return 40
else:
return 60
# to let our fish to participate in tournament it should have similar interface as
# cat, we can also do this by using an adaptor class RunningFish
class RunningFish(object):
def __init__(self, fish):
self.legs = 4 # dummy
self.fish = fish
def running_speed(self):
return self.fish.swim_speed()
def __getattr__(self, attr):
return getattr(self.fish,attr)
running_competition(Cat('cat_a',4),
Cat('cat_b',3),
RunningFish(Fish('nemo',3)),
RunningFish(Fish('dollar',1)))
The output of the preceding code is follows:
winner is cat_a with 40 Km/h
winner is nemo with 60 Km/h
Facade pattern
Key 6: Hiding system complexity for a simpler interface.
In this pattern, a main class called facade exports a simpler interface to client classes and encapsulates the complexity of interaction with many other classes of the system. It is like a gateway to a complex set of functionality, such as in the following example, theWalkingDrone class hides the complexity of synchronization of the Leg classes and provides a simpler interface to client classes:
class Leg(object):
def __init__(self,name):
self.name = name
def forward(self):
print("{0},".format(self.name), end="")
class WalkingDrone(object):
def __init__(self, name):
self.name = name
self.frontrightleg = Leg('Front Right Leg')
self.frontleftleg = Leg('Front Left Leg')
self.backrightleg = Leg('Back Right Leg')
self.backleftleg = Leg('Back Left Leg')
def walk(self):
print("\nmoving ",end="")
self.frontrightleg.forward()
self.backleftleg.forward()
print("\nmoving ",end="")
self.frontleftleg.forward()
self.backrightleg.forward()
def run(self):
print("\nmoving ",end="")
self.frontrightleg.forward()
self.frontleftleg.forward()
print("\nmoving ",end="")
self.backrightleg.forward()
self.backleftleg.forward()
wd = WalkingDrone("RoboDrone" )
print("\nwalking")
wd.walk()
print("\nrunning")
wd.run()
This code will give us the following output:
walking
moving Front Right Leg,Back Left Leg,
moving Front Left Leg,Back Right Leg,
running
moving Front Right Leg,Front Left Leg,
moving Back Right Leg,Back Left Leg,Summary
Flyweight pattern
Key 7: Consuming less memory with shared objects.
A flyweight design pattern is useful to save memory. When we have lots of object count, we store references to previous similar objects and provide them instead of creating new objects. In the following example, we have a Link class used by the browser, which stores the link data.
The browser uses this data, and there may be a lot of data that is associated with pictures referenced by the link, such as image content, size, and so on, and images can be reused over the page. Hence, the nodes using it only store a flyweight BrowserImage object to decrease the memory footprint. When the link class tries to create a new BrowserImage instance, the BrowserImage class checks whether it has an instance in its _resources mapping for the resource path. If it does, it will just pass the old instance:
import weakref
class Link(object):
def __init__(self, ref, text, image_path=None):
self.ref = ref
if image_path:
self.image = BrowserImage(image_path)
else:
self.image = None
self.text = text
def __str__(self):
if not self.image:
return "<Link (%s)>" % self.text
else:
return "<Link (%s,%s)>" % (self.text, str(self.image))
class BrowserImage(object):
_resources = weakref.WeakValueDictionary()
def __new__(cls, location):
image = BrowserImage._resources.get(location, None)
if not image:
image = object.__new__(cls)
BrowserImage._resources[location] = image
image.__init(location)
return image
def __init(self, location):
self.location = location
# self.content = load picture into memory
def __str__(self,):
return "<BrowserImage(%s)>" % self.location
icon = Link("www.pythonunlocked.com",
"python unlocked book",
"http://pythonunlocked.com/media/logo.png")
footer_icon = Link("www.pythonunlocked.com/#bottom",
"unlocked series python book",
"http://pythonunlocked.com/media/logo.png")
twitter_top_header_icon = Link("www.twitter.com/pythonunlocked",
"python unlocked twitter link",
"http://pythonunlocked.com/media/logo.png")
print(icon,)
print(footer_icon,)
print(twitter_top_header_icon,)
The output of the preceding code is follows:
<Link (python unlocked book,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>
<Link (unlocked series python book,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>
<Link (python unlocked twitter link,<BrowserImage(http://pythonunlocked.com/media/logo.png)>)>
Command pattern
Key 8: Easy-execution management for commands.
In this pattern, we encapsulate information that is needed to execute a command in an object so that command itself can have further capabilities, such as undo, cancel, and metadata that are needed at a later point of time. For example, let's create a simple Chef in a restaurant, users can issue orders (commands), commands here have metadata that are needed to cancel them. This is similar to a notepad app where each user action is recorded with an undo method. This makes coupling loose between caller and the invoker, shown as follows:
import time
import threading
class Chef(threading.Thread):
def __init__(self,name):
self.q = []
self.doneq = []
self.do_orders = True
threading.Thread.__init__(self,)
self.name = name
self.start()
def makeorder(self, order):
print("%s Preparing Menu :"%self.name )
for item in order.items:
print("cooking ",item)
time.sleep(1)
order.completed = True
self.doneq.append(order)
def run(self,):
while self.do_orders:
if len(self.q) > 0:
order = self.q.pop(0)
self.makeorder(order)
time.sleep(1)
def work_on_order(self,order):
self.q.append(order)
def cancel(self, order):
if order in self.q:
if order.completed == True:
print("cannot cancel, order completed")
return
else:
index = self.q.index(order)
del self.q[index]
print(" order canceled %s"%str(order))
return
if order in self.doneq:
print("order completed, cannot be canceled")
return
print("Order not given to me")
class Check(object):
def execute(self,):
raise NotImplementedError()
def cancel(self,):
raise NotImplementedError()
class MenuOrder(Check):
def __init__(self,*items):
self.items = items
self.completed = False
def execute(self,chef):
self.chef = chef
chef.work_on_order(self)
def cancel(self,):
if self.chef.cancel(self):
print("order cancelled")
def __str__(self,):
return ''.join(self.items)
c = Chef("Arun")
order1 = MenuOrder("Omellette", "Dosa", "Idli")
order2 = MenuOrder("Mohito", "Pizza")
order3 = MenuOrder("Rajma", )
order1.execute(c)
order2.execute(c)
order3.execute(c)
time.sleep(1)
order3.cancel()
time.sleep(9)
c.do_orders = False
c.join()
The output of the preceding code is as follows:
Arun Preparing Menu :
cooking Omellette
order canceled Rajma
cooking Dosa
cooking Idli
Arun Preparing Menu :
cooking Mohito
cooking Pizza
Abstract factory
This design pattern creates an interface to create a family of interrelated objects without specifying their concrete class. It is similar to a superfactory. Its advantage is that we can add further variants, and clients will not have to worry further about the interface or actual classes for the new variants. It is helpful in supporting various platforms, windowing systems, data types, and so on. In the following example, the Animal class is the interface that the client will know about for any animal instance. AnimalFactory is the abstract factory that DogFactory and CatFactory implement. Now, on the runtime by user input, or configuration file, or runtime environment check, we can decide whether we will have all Dog or Cat instances. It is very convenient to add a new class implementation, as follows:
import os
import abc
import six
class Animal(six.with_metaclass(abc.ABCMeta, object)):
""" clients only need to know this interface for animals"""
@abc.abstractmethod
def sound(self, ):
pass
class AnimalFactory(six.with_metaclass(abc.ABCMeta, object)):
"""clients only need to know this interface for creating animals"""
@abc.abstractmethod
def create_animal(self,name):
pass
class Dog(Animal):
def __init__(self, name):
self.name = name
def sound(self, ):
print("bark bark")
class DogFactory(AnimalFactory):
def create_animal(self,name):
return Dog(name)
class Cat(Animal):
def __init__(self, name):
self.name = name
def sound(self, ):
print("meow meow")
class CatFactory(AnimalFactory):
def create_animal(self,name):
return Cat(name)
class Animals(object):
def __init__(self,factory):
self.factory = factory
def create_animal(self, name):
return self.factory.create_animal(name)
if __name__ == '__main__':
atype = input("what animal (cat/dog) ?").lower()
if atype == 'cat':
animals = Animals(CatFactory())
elif atype == 'dog':
animals = Animals(DogFactory())
a = animals.create_animal('bulli')
a.sound()
The preceding code will give us the following output:
1st run:
what animal (cat/dog) ?dog
bark bark
2nd run:
what animal (cat/dog) ?cat
meow meow
Registry pattern
Key 9: Adding functionality from anywhere in code to class.
This is one of my favorite patterns and comes to help a lot. In this pattern, we register classes to a registry, which tracks the naming to functionality. Hence, we can add functionality to the main class from anywhere in the code. In the following code, Convertor tracks all convertors from dictionary to Python objects. We can easily add further functionalities to the system using the convertor.register decorator from anywhere in the code, as follows:
class ConvertError(Exception):
"""Error raised on errors on conversion"""
pass
class Convertor(object):
def __init__(self,):
"""create registry for storing method mapping """
self.__registry = {}
def to_object(self, data_dict):
"""convert to python object based on type of dictionary"""
dtype = data_dict.get('type', None)
if not dtype:
raise ConvertError("cannot create object, type not defined")
elif dtype not in self.__registry:
raise ConvertError("cannot convert type not registered")
else:
convertor = self.__registry[dtype]
return convertor.to_python(data_dict['data'])
def register(self, convertor):
iconvertor = convertor()
self.__registry[iconvertor.dtype] = iconvertor
convertor = Convertor()
class Person():
""" a class in application """
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self,):
return "<Person (%s, %s)>" % (self.name, self.age)
@convertor.register
class PersonConvertor(object):
def __init__(self,):
self.dtype = 'person'
def to_python(self, data):
# not checking for errors in dictionary to instance creation
p = Person(data['name'], data['age'])
return p
print(convertor.to_object(
{'type': 'person', 'data': {'name': 'arun', 'age': 12}}))
The following is the output for the preceding code:
<Person (arun, 12)>
State pattern
Key 10: Changing execution based on state.
State machines are very useful for an algorithm whose vector-flow of control depends on the state of the application. Similar to when parsing a log output with sections, you may want to change the parser logic on every next section. It is also very useful to write code for network servers/clients who enable certain commands in a certain scope:
def outputparser(loglines):
state = 'header'
program,end_time,send_failure= None,None,False
for line in loglines:
if state == 'header':
program = line.split(',')[0]
state = 'body'
elif state == 'body':
if 'send_failure' in line:
send_failure = True
if '======' in line:
state = 'footer'
elif state == 'footer':
end_time = line.split(',')[0]
return program, end_time, send_failure
print(outputparser(['sampleapp,only a sampleapp',
'logline1 sadfsfdf',
'logline2 send_failure',
'=====================',
'30th Jul 2016,END']))
This will give us the following output:
('sampleapp', '30th Jul 2016', True)
Summary
In this chapter, we saw various design patterns that can help us better organize the code, and in some cases, increase performance. The good thing about patterns is they let you think beyond classes, and they provide strategy for architecture of your application. As closing advice for this chapter, do not code to use design pattern; when you code and see a good fit, only then use design pattern.
Now, we will go onto testing, which is a must for any serious development effort.