my first class: Args
Scott David Daniels
Scott.Daniels at Acm.Org
Sat Aug 28 14:25:36 EDT 2004
Peter Kleiweg wrote:
> I'm still new to Python. All my experience with OO programming
> is in a distant past with C++. Now I have written my first class
> in Python. The class behaves exactly as I want, but I would like
> to get comments about coding style.
I like: "look at a coding standard (PEP 8), then vary for clarity."
<http://www.python.org/peps/pep-0008.html>
> ... when to use double quotes, and when single.
I generally prefer single a lot of places since typefaces sometimes make
it hard to tell the difference between a pair of singles and a double.
Here is a lot of blue pencil. Don't consider that a rip of your code.
Don't consider me correct, see each change and decide for yourself
which way is clearer. One thing I use here several places is the
"try and fail, don't ask permission" -- a pythonic style.
> I won't tell you what the class is about,
Ah, but everyone has a context in which they read code.
> """
> Handling of arguments: options, arguments, file(s) content iterator
^^ Try "Demonstrate use of arguments..." (a verb helps).
... <keepers>
> class Args:
> """
^^ Here put a line about what the class is about:
Source provision for numbered lines with (possibly) multiple inputs.
... <keepers>
> self._argv = sys.argv[1:]
> self._argc = len(self._argv)
^^ I'd forego _argc altogether and use len(self._argv) where needed.
> self._usage = usage % {'progname': self.progname}
^^ I'd wait to do this in the usage method -- no need for it normally
self._usage = usage
> def __iter__(self):
> "iterator set-up"
^^ useless docstring -- presume the reader knows python
> if self._argc == 0 and sys.stdin.isatty():
> self.usage()
> if self._argc == 0:
> self.infile = '<stdin>'
> self._stdin = 1
> self._in = sys.stdin
> else:
> self.infile = self._argv.pop(0)
> self._argc -= 1
> self._stdin = 0
> self._in = open(self.infile, 'r')
> return self
^^
try:
self.infile = self._argv.pop(0)
except IndexError:
if sys.stdin.isatty():
self.usage() # Doesn't return
else:
self.infile = '<stdin>'
self._stdin = True
self._in = sys.stdin
else:
self._stdin = False
self._in = open(self.infile, 'r')
return self
>
> def next(self):
> "iterator next"
^^ again -- skip the comment unless you say more than the language does.
Maybe: "get another line, possibly going to another file for it"
> line = self._in.readline()
> if line:
> self.lineno += 1
> self.linesum += 1
> return line
> self.lineno = -1
> self.infile = None
> if self._stdin:
> raise StopIteration
> self._in.close()
> if self._argc < 1:
> raise StopIteration
> self.lineno = 0
> self.infile = self._argv.pop(0)
> self._argc -= 1
> self._in = open(self.infile, 'r')
> return self.next()
^^ Loop rather than recur unless you have a good reason.
while True:
line = self._in.readline()
if line:
self.lineno += 1
self.linesum += 1
return line
# Look for a source of more lines
if self._stdin:
assert not self._argv # stdin is not part of a list
break
self._in.close()
try:
self.infile = self._argv.pop(0)
except IndexError:
break # No more files
self.lineno = 0
self._in = open(self.infile, 'r')
self.lineno = -1
self.infile = None
raise StopIteration
>
> def warning(self, text):
> "print warning message to stderr, possibly with filename and lineno"
> if self.lineno > 0:
> print >> sys.stderr, '%s:%i: warning: %s' % (self.infile, self.lineno, text)
> else:
> print >> sys.stderr, '\nWarning %s: %s\n' % (self.progname, text)
>
^^ these lines look a bit long. Consider:
if self.lineno > 0:
print >>sys.stderr, '%s:%i: warning: %s' % (
self.infile, self.lineno, text)
else:
print >>sys.stderr, '\nWarning %s: %s\n' % (
self.progname, text)
or even consider:
def error(self, text, fatal=True):
if fatal:
style = 'Error'
else:
style = 'Warning'
if self.lineno > 0:
print >>sys.stderr, '%s:%i: %s:' % (
self.infile, self.lineno, style),
else:
print >>sys.stderr, '\n%s %s:' % (style, self.progname),
print >>sys.stderr, text
if fatal:
sys.exit(1) # or even: raise SystemExit
Where warnings look like: obj.error(msg, fatal=False)
> def usage(self):
> "print usage message"
> print >> sys.stderr, '\n' + self._usage + '\n'
> sys.exit(1)
^^ As mentioned above:
def usage(self):
"print usage message and leave"
print >> sys.stderr,
print >> sys.stderr, self._usage % {'progname': self.progname}
print >> sys.stderr,
sys.exit(1)
>
> def shift(self):
> "pop first of remaining arguments (shift)"
> if self._argc < 1:
> self.usage()
> self._argc -= 1
> return self._argv.pop(0)
^^ becomes:
def shift(self):
"pop first of remaining arguments (shift)"
try:
return self._argv.pop(0)
except IndexError:
self.usage()
>
> def pop(self):
> "pop last of remaining arguments"
> if self._argc < 1:
> self.usage()
> self._argc -= 1
> return self._argv.pop()
^^ becomes:
def pop(self):
"pop last of remaining arguments"
try:
return self._argv.pop()
except IndexError:
self.usage()
> def getopt(self, shortopts, longopts=[]):
> "get options and merge into dict 'opt'"
> options, self._argv = getopt.getopt(self._argv, shortopts, longopts)
> self.opt.update(dict(options))
> self._argc = len(self._argv)
^^ As I've said, I'd drop this one
> if __name__ == '__main__':
> ...
^^ also nicer to be able to test from included version:
def demo():
...
if __name__ == '__main__':
demo()
More information about the Python-list
mailing list