Q's on my first python script

K

kj

Below is my very firs python script.

This was just a learning exercise; the script doesn't do anything
terribly exciting: for an argument of the form YYMMDD (year, month,
day) it prints out the corresponding string YYMMDDW, where W is a
one-letter abbreviation for the day of the week. E.g.

% wd 090511
090511M

The script runs OK, but I can still see a few areas of improvement,
for which I could use your advice.

1. The name of the BadArgument exception class defined in the script
does not seem to me sufficiently specific. If one were to import
the script in order to reuse its wkday_abbrev function, I'd like
this exception's name to be more unequivocally tied to this
script. What I'm looking for is something like a "namespace"
for this script. What's the pythonic way to construct a namespace?

2. In some python modules I've seen the idiom

if __name__ == "__main__":
# run some tests here

I'd like to set up tests for this script, mostly to ensure that
it handles the error cases properly, but I'm alread using the
idiom above to actually run the script under normal operation.
What's the typical python idiom for running tests on a *script*
(as opposed to a module that is normally not supposed to be run
directly)?

3. Still on the subject of testing, how does one capture in a
variable the output that would normally have gone to stdout or
stderr?

4. What's the python way to emit warnings? (The script below should
warn the user that arguments after the first one are ignored.)

5. The variable wd is meant to be "global" to the script. In other
languages I've programmed in I've seen some typographic convention
used for the name of such variables (e.g. all caps) to signal
this widened scope. Does python have such a convention?

Any comments/suggestions on these questions, or anything else about
the script, would be much appreciated.

TIA!

kynn


----------------------------------------------------------------
from optparse import OptionParser
import re
import datetime
import sys

class BadArgument(Exception): pass

wd = ("M", "T", "W", "H", "F", "S", "U")

def wkday_abbrev(str):
try:
mm = re.match("(\d{2})(\d{2})(\d{2})\Z", str)

y, m, d = map(lambda x: int(mm.group(x)), (1,2,3))

if y < 38: y = y + 1900
else: y = y + 2000

return wd[datetime.datetime(y, m, d).weekday()]
except (AttributeError, ValueError):
raise BadArgument()


def main():

usage = '''Usage: %prog [options] YYMMDD
%prog -h|--help
'''

parser = OptionParser(usage=usage)
parser.add_option("-n", "--no-newline", dest="nonl",
action="store_true", help="omit newline in output")

(options, args) = parser.parse_args();

try:
sys.stdout.write("%s%s" % (args[0], wkday_abbrev(args[0])))
if not options.nonl: print

except (IndexError, BadArgument):
print usage
sys.exit(1)
except: raise

if __name__ == "__main__": main()
 
S

Steven D'Aprano

1. The name of the BadArgument exception class defined in the script
does not seem to me sufficiently specific. If one were to import the
script in order to reuse its wkday_abbrev function, I'd like this
exception's name to be more unequivocally tied to this script. What
I'm looking for is something like a "namespace" for this script.
What's the pythonic way to construct a namespace?

You already have one. The module you have created is a namespace. If your
script is called "myscript.py", then to use it elsewhere you would do:

import myscript
raise myscript.BadArgument

2. In some python modules I've seen the idiom

if __name__ == "__main__":
# run some tests here

I'd like to set up tests for this script, mostly to ensure that it
handles the error cases properly, but I'm alread using the idiom
above to actually run the script under normal operation. What's the
typical python idiom for running tests on a *script* (as opposed to a
module that is normally not supposed to be run directly)?

I sometimes give my scripts an option -t or --self-test, and then run
tests if that option is passed on the command line.

Alternatively, put your tests in another module, say, myscript_tests.py,
and then just run that when you want to test myscript.

3. Still on the subject of testing, how does one capture in a
variable the output that would normally have gone to stdout or
stderr?

Normally you would write the function to *return* the result, rather than
*print* the result. If all output goes through the function return
mechanism, then it's easy to capture: x = func().

However, for cases where the function does print directly, you can
redefine stdout and strerr to be any file-like object, so you can do
something like this:


# untested
import sys
import cStringIO
save_stdout, save_stderr = sys.stdout, sys.stderr
c1 = cStringIO.StringIO()
c2 = cStringIO.StringIO()
try:
sys.stdout = c1
sys.stderr = c2
result = func(*args, **kwargs) # normally prints some stuff
finally:
# restore standard files
sys.stdout = save_stdout
sys.stderr = save_stderr
captured_from_stdout = c1.getvalue()
captured_from_stderr = c2.getvalue()


4. What's the python way to emit warnings? (The script below should
warn the user that arguments after the first one are ignored.)

import warnings
warnings.warn("The end of the world is coming!")

5. The variable wd is meant to be "global" to the script. In other
languages I've programmed in I've seen some typographic convention
used for the name of such variables (e.g. all caps) to signal this
widened scope. Does python have such a convention?

As a general rule, it's best to avoid globals variables as much as
possible.

One convention I occasionally use is to prefix global variables with a
lowercase g. And then ruthlessly refactor my code until any variable
starting with a lowercase g is removed :)
 
A

Andre Engels

As a general rule, it's best to avoid globals variables as much as
possible.

However, this is not really a global variable, this is a constant,
which is not something to be avoided, nor something that there is a
convention for (as far as I know).
 
M

MRAB

Andre said:
However, this is not really a global variable, this is a constant,
which is not something to be avoided, nor something that there is a
convention for (as far as I know).
In Python the convention for constants is CAPS_WITH_UNDERSCORES.
 
