Request for criticism - tell me how to become more Pythonic
Andrew Dalke
dalke at dalkescientific.com
Mon Sep 10 14:06:26 EDT 2001
Jon:
> If anyone has the time, I'd be interested in comments
> about my style: what I am doing right, and what I need
> to improve.
You've a lot of code, and much of it needs a bit of changing.
And I can't give it enough time to think about how to make
the whole thing more Pythonic. What follows is only
addressing parts.
Turn
options = {
'screen_width': 80,
'description_file': "info.txt",
...
into
options = {
'screen_width': (80, "w"),
'description_file': ("info.txt", "f"),
...
and move the initialization of 'options' to module
scope (makes it easier to understand what 'process_command_line'
does and reduces typo errors from having to repeat the key
twice). Could also move short_options and long_options to module
scope.
Much as you like list comprehensions,
short_options = "".join([short_form[opt]+':' for opt in options.keys])
the following is easier for me to understand
short_options = ":".join(options.keys) + ":"
The code all inside the try block
try:
(matched_list, remainder) = \
getopt(sys.argv[1:], short_options, long_options)
[lots of code]
except:
...
should have much less of the code in the try block, and should
not do a blanket except:
try:
matched_list, remainder = getopt(sys.argv[1:] ... )
except getopt.error, err:
usage(sys.stderr)
raise SystemExit(str(err))
(Note that this means having a 'usage' function which takes
an output file handle. Very useful since it also allows you
to have a --help command line option, which then just calls
usage(sys.stdout).)
def usage(outfile):
print >>outfile, the_program_title
print >>outfile, "-"*len(the_program_title)
print >>outfile,
print >>outfile, sys.argv[0]," [--options] [information file name]"
print >>outfile,
print >>outfile, "Possible command line arguments:"
keys = options.key() # Note that I'm sorting the keys
keys.sort()
for key in keys:
default, short_form = options[key]
print >>outfile, "\t-%s\t--%s" % (short_form, key)
In the main sys.argv parsing loop, I'll often raise a SystemExit
exception rather than
raise "Too many arguments on command line"
That should not be done if you want to use the parsing code as
part of a larger library. In that case, your function would
need to take the argv to use (instead of looking up sys.argv[1:])
and would need to declare its own exception class, which would
be explicitly caught in the main(). You're not interested in
that for this code so I won't go into details.
Try replacing your argument processing to
args = {}
for k, v in matched_list:
if k.startswith("--"):
assert options.has_key(k[2:]), k
args[k[2:]] = v
elif k.startswith("-"):
assert options.has_key(k[1:]), k
args[k[1:]] = v
else:
raise AssertionError(k)
(the assertion checks arise from a general desire to double
check that the input really is parsed correctly. I've never
had getopt fail on me to produce those exceptions.)
then replace your post-processing to
for k, (default, short_form) in options.items():
if args.has_key(k):
if isinstance(default, type(0)):
args[k] = int(args[k])
elif isinstance(default, type("")): # You don't need these
pass # lines for your code,
elif isinstance(default, type(0.0)): # since you only have
args[k] = float(args[k]) # strings and ints.
else:
args[k] = default
(In 2.2 you can replace this with
for k, (default, short_form) in options.items():
if k in args:
args[k] = type(default)(args[k])
else:
args[k] = default
> # finally, turn the options into global variables so all the other
> # functions can use them easily
> for (option, value) in options.items():
> globals()[option] = value
Very non-Pythonic. Just pass the list of arguments around.
Better yet, I have my main code know which functions use which
arguments.
You use string exceptions:
> if len(input)<len(header_fields):
> raise "Input error: Album description truncated
These are deprecated. If you don't want to make your own
exception type, then for these sorts of errors I like using
AssertionError, as in
assert len(input) >= len(header_files), "Input error: Alb ..."
The variable name 'input' in things like
> def parse_album_information(input):
isn't descriptive enough. It looks like a list of lines, but
other implementations could use, say, a file handle. If you
say 'input_lines' then it's easier to understand.
You could also recast your input code as a reader:
for disk_info in DiskInfoReader(open(description_file, 'r')):
tracklist_info = parse_track_list(disk_info)
display_tracklist(disk_info, tracklist_info)
# Switch to using an iterator for 2.2
class DiskInfoReader:
def __init__(self, file):
self.file = file
self._n = 0
def __getitem__(self, i):
assert self._n == i, "forward iteration only"
...
self._n += 1
return disk_info
but I don't understand your code well enough to do the
refactoring for that, or even if it's appropriate (there
seem to be two different chunks of code that alternate
consuming from input_lines.)
Don't use 'except:', as in
> try:
> os.makedirs(destination)
> except:
Check for the minimal error you can, which is os.error in
this case.
build_command_line should ensure the info_value passed to
the 'encoder_option_format[info_name]' string doesn't contain
a " or other escape character. I always find escapeing
characters for the shell an annoyance. I've only found one
function in the std. Python libs that helps - commands.mkarg,
and it always adds a space at the front.
Uggh, I knew this would take a long time to write. That's
why few others have responded to this thread.
Andrew
dalke at dalkescientific.com
More information about the Python-list
mailing list