Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Friday, December 30, 2011

Factory Method Pattern in Python

Ok, here we go again. This time, I will demonstrate Factory Method Design Pattern  


Let's say we need to design a module to retrieve 4 statistics counters, 
  1. Bytes Transmitted 
  2. Bytes Received
  3. Packet Transmitted
  4. Packets Received
So, we start with creating a base class and 4 inherited classes:


1:  class StatsClass(object):  
2:    
3:    def __init__(self):  
4:      self._counter = 0  
5:      self._name ="Abtract Statistics Class"  
6:        
7:    def Get_Name(self):  
8:      return self._name  
9:      
10:    def Get(self):  
11:      return self._counter;  
12:      
13:    def Reset(self):  
14:      self._counter = 0;  
15:      
16:    def Set(self,counter):  
17:      self._counter = counter;  
18:        
19:    def PollFromHw(self):  
20:      print(self._name, "is poll statistics counter from hardware register")  
21:      
22:    
23:  class StatsBytesTransmitted(StatsClass):  
24:    def __init__(self):  
25:      self._name = "Bytes Transmitted"  
26:      self._counter = 0    
27:    
28:  class StatsBytesReceived(StatsClass):  
29:    def __init__(self):  
30:      self._name = "Bytes Received"  
31:      self._counter = 0  
32:    
33:  class StatsPacketsTransmitted(StatsClass):  
34:    def __init__(self):  
35:      self._name = "Packets Transmitted"  
36:      self._counter = 0  
37:      
38:  class StatsPacketsReceived(StatsClass):  
39:    def __init__(self):  
40:      self._name = "Packets Received"  
41:      self._counter = 0  
42:        


Then, create a Factory Class


1:  # Factory Class  
2:  class AbstractStatsFactory():  
3:    def CreateStats(self,strStats):  
4:      pass  
5:    
6:  class StatsFactory(AbstractStatsFactory):  
7:    def CreateStats(self,strStats):  
8:      if( strStats == "Bytes Received"):  
9:        StatsObj = StatsBytesReceived()  
10:      elif ( strStats == "Bytes Transmitted"):  
11:        StatsObj = StatsBytesTransmitted()  
12:      elif ( strStats == "Packets Transmitted"):  
13:        StatsObj = StatsPacketsTransmitted()    
14:      elif ( strStats == "Packets Received"):  
15:        StatsObj = StatsPacketsReceived()    
16:      else:  
17:        StatsObj = None    
18:    
19:      return StatsObj  




Now, let's put them into use


1:  def Use_StatsObj(StatsObj):   
2:    StatsObj.PollFromHw()  
3:    StatsObj.Set(100)  
4:    print(StatsObj.Get_Name(), "counter is ", StatsObj.Get())    
5:    StatsObj.Reset()  
6:    print(StatsObj.Get_Name(), "counter Reset. Now counter is ", StatsObj.Get())    
7:    
8:    
9:    
10:  strStats = "Bytes Received"  
11:  StatsObj = StatsFactory().CreateStats(strStats)  
12:  Use_StatsObj(StatsObj)  
13:    
14:  strStats = "Bytes Transmitted"  
15:  StatsObj = StatsFactory().CreateStats(strStats)  
16:  Use_StatsObj(StatsObj)  


The output is 
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Bytes Transmitted is poll statistics counter from hardware register  
 Bytes Transmitted counter is 100  
 Bytes Transmitted counter Reset. Now counter is 0  


No bad so far, though, I am not satisfied.

There is still an if-elif-else statement in the StatsFactory Class. It will be nice if the Factory class knows how many Statistics Objects are there, without explicitly instantiated these concrete objects .


In OO theme, you can use Reflection to do so.  In Python, this is how I implement it


1:  class StatsFactory2(AbstractStatsFactory):  
2:    def CreateStats(self,strStats):  
3:      # Get all subclass  
4:      for childClass in StatsClass.__subclasses__() :  
5:        obj = childClass()  
6:        if( strStats == obj.Get_Name()):  
7:          return obj  
8:    def ListStatsObjNames(self):  
9:      objList = list()  
10:      for childClass in StatsClass.__subclasses__() :  
11:        # print(childClass)  
12:        obj = childClass()  
13:        objList.append(obj.Get_Name())  
14:      return objList   
15:        
16:    


