[Tutor] Setting Command Line Arguments in IDLE
Martin A. Brown
martin at linux-ip.net
Sun May 26 11:09:58 EDT 2019
Hello Richard,
In addition to the answers you have received from Alex and Alan,
I'll add a bit longer of an answer mostly around how to separate the
command-line invocation / argument handling part of your program
from the pure-Python logic.
While I, also, cannot help terribly with the direct question about
your IDLE environment, I hope this explanation is generally helpful
and useful.
>I am working on a python script that will be provided arguments
>when run from the system command line. Is there any place in IDLE
>to provide equivalent arguments for testing while developing in
>IDLE?
I can't really help so much with the IDLE part of the question, but
I do have a solution that has worked very well for me for testing
programs that are usually invoked by command-line. My answer will
focus on that angle rather than the IDLE angle.
>Is there any way to define the working directory for the program,
>or will it always be the directory the script is in (it will be
>typically run using the PATH, so not the same directory as the
>script)?
Defining working directory.
---------------------------
You cannot control the initial working directory when your program
has been executed. It was executed by another process (or human, or
alien from the Ophiucus Cluster) so your working directory is
whatever was in the parent parent process just before
forking/execution of your program.
However, you are free to chdir() once your program is running:
>>> os.getcwd()
'/home/mabrown'
>>> os.chdir('/home/mabrown/tmp/')
>>> os.getcwd()
'/home/mabrown/tmp'
One bit of care that is warranted when you are calling os.chdir()
(besides the usual error-checking, 'Can the user running this
program actually chdir() to that directory?)' is to think about
whether you want your program to use relative paths or absolute
paths. It's worth thinking about early because if you don't, you
can end up with a mess of relative and absolute paths. That way
madness, bugs and possible data loss lie.
What is my install directory?
-----------------------------
If you need to know what directory your Python program is in, you
can use os.path.dirname(os.path.abspath(__file__)).
Sometimes, there are reasons to find your data (or other code?) in
the same directory as (or nearby) your Python program. Think
carefully about this and consider how that might work with
your Python packaging solution, if you are going down that path.
In general, I have tried to use command-line options to find data
directories or other files needed by a Python program, as that is
more intelligible to users (and to me when I come back to the code a
year later) than the automatic discovery of files that just happen
to be in the same directory as my Python program.
Random note on $PATH and full pathnames
---------------------------------------
Have you ever been trying to diagnose a problem with a script and
you realized about four months two late (because that 20 minute
debugging session where you think you are going insane feels like
four months by the time you are done), and you realized at the end
that the problem was that you thought you were running a copy of
script A in directory A, but you were actually running older copy B
in directory B. This meant that none of your changes were being
reflected in the output and you couldn't figure it out.
Well, I'll tell you, I have never, ever had that experience! Nope.
Not even once. Yeah.
Anyway, I have found that logging os.path.abspath(__file__) to the
system log (or to STDERR or elsewhere) what the full path is to the
file that is executing. This is useful to avoid the "I think I'm
running my dev copy, but am actually running something else." sort
of problem.
>If not, is there an easy way to detect that I am running in IDLE so
>I can fake the command line arguments when testing?
I'll give you an example (see below) of how I write the Python to
call most of my programs that are invoked directly from the
command-line.
The basic idea (which I think I picked up here) is to turn anything
that you want to be able to test in your program into Python
variables almost as soon as the program begins.
That way, things like STDIN, STDOUT and the arguments become simply
file objects and lists of strings. With file objects and lists of
strings you can write testing functions to see how your program
behaves when invoked a certain way.
So, this is fairly unexciting code:
def cli(me, fin, fout, argv):
config, args = collectconfiguration(me, argv)
rc = process_something(fin, fout, config)
if rc == 0:
return os.EX_OK
return 1
if __name__ == '__main__':
me = os.path.basename(sys.argv[0])
sys.exit(cli(me, sys.stdin, sys.stdout, sys.argv[1:]))
You will see that I did not add os.environ to the argument list to
the function cli(), but if my program(s) did anything with
environment variables, I would add that here.
You will also see that I don't mess with sys.stderr. In general, I
like to stay away from redirecting that unless there's a strong
need.
Benefits:
* you can call cli() in your testing programs with different lists
of command-line invocations to see how your program behaves
* you can see how
-Martin
Note: You will see in this example of collectconfiguration()
(possibly not the best name for this function) that I make reference
to sys.stdin and sys.stdout. They are also arguments to cli(). I
think that is OK because I use this sort of calling structure many
places and I want to keep the signature for the cli() function the
same everywhere. In short, some programs may not actually have the
'--input' / '--output' command-line options, in which case cli() has
the variables fin and fout to hold those.
def collectconfiguration(me, argv):
ap = argparse.ArgumentParser(prog=me)
ap.add_argument('--input',
default=sys.stdin, type=argparse.FileType('r'),
help='where to read input data [%(default)s]')
ap.add_argument('--output',
default=sys.stdout, type=argparse.FileType('w'),
help='where to write output data [%(default)s]')
ap.add_argument('--apiurl',
default='http://localhost:8080',
help='specify alternate API URL [%(default)s]')
ap.add_argument('--delimiter',
default='\t', type=str,
help='set delimiter [%(default)s]')
ap.add_argument('--loglevel',
default=logging.ERROR, type=arg_isloglevel,
help='set loglevel, e.g. INFO, ERROR [%(default)s]')
ap.add_argument('--version', action='version', version=__version__)
# -- and handle the CLI
#
config, args = ap.parse_known_args(argv)
logger.setLevel(config.loglevel)
logger.debug("Received the following configuration:")
for param, value in sorted(vars(config).items()):
logger.debug(" %s = %r", param, value)
logger.debug(" args: %r", args)
if args:
ap.print_help()
sys.exit("\nExtra arguments received: %r" % (args,))
return config, args
def arg_isloglevel(l, defaultlevel=logging.ERROR):
try:
level = int(l)
return level
except ValueError:
pass
level = getattr(logging, l.upper(), None)
if not level:
level = defaultlevel
return level
--
Martin A. Brown
http://linux-ip.net/
More information about the Tutor
mailing list