There's a difference between code when you know you will be using it later and code that you're going to throw away. There's also a difference between being lazy and taking time to follow best practices. Below is an example of two command line programs that do the same thing. One is 3 lines long and the other is 100. (Of course the example is contrived, and usually the logic will be more than 3 lines. For cat I would usually use the command line cat) The effect of having the actual logic portion be small is that this is boilerplate code useful for starting python command line programs.
The extra length needs to buy us something. Here's my ideas of what it gives me:
- Order - I know where items belong
- Isolated Functionality - No (or limited) execution when importing/reusing
- Logging - rather than dealing with prints that clutter the screen
- Testing - I've got doctest built in, but I have a unittest file as well that gives decent coverage
- Documentation - Illustrates module and function docs as well as doctests
- Command line docs - use of optparse
__main__- Illustrates use of main statement (with argument arg for reusability So lazy web. Am I a pep8 following geek that just complicates things? Should boilerplate code include more? less?
I'm asking cause I'm doing two intro to python classes soon, and I thought that I might use this as an example since it covers a bunch of ground.
import sys for line in open(sys.argv): print line, More complicated, yet testable, maintainable #! /usr/bin/env python """ canonicalcat is an example of writing a python program that perhaps you want to distribute or will be maintained over time. As such one should add documentation and this is an example of module level documentation. If one were to import canonicalcat and type `help(canonicalcat)` it would spit this out """ # These are the imports from the python stdlib import sys import logging import optparse # Some like to separate additional libraries from the standard ones # file meta data __version__ = "0.1" __author__ = "matt harrison" __license__ = "psf" # GLOBAL ARGS/CONSTS # Since python doesn't have constants, we can emulate them # using (naming) conventions. Put them here # GLOBAL INIT logging.basicConfig(filename=".concat.log", level=logging.DEBUG) def cat_file(fin, outfile=None): """ Main logic to cat file >>> import StringIO >>> fout = StringIO.StringIO() >>> cat_file("small.txt", fout) >>> lines = fout.getvalue() >>> lines 'foo\nbar\n' """ logging.log(logging.DEBUG, "CONCAT: %s"% fin) if outfile is None: fout = sys.stdout elif isinstance(outfile, str): fout = open(outfile, 'w') else: fout = outfile for line in open(fin): fout.write(line) def _test(): import doctest doctest.testmod() def main(prog_args=None): """ A main function. Rather than putting this logic into the the if __name__ statement below, creating a main function allows other programs to use main logic. It also allows for testing, by passing in args rather than monkey patching sys.argv (which won't be thread safe). >>> main(["canonicalcat.py", "small.txt"]) foo bar """ if prog_args is None: prog_args = sys.argv parser = optparse.OptionParser() parser.usage = """A python implementation of 'cat', default use is to provide a filename to cat""" parser.add_option("-o", "--output-file", dest="fileout", help="specify file to cat to (default is stdout)") parser.add_option("-t", "--test", dest="test", action="store_true", help="run doctests") opt, args = parser.parse_args(prog_args) if args[1:]: cat_file(args, opt.fileout) if opt.test: _test() else: parser.print_help() if __name__ == "__main__": # when one "exectutes" a python program, it's __name__ is # __main__, otherwise it's name will be the module name main()