| 
      Often it is difficult to make the transition from 
      procedural scripting to object-oriented programming. 
      This article explores how to reuse knowledge from PHP, 
      Bash, or Python scripting to transition to 
      object-oriented programming in Python. The article also 
      briefly touches on the appropriate use of functional 
      programming. 
      Introduction Python has begun soaring in popularity in recent years, 
     and part of the reason is that the language is very 
     flexible, yet incredibly powerful. Python can be used for 
     systems administration, Web development, GUI programming, 
     scientific computing, and more. The main aim of this article 
     is to introduce people who are used to scripting procedural 
     code in Bash, PHP, or some other language, and to assist 
     them in moving into object-oriented Python developing. This 
     rising popularity of Python means that developers currently 
     using other programming languages might be called upon to do 
     some of their projects in Python, in addition to their 
     favorite language.  Procedural programming certainly has its place, and it 
     can be a highly effective way to solve a problem. On a very 
     basic level, procedural programmming can be defined as a 
     list of instructions, and Bash and PHP often are written in 
     such a manner. Because of Python's popularity, though, PHP 
     and Bash scripters who are Web developers or systems 
     administrators are getting thrown into situations in which 
     they have to learn both object-oriented programming and 
     Python at the same time.  Object-oriented can be a lot to digest at once, so this 
     article takes on procedural Bash and PHP scripts, and first 
     converts them to procedural Python. As a final step, they 
     get converted into the end goal of object-oriented Python. 
     The article concludes by touching on a few advantages of 
     object-oriented Python, and then finally with some 
     disadvantages in which either procedural or functional 
     programming might be a better fit. By the end of this 
     article, Bash or PHP programmers should be able to jump into 
     object-oriented Python projects without fear.  
      
       |  | 
        
         | If you haven't heard 
         of functional programming before, I would 
         highly recommend reading some of the 
         articles on functional programming in the
         
         resources section. Briefly, though, 
         functional programming can be described as 
         "passing functions around." Often functional 
         programming can be a more succinct and clear 
         way to express an idea than 
         object-orientated programming. |  |  Writing a 
     disk-monitoring function in PHP and Bash While PHP is mostly meant to be run inside a browser, it 
     can also perform system calls by way of the exec function. 
     The first example, written in PHP, will capture the output 
     of the shell command "df -h," place the output into an 
     array, and then examine each line of output against a 
     regular expression. If the line matches the regular 
     expression, then you print out that line. If you want to run 
     this example from home, you will only need to call this 
     script index.php and place it in the served-out 
     directory of an Apache/mod_php server.  PHP disk-monitoring 
     example
 
 
      
       |                 
<html>
<body>
<?php
//Analyzes disk usage
//Takes regex pattern and message
function disk_space( $pattern="/2[0-9]%/", $message="CAPACITY WARNING:" )
{
    exec(escapeshellcmd("df -h"),$output_lines,$return_value);
    foreach ($output_lines as $output) {
        if (preg_match( $pattern, $output ))
            echo "<b>$message</b> $output <br />";
    }
}
disk_space()
?>
</body>
</html>
        
       |  
 If you run this Web page in a browser, you get the following results:
      
      
       |         CAPACITY WARNING: /dev/sda1 3.8G 694M 2.9G 20% / 
       |  Looking at the code, you can see that the regular expression 
     pattern was set to match a line that contained 20-29%. This 
     could be modified easily to fit some other flag, such as 
     90-99%, as 20% is a very low disk capacity.
 Next let's look at doing this in a Bash function. In 
     Bash, the problem is much easier to solve because you are 
     really processing system calls. In this example, you don't 
     even need to use an array or a regular expression library, 
     because a pipe to grep is much easier. Setting default 
     parameters for functions in Bash are always a bit verbose, 
     though.  Bash 
     disk-monitoring example
 
 
      
       |                 
#!/usr/bin/env bash
#function flags disk usage takes pattern and message optionally
function disk_space ()
{
    #checks for pattern parameter
    if [ "$1" != "" ]; then
        pattern=$1
    else
        pattern="2[0-9]%"
    fi
    #checks for message parameter
    if [ "$2" != "" ]; then
        message=$2
    else
        message="CAPACITY WARNING:"
    fi
    #looks at output for pattern to flag
    output_lines=`df -h | grep $pattern`
    if [ "$output_lines" != "" ]; then
        echo $message $output_lines
    fi
}
#example of optional parameters usage
#disk_space 9[0-9]% ALERT:
disk_space
       |  
 When you run this script, you get the same output, so you can skip 
     showing it. What you can correlate from the PHP version of 
     the script and Bash version is that this procedural code 
     does in fact run like a set of instructions. It is almost as 
     if the computer is a small child, and you are telling the 
     child how to do something like tie his shoes for the first 
     time. Before you get into thinking in the "Object-Oriented 
     Paradigm" in Python, let's look at making a procedural 
     version of this same function in Python.  Python 
     disk-monitoring example
 
 
      
       |                 
