Python has a bunch of built-in functions, and it also comes with a whole list of modules and classes that can be used to construct your programs. I introduce programming in Python here. In most cases, you will start by building functions, and those functions may, or may not, be combined into modules at some point. We’ll take it a step at a time.
Editor's Note: This article is excerpted from chapter 9 of Open Source Starter Guide for IBM i Developers, by Pete Helgren.
For RPG programmers, at least the more “modern” ones (whoever they are), monolithic code is an anathema. It’s hard to read, hard to debug, and hard to share. And if you do decide to share or copy it, then if you tweak the “mother” of the others, you need to update the others as well. Most RPG and COBOL programmers I know abandoned that technique about the time that Prince changed his name to ... well, whatever it was changed to. What replaced the monolithic coding style? Well, subroutines were the first step, and then subprocedures, and then those were gathered into service programs, which made for nice, neat bundles of goodness in the ILE world. Leveraging those service programs across multiple applications made for nice, easy-to-maintain code. I am not going to make you relive that process; we’ll just start at the service program equivalent: functions with modules.
Modules are just groups of functions gathered into a source file that can be included in other source files. There isn’t much magic to modules; you just need to “import” them when you need the functions within them, kind of like using a BNDDIR in your H-specs to reference a procedure in a service module.
Functions come in all shapes and sizes. Functions begin with a def and finish with a return (in most cases). Between the def and return would be 1) a function name followed by parentheses, 2) input parameters to go within the parentheses if needed, 3) a colon (:), 4) an optional string that describes what the function is (docstring), 5) a block of code, and 6) a return statement that could return a value to the caller or can just be left blank.
A prototype would be like this:
Let’s continue the example by going back to some of the code above and “functionalizing” it. Remember that we tiptoed through the tuples like so:
We can create a function that would handle just that bit of code:
The output is just the same as before:
I need to mention something that is unique to Python, and that is indentation matters. This will probably come back to you time and time again, since I often get it wrong myself. It doesn’t matter if you indent using tabs or spaces; just be consistent and make sure your indentations are properly relative to each other. This is where a good text editor that can syntax-check your Python will be very helpful.
If you took the function and stuffed it inside a file called myMod.py, you could then use that function by issuing the following:
You’d get the same output with the added advantage that you can use the tttt function in other programs with just one line of code: import myMod.
That is about all the complexity that a module has: it is a file containing one or more functions, and usually those functions are in the module because the functionality they bring to the party is broad-based. Outside of easing maintenance by locating similar functions in a single file, the utility of a module is that typically the functions they encapsulate can be used in multiple programs.
I didn’t mention scope, but since the parameters are passed by reference, you need to remember that variables declared local to the function are private to that function. For example, suppose you have a function like this:
Part of me expects that the phrase variable would be changed because it was changed inside the function. But I’d be disappointed. Here is what we get:
Outside the function
Inside the function
Outside the function
We get that result because the phrase variable declared inside the function is “local” to the function, even though it may be the same name as the parameter passed in.
When declared as above, the parameters are required. If we ran the same code as above but, instead of calling sayit(phrase), we forgot and called sayit(), we would see this:
So, if you need three parameters, then you have to have three parameters passed to it, and they all need to be in the correct order that the function expects them in.
Another possible way to pass parameters would be to use keywords when the parameters are passed to the function. Suppose you had a function like this:
The output is:
Name: Joe Zablotnik
Address: 123 Main Street
Name: Joe Zablotnik
Address: 123 Main Street
Remove the keywords, and you’ll get this:
Name: Joe Zablotnik
Address: 123 Main Street
Name: 123 Main Street
Address: Joe Zablotnik
If you are not sure how many parameters you’ll be passing, you can always allow any number of arguments to be passed in, like so:
The function doesn’t really care, but we need to at least figure out how many parms were passed in because we need to deal with them in the code. That means we evaluate how many were passed in and handle them individually. Note also that the passed parameters are tuples.
Perhaps a clearer way to handle a variable number of variables would be to use keyword variables so we know what we are looking for, and when we find it, we use it, like this:
So far, so good, and no convolutions like “blocks” in Ruby.
Let’s take it a step at a time.
In the example above, we are declaring a “regular” function (increment_it), but we return the value to a lambda, along with a parameter, and the lambda function then returns a value. increment_it as a function takes a single value and returns. But that return then passes it to a lambda function, which has one parameter that takes the return value and adds whatever was passed to it to the value passed to increment_it. So that is a killer first line. The second and third lines could appear equally cryptic, but remember that variables in Python are dynamic and can hold any object, and in this case fii and gii are lambda functions themselves that will be initialized with the increments of 2 and 6, respectively. Let me say that again: fii and gii are variables holding lambda functions with slightly different values for increment_it. The very last line shows how the whole thing is invoked, passing 22 as the increment to increment_it, and 33 will be passed to the lambda function. Here are the results:
Pretty nice. Lambda become helpful in just these kinds of situations where an output from a named function needs to be further processed without the overhead of writing a traditional function. You probably won’t be writing a lambda right out of the blocks, but you are bound to run into them at some point. Might as well familiarize you with them now rather than have you run out of the room screaming hysterically later.
Classes in Python
Before we move on to a more complete code example, we have to embrace the object- oriented nature of Python by reviewing classes. As mentioned in other chapters of this book, a class is really a template of how an object should look and act. It defines the object’s properties, data, and functions and provides a method for initializing and creating objects based on the class.
A simple example is this:
This is simple and contrived, but it demonstrates a few things. First, you can have an initialization routine that takes the initial value and stores it in an instance variable in the object when created. Second, you have a class variable that will be shared across all instances of the class (that is the Talker.talkerCount as opposed to using something like self.talkerCount). Third, Klingon is the universal language spoken by all Talkers.
The output would be:
If you included a module for speaking Klingon, klingon.py, you could just include the module in your Talker class so that all talkers would speak Klingon. Take a look.
Our klingon.py has this:
And our Talker class now looks like this:
Invoke the code:
File Access in Python
How about a simple example with a little more oomph and less mystery? File access is something you’ll probably be doing on a regular basis, and Python has no problems with file access. Let’s build a file and read through it. Here is the whole tamale:
Don’t ya just love it? With very little code, you can get a boatload of work done.
Most of the magic is in that first line: import csv. That module provides quite a bit of functionality, and we get all of it on that one line. The first line is a comment (the # sign denotes comments). The second line may look a little foreign to someone who hasn’t spent time in the object-oriented world, but there is real beauty in the second line. We have a variable that we are calling writer (good name, since it will be writing our data), and we assign that variable the result of a call to the writer function in the csv module. That writer takes an object returned from the open function, and the open function, which is a built-in function in Python, so it isn’t qualified by a csv, takes a filename, a mode string, which identifies how the file should be created and used, and a third optional parameter that determines how the file I/O will be buffered. Open will return a handle to the file, which the writer object will reference to write to the file. I say “write” because the string 'wb' that was passed to open means that the file will be created if it doesn’t exist; it will be open for writing, and it will contain binary data 'b'. We won’t deep dive into file I/O, but there are plenty of options and methods at your disposal.
So, out of the wonderful second line of code, we get an object called writer (we could have called it anything). That object has functions that can be used to write to the file. The function we leverage immediately is writerows. Now, we could have used writerow (singular) and written the rows one at a time, but writerows allows us to push it all in in “one swell foop.” Note that the parameter passed in is a sequence (array) of tuple objects. Yeah, you know this stuff!
Reading the file is just as easy. Again, the csv module has a reader function that returns a reader object we called inventory. The csv.reader function is passed a file object, which is returned from the built-in file open function that is passed the filename and the “mode,” which is the string 'rb', which means the file is opened read-only for binary data.
The interesting part here is the status_labels variable, which has a dictionary object in it with three name-value pairs. Jump down a couple of lines to where the variable is actually used. You’ll see that the status variable is populated by three functions rather tersely combined on the line. The cmp function compares two values and returns one of three results: It returns -1 if the left comparator is less than the right; it returns 0 if they are equal; and it returns 1 if the left comparator is greater than the right. We cast the qty parameter to an int so that the comparison is performed “apples to apples” (remember that our variables can be dynamically typed). Our comparison is to the integer 500, so our quantities in each inventory record are compared to 500 each time. The cool part is that the return value (-1, 0, or 1) is then used to retrieve a value from our status_labels dictionary—for example, status_labels[ ]. And that value is used as the value for the status variable. Nice!