Now, it's time to put the code into test


1:  print("--- now use New Factory --- ")  
2:  strStats = "Bytes Received"  
3:  StatsObj = StatsFactory2().CreateStats(strStats)  
4:  Use_StatsObj(StatsObj)  
5:    
6:  print("Counter can be read: ", StatsFactory2().ListStatsObjNames())  
7:  while(True):  
8:    strStats = input("Which counter do you want to know ?");  
9:    StatsObj = StatsFactory2().CreateStats(strStats)  
10:    if( StatsObj == None ):  
11:      break  
12:    Use_StatsObj(StatsObj)  
13:    


This is the Output


 --- now use New Factory ---   
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Counter can be read: ['Bytes Transmitted', 'Bytes Received', 'Packets Transmitted', 'Packets Received']  
 Which counter do you want to know ?Bytes Transmitted  
 Bytes Transmitted is poll statistics counter from hardware register  
 Bytes Transmitted counter is 100  
 Bytes Transmitted counter Reset. Now counter is 0  
   


Now, this is beauty part -- let's add two new statistics counters right after the  StatsPacketsReceived 


1:  class StatsFcsError(StatsClass):  
2:    def __init__(self):  
3:      self._name = "Fcs Error"  
4:      self._counter = 0  
5:    
6:  class StatsDiscardPacket(StatsClass):  
7:    def __init__(self):  
8:      self._name = "Discard Packets"  
9:      self._counter = 0    
10:    


Then, run my test code without any change. Voila, the two new classes are automatically recognized and can be used. 


 --- now use New Factory ---   
 Bytes Received is poll statistics counter from hardware register  
 Bytes Received counter is 100  
 Bytes Received counter Reset. Now counter is 0  
 Counter can be read: ['Bytes Transmitted', 'Bytes Received', 'Packets Transmitted', 'Packets Received', 'Fcs Error', 'Discard Packets']  
 Which counter do you want to know ?Discard Packets  
 Discard Packets is poll statistics counter from hardware register  
 Discard Packets counter is 100  
 Discard Packets counter Reset. Now counter is 0  
 Which counter do you want to know ?  




Beautiful, isn't it ?

Wednesday, December 28, 2011

Decorator Pattern in Python

I re-read Head-First Design Pattern lately. And decide to implement Decorator Design Pattern in Python.


The following is based on the example from the book

  1. The existing code has base class Beverage, and subclass DarkRoast, Espresso. These code has been fully tested and in production (line 1-21). 
  2. Three months later, your boss want to have more varieties of drink, .e.g Whip Cream, Vanilla. So, you need to write more code to support that.
  3. It's best to use Decorator Design Pattern, as it obeys the OCP(Open-Closed) Principle. (line 24-51)
  4. Mocha, WhipCream, Vanilla are all decorator class.


Source Code