from subprocess import Popen, PIPE
import re
def disk_space(pattern="2[0-9]%", message="CAPACITY WARNING:"):
        #takes shell command output
        ps = Popen("df -h", shell=True,stdout=PIPE, stderr=PIPE)
        output_lines = ps.stdout.readlines()
        for line in output_lines:
            line = line.strip()
            if re.search(pattern,line):
                print "%s %s" % (message,line)
disk_space()
       |  
 Looking over the procedural Python version of our code, it quite 
     similar to both the Bash and PHP versions. With Python, the 
     subprocess module handles making the system call to the 
     shell command, and puts it into a list, called in array in 
     Bash or PHP. Much like the PHP version, I then iterate over 
     the items in the list that are lines of the standard out of 
     the command. I look for a regular expression that makes the 
     pattern I am looking for, and then print the line of the 
     disk report with a special message that gets injected. This 
     is a classic example of how a top-down scripting problem can 
     be solved, but in the next section you change your approach 
     completely and think in terms of objects.  From procedural 
     to object-oriented Python Procedural programming is often the most natural style of 
     programming for a beginning developer, and it is also highly 
     effective for many problems. On the other hand, 
     object-oriented programming can be a very useful way to 
     create abstraction, and thus resuable code. Often procedural 
     code can show cracks in its foundation when a project 
     approaches a certain level of complexity, though. Let's jump 
     right into an object-oriented version of the last example 
     and see how that changes things.  Object-oriented Python 
     disk-monitoring script
 
 
      
       |                 
#!/usr/bin/env python
from subprocess import Popen, PIPE
import re
class DiskMonitor():
    """Disk Monitoring Class"""
    def __init__(self,
                pattern="2[0-9]%",
                message="CAPACITY WARNING",
                cmd = "df -h"):
        self.pattern = pattern
        self.message = message
        self.cmd = cmd
    def disk_space(self):
        """Disk space capacity flag method"""
        ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
        output_lines = ps.stdout.readlines()
        for line in output_lines:
            line = line.strip()
            if re.search(self.pattern,line):
                print "%s %s" % (self.message,line)
if __name__ == "__main__":
    d = DiskMonitor()
    d.disk_space()
       |  
 In looking at the object-oriented version of the code, you can see that 
     the code becomes more abstract. Sometimes too much 
     abstraction can lead to design problems, but in this case it 
     allows you to separate the problem into more reusable 
     pieces. The DiskMonitor class has an __init__ method where 
     you define your new parameters, and the disk_space function 
     is now a method inside of the class.  With this new style, you can easily reuse and customize 
     things without changing the original code, as is often 
     necessary with procedural code. One of the more powerful, 
     and also overused, aspects of object-oriented design is 
     inheritance. Inheritance allows you to reuse and customize 
     existing code inside a new class. Let's see what that might 
     look like in this next example.  Object-oriented Python 
     disk-monitoring script using inheritance
 
 
      
       |                 
#!/usr/bin/env python
from subprocess import Popen, PIPE
import re
class DiskMonitor():
    """Disk Monitoring Class"""
    def __init__(self,
                pattern="2[0-9]%",
                message="CAPACITY WARNING",
                cmd = "df -h"):
        self.pattern = pattern
        self.message = message
        self.cmd = cmd
    def disk_space(self):
        """Disk space capacity flag method"""
        ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
        output_lines = ps.stdout.readlines()
        for line in output_lines:
            line = line.strip()
            if re.search(self.pattern,line):
                print "%s %s" % (self.message,line)
class MyDiskMonitor(DiskMonitor):
    """Customized Disk Monitoring Class"""
    def disk_space(self):
        ps = Popen(self.cmd, shell=True,stdout=PIPE,stderr=PIPE)
        print "RAW DISK REPORT:"
        print ps.stdout.read()