D

Dave Angel

kj said:
Below is my very firs python script.

This was just a learning exercise; the script doesn't do anything
terribly exciting: for an argument of the form YYMMDD (year, month,
day) it prints out the corresponding string YYMMDDW, where W is a
one-letter abbreviation for the day of the week. E.g.

% wd 090511
090511M

The script runs OK, but I can still see a few areas of improvement,
for which I could use your advice.

1. The name of the BadArgument exception class defined in the script
does not seem to me sufficiently specific. If one were to import
the script in order to reuse its wkday_abbrev function, I'd like
this exception's name to be more unequivocally tied to this
script. What I'm looking for is something like a "namespace"
for this script. What's the pythonic way to construct a namespace?

2. In some python modules I've seen the idiom

if __name__ == "__main__":
# run some tests here

I'd like to set up tests for this script, mostly to ensure that
it handles the error cases properly, but I'm alread using the
idiom above to actually run the script under normal operation.
What's the typical python idiom for running tests on a *script*
(as opposed to a module that is normally not supposed to be run
directly)?

3. Still on the subject of testing, how does one capture in a
variable the output that would normally have gone to stdout or
stderr?

4. What's the python way to emit warnings? (The script below should
warn the user that arguments after the first one are ignored.)

5. The variable wd is meant to be "global" to the script. In other
languages I've programmed in I've seen some typographic convention
used for the name of such variables (e.g. all caps) to signal
this widened scope. Does python have such a convention?

Any comments/suggestions on these questions, or anything else about
the script, would be much appreciated.

TIA!

kynn


----------------------------------------------------------------
from optparse import OptionParser
import re
import datetime
import sys

class BadArgument(Exception): pass

wd = ("M", "T", "W", "H", "F", "S", "U")

def wkday_abbrev(str):
try:
mm = re.match("(\d{2})(\d{2})(\d{2})\Z", str)

y, m, d = map(lambda x: int(mm.group(x)), (1,2,3))

if y < 38: y = y + 1900
else: y = y + 2000

return wd[datetime.datetime(y, m, d).weekday()]
except (AttributeError, ValueError):
raise BadArgument()


def main():

usage = '''Usage: %prog [options] YYMMDD
%prog -h|--help
'''

parser = OptionParser(usage=usage)
parser.add_option("-n", "--no-newline", dest="nonl",
action="store_true", help="omit newline in output")

(options, args) = parser.parse_args();

try:
sys.stdout.write("%s%s" % (args[0], wkday_abbrev(args[0])))
if not options.nonl: print

except (IndexError, BadArgument):
print usage
sys.exit(1)
except: raise

if __name__ == "__main__": main()

Other replies cover most of your questions nicely. But for the testing
question:

Rename this script to some other name, and call it a module
Write a new script with just three lines in it:

import othermodule

if __name__ == "__main__":
othermodule.main()



Now your testing code can go in "othermodule.py"

Note that I would put the argument parsing logic in the new file, so
parts of main() would actually move. Normally I'd call main() with two
arguments - options, args

Your use of optparse could be more streamlined. For example, it'll
build the help string for you, based on the various calls to
add_option(), and it includes its own -h and --help implicitly.
 
K

kj

You already have one. The module you have created is a namespace. If your
script is called "myscript.py", then to use it elsewhere you would do:
import myscript
raise myscript.BadArgument
I sometimes give my scripts an option -t or --self-test, and then run
tests if that option is passed on the command line.
Alternatively, put your tests in another module, say, myscript_tests.py,
and then just run that when you want to test myscript.
Normally you would write the function to *return* the result, rather than
*print* the result. If all output goes through the function return
mechanism, then it's easy to capture: x = func().
However, for cases where the function does print directly, you can
redefine stdout and strerr to be any file-like object, so you can do
something like this:

# untested
import sys
import cStringIO
save_stdout, save_stderr = sys.stdout, sys.stderr
c1 = cStringIO.StringIO()
c2 = cStringIO.StringIO()
try:
sys.stdout = c1
sys.stderr = c2
result = func(*args, **kwargs) # normally prints some stuff
finally:
# restore standard files
sys.stdout = save_stdout
sys.stderr = save_stderr
captured_from_stdout = c1.getvalue()
captured_from_stderr = c2.getvalue()
import warnings
warnings.warn("The end of the world is coming!")
As a general rule, it's best to avoid globals variables as much as
possible.
One convention I occasionally use is to prefix global variables with a
lowercase g. And then ruthlessly refactor my code until any variable
starting with a lowercase g is removed :)


Thanks! That was very helpful!

Kynn
 
M

Marius Gedminas

    def __init__(self):
        usage = '''Usage: %prog [options] YYMMDD
           %prog -h|--help
'''
        parser = OptionParser(usage=usage)
        parser.add_option("-n", "--no-newline", dest="nonl",
                          action="store_true", help="omit newline in output")
        (options, args) = parser.parse_args();
        try:
            weekday = self.wkday_abbrev(args[0])
        except BadDateString, e:
            print usage
            print e
            sys.exit(1)

I would recommend printing error messages to sys.stderr.

BTW, in this particular case you may want to use

parser.error(e)

instead of print + sys.exit.
 
M

Marius Gedminas

import warnings
warnings.warn("The end of the world is coming!")

The warnings module is used for warnings about program code, not user
input.

import logging
logging.warn("Oh noes, you passed me two arguments instead of one!")
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,573
Members
45,046
Latest member
Gavizuho

Latest Threads

Top