Hi! I’m Liz from Athenageek. Tarah and I are writing guest posts in each other’s blogs on the scripting languages we love.
I must make a confession before I continue with this post – I adore Python. It’s made me a better coder. I think about problems differently than I used to, I’m better at breaking them down into tiny components that are easy to code. My coding in other languages improved drastically as a direct result of learning Python – and using it in a Pythonic manner.
I used to use Bash for scripting. That’s what Bash is for, right? I use a Bash shell, so it only made sense.
One day, I was processing some xml scripts. I had multiple xml files with the same root node, and needed to put them all together into one big xml file. Frustrated at some sticking point or other, I decided to see if I could do it with Python. Oh, my goodness, it was SO EASY.
Before long, I
had a string of utility files to handle common tasks that I was always scripting.
Reusability
When scripting, we often have tasks that we do over and over, but with slight changes. Rather than having a base set of methods that you copy out of and then change for each new usage, you can create utility files of classes and methods to use. Add methods to classes to handle new tasks, or allow new variables to be passed in for different usages. With Python’s named variables, you can change a method without effecting or needing to change other code that calls it.
One of the base tenants of Python is DRY (don’t repeat yourself). If you find yourself writing the same code more than once, put it in a place where it can be reused, like a method in a class or a function in a utility file.
Building up a libarary of these types of files is a great way to expand your toolbox. The longer you do it, the easier tasks become, because you’ve already done the work. Lots of scripting projects are,
“Just like the one you did for x, but with this minor change”.
Testing
Python the community is heavily in favor of testing. One reason this is helpful for scripting is the case I mentioned above, when you change a method to handle some new functionality that you need. How do you make sure this new fuctionality doesn’t break old code? You test it. Doctesting and unit testing are built in to Python, no hunting around for which framework is best, balancing framework versions with script versions, or figuring out how to import your framework on a different operating system.
Doctesting
I love doctesting. In one fell swoop, you are showing how to use code, documenting that code, and proving that it works as expected.
Doctesting is very easy to do. Let’s say we create a method to square a number.
def square(numberIn): return numberIn * numberIn
No documentation there, let’s fix that.
def square(numberIn): """Returns the square of the number passed in""" return numberIn * numberIn
The doctest actually goes inside the documentation you’ve just created.
def square(numberIn): """Returns the square of the number passed in
>>> square(2) 4 """ return numberIn * numberIn
So, you use >>> to delineate code that is going to be tested. That’s the same >>> you see when you’re working at a Python prompt. In this case, we’re calling our method and passing in 2. On the next line, put the expected result, which is 4. You can put as many of these tests as you like in the documentation block, separate them with a blank line. You can even put error checking here.
def square(numberIn): """Returns the square of the number passed in
>>> square(2) 4
>>> square('three') Traceback (most recent call last): ... TypeError: can't multiply sequence by non-int of type 'str' """ return numberIn * numberIn
I got that traceback by using my function at a command prompt. I copied and pasted it into my doctest. I deleted the variable stuff between the two important lines and replaced it with ellipses.
In order to run the doctest, you have to add the following to the bottom
of your file:
if '__main__' == __name__: import doctest doctest.register_optionflag('ELLIPSIS') doctest.testmod()
Note that I’ve registered the ellipsis option flag. This allows you to use … as a wildcard so you can skip over some of the expected output. It keeps your code cleaner and lets you handle situations where expected output might be different – say – generating a random number.
Ok, now you can run the file. Let’s say we called it utility.py.
$ python utility.py $
No output. That’s the default. In order to see the test results, you need to use the verbose flag.
$ python utility. py -v
Trying:
square(2) Expecting: 4 ok Trying: square('three') Expecting: Traceback (most recent call last): ... TypeError: can't multiply sequence by non-int of type 'str' ok 1 items had no tests: __main__ 1 items passed all tests: 2 tests in __main__.square 2 tests in 2 items. 2 passed and 0 failed.
Test passed
$
And there are our test results – everything passed.
Aside from the option to put doctests in your code, you can also create txt files for documentation and add doctests to them. This is what bigger projects tend to do, especially when they have multiple files of code to deal with.
Unit tests are another great way to test that is very helpful for scripting. Every time you get a new requirement, add in a unit test for any situation you can think of that would effect that new requirement. Test things going right, things going wrong, and
any unexpected behavior or input you can think of. If you find an error that isn’t covered in your tests, create a unit test to cover it. A baseline of code like this ensures that any future changes won’t break existing code. If it does, it will make the error really easy to track down – you won’t have to try to remember all the past requirements.
Easy for the next person to maintain
Between keeping code in one place, not repeating yourself, and having robust testing, you’ve just made maintaining this code really, really easy for the next person that comes along. Even if they don’t know Python, a glance at your robust documentation and clear code will make it easy to make small changes. Running the tests will leave them confident that their changes won’t have a negative effect on other code.
We all want to be able to go on vacation and not get a call that someone broke your code. Clear code with lots of tests are the best way to ensure a relaxing break. Python makes this quick
and easy, so you can move on to something hard.