Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Q's on my first python script

0 views
Skip to first unread message

kj

unread,
May 10, 2009, 8:52:21 AM5/10/09
to

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()

--
NOTE: In my address everything before the first period is backwards;
and the last period, and everything after it, should be discarded.

Steven D'Aprano

unread,
May 10, 2009, 11:56:00 AM5/10/09
to
On Sun, 10 May 2009 12:52:21 +0000, kj wrote:

> 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 :)


--
Steven

Andre Engels

unread,
May 10, 2009, 12:56:52 PM5/10/09
to Steven D'Aprano, pytho...@python.org
On Sun, May 10, 2009 at 5:56 PM, Steven D'Aprano
<st...@remove-this-cybersource.com.au> wrote:

>> 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.

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).


--
André Engels, andre...@gmail.com

Scott David Daniels

unread,
May 10, 2009, 1:31:54 PM5/10/09
to
Steven D'Aprano wrote:
> On Sun, 10 May 2009 12:52:21 +0000, kj wrote:
> ....

>> 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 :)

You (the OP) don't seem to know about PEP 8, which advises 4-space
indents, all-caps constants, and many more style specifics.
http://www.python.org/dev/peps/pep-0008/

--Scott David Daniels
Scott....@Acm.Org

MRAB

unread,
May 10, 2009, 1:37:56 PM5/10/09
to pytho...@python.org
Andre Engels wrote:
> On Sun, May 10, 2009 at 5:56 PM, Steven D'Aprano
> <st...@remove-this-cybersource.com.au> wrote:
>
>>> 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.
>
> 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.

Dave Angel

unread,
May 10, 2009, 3:33:31 PM5/10/09
to kj, pythonlist

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.


kj

unread,
May 10, 2009, 4:43:21 PM5/10/09
to

>import myscript
>raise myscript.BadArgument


Thanks! That was very helpful!

Kynn

kj

unread,
May 10, 2009, 4:52:04 PM5/10/09
to

Thank you all very much! I really appreciate it.

kynn

Nick Craig-Wood

unread,
May 11, 2009, 5:30:04 AM5/11/09
to

To be honest, my first try at this script probably would have looked
quite like yours! Once it was working I'd have tidied it as below.

* Put into a class to stop spread of globals
* WD made class member and upper case
* 4 space indents as per PEP8
* remove lamda as it isn't needed
* only try:/except: the minimum possible amount of code
* new name for exception


from optparse import OptionParser
import re
import datetime
import sys

class BadDateString(Exception):
"""Malformed date string"""

class Main(object):
"""Script to implement weekday"""
WD = ("M", "T", "W", "H", "F", "S", "U")
def wkday_abbrev(self, date_string):
mm = re.match("(\d{2})(\d{2})(\d{2})\Z", date_string)
if not mm:
raise BadDateString("Couldn't match date string from %r" % date_string)
y, m, d = map(int, mm.groups())


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

try:
return self.WD[datetime.datetime(y, m, d).weekday()]
except ValueError:
raise BadDateString("Bad date in %r" % date_string)

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)
sys.stdout.write("%s%s" % (args[0], weekday))
if not options.nonl:
print

if __name__ == "__main__":
Main()

--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick

Marius Gedminas

unread,
May 14, 2009, 6:15:23 PM5/14/09
to
On May 11, 12:30 pm, Nick Craig-Wood <n...@craig-wood.com> wrote:
>     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.

--
Marius Gedminas

Marius Gedminas

unread,
May 14, 2009, 6:18:54 PM5/14/09
to
On May 10, 6:56 pm, Steven D'Aprano <st...@REMOVE-THIS-

cybersource.com.au> wrote:
> > 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!")

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!")

--
Marius Gedminas

0 new messages