Rapid Development Using Python
SkipWare is a series of extensions to standard Internet transport protocols that, when installed on a network device, maximizes link utilization and reliability in a stressed transmission environment. It has been developed collaboratively between GST and Comtech; GST provides the software and Comtech provides the hardware. Although SkipWare is an implementation of SCPS (space communication protocol standards), a graphical user interface is required to specify configuration settings that impact overall system performance. Given that the product forwards IP (Internet) traffic, a Web-based client was the most feasible interface for our customers.
Because the interface requirements were soft and time was short, we approached the interface with a rapid development mentality focused on constant customer feedback. A language decision had to be made, and we had the following choices (primarily due to our experience): C, PHP, Perl, Java and Python. Our selection was based upon the following criteria:
We planned to prototype on a remote device and anticipated numerous changes. We needed a language that was designed with change in mind.
We wanted to avoid the added step of code compilation in order to minimize the overhead associated with a change. An interpreted language seemed pragmatic.
We wanted a language with good introspection capability.
We needed to do a lot of string manipulation and file I/O. Whatever language we chose had to excel in both of these areas.
Disk space was a concern in our decision but not a driving force. The hardware provided to us was an 800MHz processor with 64MB of RAM and a 32MB root filesystem. Of those characteristics, the disk space was clearly the most precious. Because of the high clock speed and availability of RAM, interface performance was not a driving force.
Our development environment focused on customer interaction. Because time was short, the traditional approach—formal requirements gathering followed by independent development and testing—was not practical. We anticipated making changes frequently on a remote device, with a customer representative viewing the interface and providing instant feedback.
Python allowed us to achieve this environment primarily because it is easy to use interactively. Prototyping through an interactive interpreter is an effective mechanism for exploring different approaches to solving a problem. With a customer representative providing real-time feedback, we needed a language with which we could prototype a function instantly through an interactive interpreter and then copy the working function to the remote device for approval. Perl did not provide an interactive interpreter that enabled this behavior.
Our rapid development environment meant that changes had to be visible immediately to both the developer and the customer representative. Coding sessions frequently would involve work on a remote device during which time changes would be made and feedback would be gathered. Use of a compiled language inhibited our ability to prototype on a remote device, because it required maintaining a build environment. In the scenario where we changed one or two lines of code on the remote device during an interactive feedback session, we did not want to have to wait for compilation before gathering the next round of feedback. IDEs were not feasible as a solution to our problem, because much of our development occurred on the remote device.
We wanted to dispatch Web requests to code in a very direct fashion. An architecture based on a central controller that used introspection to determine where to route requests seemed a clean and simple choice. Because our controller-based architecture was abstract and generic, we had concerns about safety. Python protects the programmer from buffer overflows and has excellent reflection capabilities that can be explored at run-time. Both characteristics satisfied our needs for introspection and safety. Moreover, an inspectable run-time is nearly equivalent to running an application under a debugger while making changes. This characteristic appealed to us.
All user input arrived in string format. All output was stored in files or returned to the user in HTML format. Both of these concepts are well supported in Python. Iterating over the content of a file takes two lines of Python. In comparison, Java requires five or five instantiations followed by two or three lines to read. Once read into memory, content must be tokenized before iteration. While iterating, casting is required. The Java approach exemplifies the importance of selecting the appropriate language for your development environment—it would not have been a good language choice for the tasks we were to perform, primarily string and file manipulation. With all of its built-in types and excellent string and file manipulation, Python provided us with the tools we needed to accomplish our tasks quickly and easily (more on this later).
Narrowing down the language choices was not particularly difficult. C requires compilation, is easy to exploit through buffer overflows (and thus poses a security risk) and does not handle strings and file I/O particularly well. PHP also was dismissed because we were not accessing a database, hence many of the features of the language would not be used. Java is too bloated, requires compilation and has horrible file I/O and string manipulation.
Python survived our language elimination process because it satisfied all of our requirements. In addition to satisfying our requirements, we gravitated towards Python because of its modularity and support for objects. This allows structured code growth and accommodates change over time. We also welcomed the safety (over C) that the interpreter provides. Above all else, we selected Python as our language because it supported rapid development and our specific requirements extremely well; no other language came close in comparison. Although other languages fulfilled a subset of our requirements, no language we considered fulfilled all of the requirements better than Python.
Our hardware vendor provided a 32MB solid state device to use as a root filesystem. With the kernel, operating system and the SkipWare binaries occupying approximately 16MB, we had to be crafty in determining our runtime environment. We initially looked to Boa as a lightweight Web server, but we were concerned about our ability to scale it down to fit within 10MB. We feared we would have to convert our Python code to C for deployment on the production systems, and none of us looked forward to the port. Nonetheless, the quick progress and huge strides we made as a result of using Python convinced us to move forward with the language until such time that we were forced to recode.
We looked to freeze as an option to avoid a re-write. Freeze seemed to offer us the best of both worlds: it allowed us to develop our interface in Python, link it statically against the interpreter and produce an executable binary we could load onto the production hardware. Unfortunately, scope creep forced us to eliminate freeze as a viable option. During the development process, unforeseen requirements were created at the last minute. In the end, we ended up building an interface well beyond the original set of requirements. Our architecture supported the unanticipated creep, but class organization became difficult.
For simplicity, our architecture initially consisted of one module containing several classes (views). Over time, we found that separating the classes into modules enabled a pluggable overall architecture. Because it supports import-on-demand and module reloading through introspection, Python allowed us to change logic within external modules and have the changes be accessible immediately by the central controller. Overall system architecture became significantly cleaner (and simpler) when we shifted to using multiple modules. Unfortunately, this meant that freeze was no longer a viable option, because we would have to statically link every individual module and that would require significant disk space.
As described above, we witnessed scope creep during development. What started out as a simple interface to control IP Address, Subnet Mask, Routes and Round Trip Time turned into a full fledged dynamic configuration wizard. Moreover, we found ourselves building an Auto-Update feature as well as a management interface that displayed uptime, traffic statistics and intelligent displays of routes and acceleration. All of this complex functionality would have to be ported to another language at the last minute.
Recognizing the difficulty of a last-minute port, we decided to load the Python interpreter onto the production hardware. This was a difficult and risky decision to make because we only had 16MB of free space, and the interpreter consumed a fairly large percentage of the remaining disk space. During the prototyping process, however, we often remarked to one another, "this code is not going to port well to language X". We worried about the number of times we uttered that phrase and dreaded the upcoming port. The unknown risks associated with porting to another language compelled us to continue using Python under the assumption that it would be available on the production hardware.
In retrospect, installing Python turned out to be a wise investment of time. Even though the interpreter and libraries consumed over 5MB of valuable disk space, the flexibility and robustness of the language led to numerous speed-ups during development. The string manipulation and file I/O commands became trivial operations. Instead of wasting time fighting with the language, we spent time fulfilling customer requirements. Our rapid development environment blazed to life fueled by Python.
We found that looking at problems from a Pythonic standpoint often led to simple and elegant solutions that addressed both functionality and portability. In one instance we needed to compare two configuration files for equality. Our first solution was to use the diff command. Due to time and space limitations, though, we were unable to load diff onto the production hardware. We looked to Python instead and implemented a solution within 10 minutes: create two dictionaries (one from each file) and compare them using the != operator. The Pythonic approach to our problem was pragmatic and easy to implement, and this trend was common throughout the development process.
With Python available on the production hardware, our capabilities have grown vertically and horizontally. Our interface continues to evolve through re-factoring, and we also have written a guardian to perform sanity checks on routes, arp entries and network connectivity. It does so by harvesting information from the proc filesystem, but it also includes non-proc oriented functionality that prevents software theft. Implementation of the nanny within another language (presumably C) would require significantly more code and would be more difficult to maintain.
The appeal of Python goes well beyond its functionality and usability. The user community and external module support is superb. FTP transfers under Python are extremely easy with the ftplib module. Simple checksums are painless thanks to the p2 module. We have used both of the above libraries within our application and have found them to be considerably more intuitive than their Java or C counterparts. After our experience with both syntax and external libraries, many of us frequently ask why other languages do it "any other way".
Python's built-in types immediately come to mind. File access and iteration is extremely easy:
for line in open('file.txt').read().split('\n'):
for word in line.split():
words[word] = words.get(word, 0) + 1
The above demonstrates iteration over a file followed by iteration of individual words per line. It concludes by showing how a bucket in a dictionary can be referenced explicitly by brackets or through a method. Conversion between built-in types also is easy. In the following example, tuples are used to construct a dictionary representing the values in a configuration file:
for line in open('settings.conf').read().split('\n'):
if line.strip() == '': continue
(name, value) = line.split(delimiter)
props[name] = value
The above illustrates how a list can automatically be converted to a tuple. The variables in the tuple can then be used as inputs to other variables. After executing this code, the developer will have a dictionary where the key is the name configuration setting and the bucket contains the value of the configuration setting.
We have also marveled at the simplicity of documenting our Python modules to conform to pydoc specifications. Initially we were apprehensive about embedding our method documentation below the method signature. This seemed counter-intuitive to us—some of us came from a Java background—but after doing it a couple of times we became fanatical about it. Unlike Java, where comments are above the method signature, it is extremely easy to locate a method within a module because the function signature (what you are searching for) stands out and is not concealed by documentation above and code below. Here is an example of a JavaDoc comment and method:
/**
* Returns true if a equals b, false otherwise
* /
public boolean equals(Object a, Object b) {
if (a instanceof b.getClass()) {
...
}
}
If you are a Java developer looking for the equals method that returns boolean and accepts two objects, you are going to have to use an IDE or visually scan for the signature. It will not be easy to find, because the one line you are looking for is surrounded by a significant amount of noise—javadoc above and code below. PyDoc (below), keeps the noise ratio low by embedding the documentation below the function declaration:
def equals(a, b):
'''returns true if a equals b, false otherwise'''
if type(a) == type(b):
...
Looking for the equals method under Python is significantly easier than it is in Java.
Our interest in Python developed from a prototyping standpoint, but we quickly adopted it as the language of choice for our management interface as well as for ad-hoc prototyping of future concepts. Numerous developers have come up to speed with Python quickly, and they have expressed pleasant surprise at the simplicity of the language and how quickly they are able to fulfill functional requirements with it. Moreover, Python's robust string and file handling (as well as its built-in types) make it the language of choice for our user interface. We're quite excited about the potential that Python has given us.
In the future, Python will be looked upon favorably by both the development staff and the company at large, because it has allowed us to respond to customer requests in a fast-paced, rapid development environment. The built-in types, lack of compilation, interactive interpreter and usability of Python have made it the language of choice of many and have raised the bar of excellence when evaluating other languages.











Comments
Re: Rapid Development Using Python
what about Ruby as to your criteria?
Re: Rapid Development Using Python
ruby is crude
ruby is perlish
Re: Rapid Development Using Python
I agree. But Ruby is powerful and does support the other objectives.
Personally, I'd still take Python. Python is perfect.
Re: Rapid Development Using Python
I love python, and use it regularly, but this column inadvertantly showed a python gotcha. Since the syntax is dependent on indentation, you have to be very careful when cutting and pasting.
def equals(a, b):
'''returns true if a equals b, false otherwise'''
if type(a) == type(b):
...
The statements after the def should be indented. This wouldn't have mattered in another language that used braces to denote blocks.
Re: Rapid Development Using Python
You barely mention Perl. Was your decision not to use Perl based on a perceived lack of an interactive interpreter (note: an interactive interpreter called ptksh is part of the standard Perl/Tk installation)? If not I would like to know what caused you to select Python vs. Perl. We are in the process of making a similar decision.
Re: Rapid Development Using Python
If not I would like to know what caused you to select Python vs. Perl. We are in the process of making a similar decision.
Just choose Python. You will save the trouble of porting your Perl program to Python afterwards, when you learn that Perl just doesn't scale anymore.
Most choices of Perl for a solution to a particular problem can be attributed to historical burden or ignorance. If you have no historical burden of huge perl codebase, do the right thing.
Re: Rapid Development Using Python
The quick and simple answer is that Perl is a little more of a kitchen sink language having a number of possible ways to do the same thing. While this can be really great for the skilled Perl Wizard it does make larger team projects difficult as each coder may have favorite constructs that some other team members have never used.
Python is more of a carefully constucted language with (mostly) one approach for a given piece of code.
Some examples of Perl I have seen are more terse then the equivelent Python code. But to me the Python also looked like it was easier to maintain. Perl is great for small hacks and has been used successfully on large projects as has Python. I have chosen to use Python for both cases because I seem to be able to maintain the code I write more easily.
Re: Rapid Development Using Python
It's simple - python code is maintainable and can be object
oriented, perl code is not maintainable and cannot (within
the realm of reasonable people) be made object oriented.
Re: Rapid Development Using Python
That is simply a lie.
Re: Rapid Development Using Python
Opposing to popular opinion, I'm not thinking that object-orientation has something to do with emulating a fraction of key-concepts inherent to object-orientation with obscure language features. No, I think it's something a language supports, and if it does it, it better damn well does it good and consistently. In the face of this, Perl has no object-orientation ( and neither has C++ )
Re: Rapid Development Using Python
Perl code can be made perfectly readable. Granted, it may be easier to be made unreadable, but that is largely the programmer's fault. I'm sure one could write unreable python code too.
As for no object oriented perl, this is simply untrue. There are many books published about the subject, and I will not go on defending against such a ridiculous accusation.
Re: Rapid Development Using Python
It's very difficult to write unreadable Python code. You really have to put a lot of effort to do it.
OTOH, I used to be a Perl coder. Perl makes it real easy to write code with is difficult to read. You have to put a lot of effort into reading someone else's Perl code.
Re: Rapid Development Using Python
This is undeniable. After all, one of Perl's mottos is "Perl makes easy things easy, and hard things possible." This does not automatically make all Perl code unreadable, however. I will not defend every piece of code ever written, because I'm sure there is a substantial portion of horrible, unreadable Perl code in the world. I just can't stand the common conception that Perl is inherently unreable. It is simply not true. All it takes is one cleanly written Perl program to prove the case, and there are plenty of those. Check out some good CPAN modules sometime, if you don't believe me.
I fear we're getting into muddy waters, though. The flames of war are a-brewin' and this post probably doesn't help. With that, I bid you adieu, and will post no further.
Re: Rapid Development Using Python
It's actualy easy to proove that perl is inherently ugly and unmaintainable.
There's surely nice perl code as there is nice python code.
-Write some 50 lines perl code, out of your head, do not comment or refactor it. Do the same in python ( you may use a few more lines ). Let it rest for a week. Look at your code afterwards, which one is the one you can still read?
-There's ugly python and ugly perl. Only a python author has to go to extreme lengths in order to make it impossible to read his code like an open book. Perl is somewhat the anathema to this.
Re: Rapid Development Using Python
In the article you use this:
for line in open('file.txt').read().split('
'):
Why not this:
for line in open('file.txt').readlines():
Or using the iterator:
for line in open('file.txt'):
Re: Rapid Development Using Python
for line in open('file.txt').read().split('
'):
strips the line endings, readlines() doesn't.
for line in open('file.txt').
for line in open('file.txt').read().splitlines():
will get of the newlines
Re: Rapid Development Using Python
To be fair, it is not that C is easy to exploit, but bad C code. One could argue that the C library itself is bad code, because the exploitable problems are due to coding errors using the library that are relatively easy to make. These problems could be avoided with new libraries; Dan Bernstein wrote his own string-handling libraries that are immune from buffer overflows when writing qmail. Additionally, Python itself is written in C, and it appears to be safe from buffer overflows, because it uses the C library correctly.
The C library API is also about 30 years old now. There are a lot of things that could be done better, but there is also a lot of inertia that's not likely to be overcome.
Oh, and Python rocks.
Post new comment