An Introduction to Python on IBM i, Part 3
May 18, 2011 Garry Taylor
Note: The code accompanying this article is available for download here. Guido Van Rossum, creator of Python, once said, “Python is an experiment in how much freedom programmers need. Too much freedom and nobody can read another’s code; too little and expressiveness is endangered.” In this third part of our introduction to Python on our faithful IBM i, you’ll learn how to use the expressiveness of Python while still keeping your code readable. This will be achieved by creating classes to represent our data in a clean and manageable fashion. You have likely heard of classes and object from reading about Java, but rest assured, Python makes object orientation an order of magnitude easier than Java. Before hitting the code, let’s clarify some terms. You can consider a “class” in Python (or just about any other language) as a specification for an object. Objects in Python are similar to the concept of objects in OS/400 itself, but not truly synonymous. Perhaps you can loosely describe classes as being like a SQL table schema, and objects as the rows of data that conform to that schema. There are many aspects of object-oriented (OO) programming that define its core strengths, and I will touch on two of them here: abstraction and hierarchy. 1. Abstraction The abstraction of data, function, and implementation details is a major tenant of OO programming. An example of this would be the following: Imagine a company with many subsidiaries, each with many employees. Perhaps over time, each subsidiary developed its own way of logging sales. Maybe one subsidiary decided to keep a simple database on their AS/400, while another decided just to save a number down to a stream file. Unfortunately, the head of this sprawling conglomerate now wants to see all sales reports on a Web dashboard on his desktop PC. Rather than attempt to explain the complexities of this endeavour to him, we can simplify it by defining a class representing each subsidiary. Each class will define methods to access the data, in a formal and predictable manner. However, due to the differences in how each subsidiary operates, the methods will use entirely different techniques to assemble the results. To the Web programmer though, who uses your objects, they will appear identical. Their complex differences will “abstracted” away. 2. Hierarchy Another great strength of OO programming is the ability to easily implement a hierarchy. This allows a large program to be broken down into manageable sections. Like a chain of command, the hierarchy allows us to implement a program that closes resembles the real world. Almost anything can be broken down into a basic hierarchy, which can be illustrated something like a family tree. We would have a root object (generally only one of these) that we call a “singleton,” which is a class that can only be instantiated once into an object. This singleton object will contain references to other objects, which in turn will reference others. The strength of this programming technique is clear; along with abstraction, extremely complex problems are split into simpler, smaller problems. Simpler classes can be assigned to less experienced, single programmers, the complex ones to more experienced groups. If each programmer/group knows clearly how to name his methods, and which parameters they accept, then the whole system will fit together like the blocks in a pyramid. Using Hierarchy and Abstraction To Build a Program For the above problem of implementing a system to collate sales data for our imaginary conglomerate, we need to implement a very simple hierarchy. There are many right and wrong ways to design a hierarchy. Break the problem down too much and you’re left with many classes to maintain, with perhaps too much abstraction. For example to abstract the concept of an employee is likely a good idea, but to abstract his or her sex is possibly not; a simple “M” or “F” in the employee class will do fine. If you don’t break the problem down enough, you risk losing the advantages of OO programming, and end up with one monolithic class that nobody quite understands. To get us started, I will outline a simple hierarchy. In the interests of brevity, it is intentionally basic, and you may feel that I have omitted some classes. However, once you have learned the basics, you can abstract the problem further if you want. This diagram illustrates the hierarchy we’ll be implementing in Python. You could easily, and perhaps correctly argue that this hierarchy is missing parts. Maybe under subsidiary, should be Department; maybe under each SalesRecord, should be lots of individual sale objects. It is left as an exercise for the reader if he or she wants to further expand on this hierarchy. Enough Talk OK, that’s enough theory for now. Time to start writing code! Let’s start at the top and create a Python class for our conglomerate: class Conglomerate: def subsidiaries(self): return self._subsidiaries Yes, that’s it. In the above snippet of code, we have defined a class called conglomerate, and also defined a method inside it called subsidiaries. The Subsidiaries method returns an array (list) of the subsidiaries that are part of the conglomerate. However, this list will not be simple list of company names, but an array of subsidiaryv objects, which we’ll define below: class Subsidiary: def __init__(self, name): self._name = name self._employees = [] def name(self): return self._name def employees(self): return self._employees def addEmployee(self,employee): self._employees.append(employee) def numberOfEmployees(self): return len(self.employees()) As you can see, this class is much the same as our first, except for that funny looking __init__ bit. The __init__ method is simply a method that is called the moment the class is instantiated into an object. It has two parameters. One is self, which is sent to all methods. You don’t need to worry about passing it to the method but you can use it inside the method, as we’ll learn a little later. The second parameter is name, which is one I’ve invented for the name of the subsidiary, for example: Ralston Engineering PLC. Also for the subsidiary class, we have the employees method, which will return a list of the employees working for this subsidiary. And of course, it will return a list of employee objects, not a simple list of names. For this class, we’ve also added a couple of simple methods to easily get the number of employees in the subsidiary, and a method to easily add an employee. So let’s get on with business and define an employee class: class Employee: def __init__(self, name,reportingstyle): self._name = name self._reportingstyle = reportingstyle self._salesRecord = None def formattedSalesTotal(self): return "$%F" % self.salesTotal() def salesTotal(self): return 0 def name(self): return self._name You can see that we have a similar __init__ method to the subsidiary class. The method has name parameter as, of course, each employee has a name, too. There is a reportingstyle parameter, that we’ll use to decide which type of reporting each employee uses. We’ve also got a method to return a sales total, but presently this just returns zero, as we’ve not worked out how to calculate this yet. Partnering this method, we’ve got formattedSalesTotal, which will return the sales total in a pleasant, readable format. We can see here use of the “self” keyword, which is used to refer to the current object, i.e., itself. It is analogous to this in Java or C#, and you will see it more than any other keyword in OO programming in Python. We’ve also put in a method to return the name of the employee, this can be called by any other object or method to get the name of the employee. We call this an “accessor” method as all it really does is access a property of the object. The benefit of using an accessor, rather than simply try to access the property directly, is that at a later date we could think “Hey, these names should really be in upper case,” and then all you’d need to do would be to add the code to the name accessor, rather than everywhere that used the _name property. Making It All Work Together So now we need to breathe some life into these classes and get them working together. First let’s get the conglomerate having some subsidiaries inside it. We’ll add an __init__ method to conglomerate and create some subsidiary objects in there. def __init__(self): self._subsidiaries = [] self._subsidiaries.append(Subsidiary("Ralston Engineering")) self._subsidiaries.append(Subsidiary("Mickey's Car Parts")) self._subsidiaries.append(Subsidiary("Slater Auto")) Now our conglomerate has three subsidiary objects in its self._subsidiaries array. Let’s add employees to each subsidiary object, at the end of your file: con = Conglomerate() con.subsidiaries()[0].addEmployee(Employee("Don Draper","DB2")) con.subsidiaries()[0].addEmployee(Employee("Tony Soprano","DB2")) con.subsidiaries()[0].addEmployee(Employee("Jack Bauer","IFS")) con.subsidiaries()[1].addEmployee(Employee("George Costanza","DB2")) con.subsidiaries()[1].addEmployee(Employee("Jerry Seinfeld","DB2")) con.subsidiaries()[1].addEmployee(Employee("Elaine Bennett","IFS")) con.subsidiaries()[2].addEmployee(Employee("Bill Adama","DB2")) con.subsidiaries()[2].addEmployee(Employee("Saul Tigh","DB2")) con.subsidiaries()[2].addEmployee(Employee("Kara Thrace","IFS")) Starting at the first line above, we instantiate a Conglomerate object. Using its own __init__ method, the Conglomerate will add the three subsidiaries without us asking it to. Then we add three employees to each Subsidiary. The Employees are instantiated with two arguments, a name, and reportingstyle. The reportingstyle will be used later when working out sales totals. Appending to the end of the file, we can now enter some code to print out our conglomerate and all its descendants to a spool file: for subsid in con.subsidiaries(): print subsid.name() print "--------EMPLOYEES ("+str(subsid.numberOfEmployees())+") ----------" i = 0 for employee in subsid.employees(): i += 1 print str(i)+") NAME: "+employee.name() + " - SALES TOTAL: "+employee.formattedSalesTotal() print "---------------------------" Now we can run our Python file: PYTHON25/PYTHON PROGRAM('/home/garry/conglomerate/code.py') And with WRKSPLF, observe the results: But we have a problem. All the sales totals are “0”, and now we need to get our employee objects using the reportingstyle parameter to get the sales totals for each employee. Inheritance Unfortunately, this has nothing to do with a receiving a lump sum from a distant relative. However, it does allow classes to inherit abilities from other classes. An example might be that we have two classes to represent cars DodgeViper and ToyotaPrius, they would both inherit from the class Car, and share methods such as numberOfWheels, but have different methods for topSpeed or sellingPrice. In our case, we’re going to have a base class called SalesRecord and have two classes that inherit from it called SalesRecord_DB2 and SalesRecord_IFS. Let’s see some code: class SalesRecord: def __init__(self): self._employee = None def setEmployee(self,employee): self._employee = employee def employee(self): return self._employee def totalMinusCommission(self): return self.total() * 0.8 def total(self): print "Should not be called" pass Here we have our basic SalesRecord class, the total method should not ever be called, but I’ve put it in for the sake of completeness, and also to aid in debugging by having it print out an error message. The total method, instead, will be implemented by the two classes that inherit this one. The method totalMinusCommission will be shared by the two inheriting classes, as commission does not change depending on reporting style. I have also added methods to set and get the Employee class associated with this SalesRecord. Again, this does not change based on reporting style, so we only need to implement it once in SalesRecord. Let’s now implement our two classes that will inherit from SalesRecord: class SalesRecord_DB2(SalesRecord): def total(self): db2connection = db2.connect() db2cursor = db2connection.cursor() sql = 'SELECT salestotal from conglom.sales WHERE name = ''+self.employee().name()+''' #print sql db2cursor.execute(sql) row = db2cursor.fetchone() salestotal = row[0] db2cursor.close() db2connection.close() return float(salestotal) The next step is: class SalesRecord_IFS(SalesRecord): def total(self): filename = "/home/garry/conglom/"+self.employee().name() try: fh = open(filename,"r") salestotal = fh.read() fh.close() return float(salestotal) except: print "COULD NOT READ '"+filename+"'" return 0 As you can see, these two classes only need to implement one method, which is their technique for acquiring the sales total for that employee. We can see that each class uses methods that initially do not seem to exist, like self.employee. That is simply because the employee method is implemented just once in the SalesRecord class. They both inherit the abilities of SalesRecord by specifying it in the definition of the class. The SQL code for created example DB2 data is at the end of the full code listing for this article, alongside QSH statements to create the IFS files. Now the last thing to do is to modify the employee class to use the correct SalesRecord implementation: We just need to modify the __init__ method: def __init__(self, name,reportingstyle): self._name = name self._reportingstyle = reportingstyle self._salesRecord = None if self._reportingstyle == "DB2": self._salesRecord = SalesRecord_DB2() elif self._reportingstyle == "IFS": self._salesRecord = SalesRecord_IFS() self._salesRecord.setEmployee(self) And also the salesTotal method: def salesTotal(self): return self._salesRecord.total() Now the salesTotal method uses the SalesRecord object to retrieve its total. The Employee, Subsidiary, and Conglomerate objects are indifferent to the techniques used due to us abstracting away the difference with two different classes. To the outside world, these different objects will appear identical. In the future, you could create a SalesRecord to get results from SalesForce.com, or maybe a MySQL database. In any case, so long as you implement the total method to return the same style of data, the whole system will keep running like nothing changed. The code included with the article is deliberately simplified, such as the use of CHAR fields in the database rather than Decimal. Also, I look up the employee from their name, but good practice would have us look up using a unique employee number, or key. It is left to the reader to modify and improve all the classes, maybe even turning them into a real world solution for a real world problem. Garry Taylor is a professional software developer, working with IBM i, Solaris, and Mac. Based in London, Garry is a co-founder of The Escapers, and developer for Kisco Information System. Send your questions or comments for Garry to Ted Holt via the IT Jungle Contact page. RELATED STORIES An Introduction to Python on IBM i, Part 2 An Introduction to Python on IBM i, Part 1
|