if __name__ == "__main__":
    d = MyDiskMonitor()
    d.disk_space()                     
       |  
 If you run this version of the script that uses inheritance, you get 
     the following output:  
      
       | RAW DISK REPORT:
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda1             3.8G  694M  2.9G  20% /
varrun                252M   48K  252M   1% /var/run
varlock               252M     0  252M   0% /var/lock
udev                  252M   52K  252M   1% /dev
devshm                252M     0  252M   0% /dev/shm
    
 |  This output is very different from the previous -- flagged 
     -- version, because it is just the unfiltered results of df 
     -h with a print statement injected at the top. You were able 
     to change the intent of the disk_space method completely by 
     overriding the method in the MyDiskMonitor class.
 The Python magic that allowed you to reuse the attributes 
     from the other class were this "MyDiskMonitor(DiskMonitor)." 
     You only needed to place the name of the previous class 
     inside of paranethesis when you defined the name of the new 
     class. As soon as that happens, you instantly gain access to 
     the other classes attributes to do with as you wish. The fun 
     doesn't stop there, either. You could further customize the 
     new class by adding another method, perhaps called 
     disk_alert(self), which would e-mail flagged messages. This 
     is the beauty of object-oriented design; it allows 
     experienced developers to constantly reuse code they have 
     written, and save themselves quite a bit of time.  There is a dark side to object-oriented programming, as 
     well, unfortunately. All of this abstraction comes at a cost 
     of complexity, and if abstraction is taken far enough, it 
     can get downright ugly. Because Python supports multiple 
     inheritance, abstraction can be taken to a level of 
     complexity that is quite unhealthy. Can you imagine having 
     to look at several files to just write one method? Believe 
     it or not, this does happen, and it represents the 
     unfortunate reality of one side of object-oriented 
     programming.  One alternate to object-oriented programming is 
     functional programming, and Python offers resources to 
     program in a functional style, as well as object oriented 
     and procedural. In the final example, let's take a look out 
     how to write our now tiresome disk-monitoring code in a 
     functional style.  
      
       |  | 
        
         | There's a simple 
         answer to the problem of overuse of 
         inheritance: Think about whether the problem 
         can be refactored as "A contains B" (a.b) 
         rather than "A is a subclass of B" (A(B)). 
         If so, the 'contains' approach is almost 
         always better. |  |  Functional-style Python 
     disk-monitoring script
 
 
      
       |                 
from subprocess import Popen, PIPE
import re
def disk_space(pattern="2[0-9]%", message="CAPACITY WARNING:"):
    #Generator Pipeline To Search For Critical Items
    ps = Popen("df -h", shell=True,stdout=PIPE, stderr=PIPE)
    outline = (line.split() for line in ps.stdout)
    flag = (" ".join(row) for row in outline if re.search(pattern, row[-2]))
    for line in flag:
        print "%s %s" % (message,line)
disk_space()
 |  
 If you look at this last example, it is much different from anything 
     else you have seen in this article. If you walk through the 
     code line by line, you can first start with something you 
     have seen before in "ps" variable. The next two lines of 
     code-use generator expressions to handle the file object 
     ps.stdout and to parse it and search it for the lines you 
     are looking for. If you cut and pasted these lines of code 
     into an interactive Python shell, you would see that outline 
     and flag are both generator objects if you printed them. 
     Generator objects come with a next method attached to them, 
     and as such, allow you "pipeline" actions together.  The outline line strips the newline character from one 
     row, and then passes that one row down to the next generator 
     expression, which searches for a regular expression match, 
     in each row, one at a time, and then passes the output to 
     flag. This type of compact workflow can be an alternative to 
     the object-oriented programming style, and it is quite 
     interesting. There are drawbacks to this style as well, 
     though, as the conciseness of the code can lead to errors 
     that are difficult to debug unless each line of code is 
     executed independantly. Functional programming also stretchs 
     the brain, as it makes you think of solving problems by 
     chaining solutions together. This is quite different from 
     either procedural or object-oriented styles.  Summary This article was somewhat experimental, as it went from 
     Bash and PHP, to procedural, object-oriented, and finally 
     functional Python using the same basic code. I hope it 
     illustrated that Python is a very flexible and powerful 
     language that developers in other programming languages can 
     also learn to appreciate. As Python continues to grow in 
     popularity, it will become more important for other 
     developers to learn about in addition to their language of 
     choice.  Two of the largest recent growth areas of Python are Web 
     development and systems administration. In terms of Web 
     development, developers in PHP may soon have to make weekly 
     choices about which project makes more sense in Python, and 
     which project makes more sense in PHP. For systems 
     administrators, Bash and Perl scripters,are often being 
     asked to look at doing some of their projects in Python. 
     Partly this is out of choice, and partly because many 
     vendors are offering Python API's for their products. Having 
     a bit of Python in your toolkit can never hurt anyone. | 
   
No comments:
Post a Comment