1:  class Beverage:  
2:    """ beverage class """  
3:    def __init__(self):  
4:      self._desc = "Abstract Drink"  
5:      self._cost = 0.0  
6:      
7:    def get_cost(self):  
8:      return self._cost  
9:      
10:    def get_desc(self):  
11:      return self._desc  
12:      
13:  class DarkRoast(Beverage):  
14:    def __init__(self):  
15:      self._cost = 3.5  
16:      self._desc = "Dark Roast ($" + str(self._cost) + ")"  
17:          
18:  class Espresso(Beverage):  
19:    def __init__(self):  
20:      self._cost = 3.0  
21:      self._desc = "Espresso ($" + str(self._cost)+ ")"  
22:    
23:    
24:  # Design Pattern   
25:  # Abstract Decorator  
26:  class Condiments(Beverage):  
27:    def __init__(self):  
28:      self._desc = "Abstract Condiments class"  
29:      self._cost_condiment = 0.0      
30:      
31:  class Mocha(Condiments):  
32:    def __init__(self, beverage):  
33:      self._cost_condiment = 1.0  
34:      self._beverage = beverage;      
35:      self._desc = "Mocha($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
36:      self._cost = self._cost_condiment + self._beverage.get_cost()   
37:        
38:  class Vanilla(Condiments):  
39:    def __init__(self, beverage):  
40:      self._cost_condiment = 0.6  
41:      self._beverage = beverage;      
42:      self._desc = "Vanilla($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
43:      self._cost = 0.6 + self._beverage.get_cost()   
44:           
45:    
46:  class WhipCream(Condiments):  
47:    def __init__(self,beverage):  
48:      self._cost_condiment = 0.4  
49:      self._beverage = beverage;      
50:      self._desc = "WhipCream($"+ str(self._cost_condiment)+ ") " + self._beverage.get_desc()   
51:      self._cost = 0.4 + self._beverage.get_cost()   
52:        
53:  ########################################################################################3    
54:    
55:    
56:  b = DarkRoast()  
57:  print(b.get_desc(), "Cost is", b.get_cost())      
58:    
59:  b = Mocha(DarkRoast())  
60:  print(b.get_desc(), "Cost is", b.get_cost())    
61:    
62:  b = Mocha(Espresso())  
63:  print(b.get_desc(), "Cost is", b.get_cost())    
64:    
65:  b = Vanilla(DarkRoast())  
66:  print(b.get_desc(), "Cost is", b.get_cost())   
67:    
68:  b = Vanilla(Mocha(DarkRoast()))  
69:  print(b.get_desc(), "Cost is", b.get_cost())   
70:    
71:  b = WhipCream(Mocha(DarkRoast()))  
72:  print(b.get_desc(), "Cost is", b.get_cost())   
73:    
74:    
75:  b = Vanilla(WhipCream(Mocha(DarkRoast())))  
76:  print(b.get_desc(), "Cost is", b.get_cost())   
77:    


Output

 Dark Roast ($3.5) Cost is 3.5  
 Mocha($1.0) Dark Roast ($3.5) Cost is 4.5  
 Mocha($1.0) Espresso ($3.0) Cost is 4.0  
 Vanilla($0.6) Dark Roast ($3.5) Cost is 4.1  
 Vanilla($0.6) Mocha($1.0) Dark Roast ($3.5) Cost is 5.1  
 WhipCream($0.4) Mocha($1.0) Dark Roast ($3.5) Cost is 4.9  
 Vanilla($0.6) WhipCream($0.4) Mocha($1.0) Dark Roast ($3.5) Cost is 5.5  


Why not using Python built-in Decorator ?

This is good question. My reasons are

  1. You have to modify the existing code. In the source code above, you have to add @decorator in DarkRoast or Espresso class. With Decorator Design Pattern, you don't have to modify existing code. 
  2. You cannot undo the @decorator during run-time. Once it's there, it's there forever. If you use Decorator Design Pattern, you still can enjoy the original espresso. :)




Saturday, August 13, 2011

Install Python Code Coverage in Ubuntu 11.04

Just started to learn using Python on Linux. For me, the most troubling stuffs is to get the program installed over Linux.


Anyhow, these are prerequisites,

  • You have Python installed. Ubuntu comes with Python2.7. But, you can install Python 3.2 following another post 
  • Python Setuptools. If you don't have this, you can install it by 
  • $ sudo apt-get install python-setuptools
    


These are steps to install Python Code Coverage
  1. Download the Python Code Coverage from http://pypi.python.org/pypi/coverage. As of writing, the version is 3.5
  2. extract the tarball to a folder, .e.g. ~/Downloads/coverage-3.5
  3. In Ubuntu, the default python is 2.7. It will install to /usr/local/lib/python2.7/dist-packages/
newgear@ubuntu:~/Downloads/coverage-3.5$ sudo python setup.py install -v

Or, you can do this to install to Python 3.2. It will install to /usr/local/lib/python3.2/dist-packages/

newgear@ubuntu:~/Downloads/coverage-3.5$ sudo python3.2 setup.py install -v

Monday, August 8, 2011

Install Python 3.2 on Ubuntu 11.04

Ubuntu comes with Python 2.7, but I want to try out Python 3.2.x. After some google searches, I cannot just remove Python 2.7 and install 3.2.x, as Ubuntu's other modules requires 2.7


But, you can have Python 3.2.x coexist with Python  2.7. This is how:





Launch Ubuntu Software Center, then search Python 3.2. Select Python 3.2 and install





After that, you can run Python 3.2 in terminal like this


newgear@ubuntu:~$ python3.2
Python 3.2 (r32:88445, Mar 25 2011, 19:28:28) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=2, micro=0, releaselevel='final', serial=0)