9

I have gone through the sys documentation, however there is something that is still unclear to me. I have looked for some similar question on stackoverflow, but I haven't find anything useful (clearly any reference is appreciated!).

I want to create a script - say foo.py - in which I want pass from 3 to 6 arguments:

$ python foo.py arg1 arg2 arg3

The first 3 arguments must be given in any case; the last 3 arguments are used in a function that have default argument values if nothing is passed.

The question is how do I do this? So far I was thinking to write something like the following foo.py (this is an easy example set only for the purpose of having something concrete in support of my question):

import sys

def example(credit_mom, credit_dad, debt_mom, debt_dad = 1000,
            salary = 2000, bonus = 0):
    total_gain = salary + credit_dad + credit_mom + bonus
    total_loss = debt_dad + debt_mom

    return total_gain - total_loss

if __name__ == '__main__':
    if len(sys.argv) < 4:
        sys.exit('Need at least 3 arguments. The order is as follows:\n\
            1.credit_mom;\n\
            2.credit_dad;\n\
            3.debt_mom;\n\
            4.others')
    else:
        sys.exit(example(sys.argv[1],
                         sys.argv[2],
                         sys.argv[3],
                         sys.argv[4],
                         sys.argv[5],
                         sys.argv[6]))

If I run this script I clearly get an IndexError exception:

$ python foo.py 110 110 220
Traceback (most recent call last):
  File "foo.py", line 19, in <module>
    sys.argv[4],
IndexError: list index out of range
JoErNanO
  • 2,458
  • 1
  • 25
  • 27
rafforaffo
  • 511
  • 2
  • 7
  • 21
  • I suggest you use [argparse](https://docs.python.org/2.7/library/argparse.html) (and [here is the tutorial](https://docs.python.org/2.7/howto/argparse.html#id1) ). Saves you having to manually check for the existence of parameters. – JoErNanO Feb 12 '15 at 14:14
  • thanks! I start going through the tutorial immediately – rafforaffo Feb 12 '15 at 14:16

4 Answers4

30

Using Argparse

I suggest you use argparse (and here is the tutorial ). Saves you having to manually check for the existence of parameters. Moreover argparse gives you the --help argument as a freebie, which will read the help="" string defined for each argument, if provided.

Sample Program

In your case you have three mandatory (positional) argument and three optional ones. A sample argparse code would look like this:

#!/usr/bin/python
# coding: utf-8

import argparse

def parseArguments():
    # Create argument parser
    parser = argparse.ArgumentParser()

    # Positional mandatory arguments
    parser.add_argument("creditMom", help="Credit mom.", type=float)
    parser.add_argument("creditDad", help="Credit dad.", type=float)
    parser.add_argument("debtMom", help="Debt mom.", type=float)

    # Optional arguments
    parser.add_argument("-dD", "--debtDad", help="Debt dad.", type=float, default=1000.)
    parser.add_argument("-s", "--salary", help="Debt dad.", type=float, default=2000.)
    parser.add_argument("-b", "--bonus", help="Debt dad.", type=float, default=0.)

    # Print version
    parser.add_argument("--version", action="version", version='%(prog)s - Version 1.0')

    # Parse arguments
    args = parser.parse_args()

    return args

def example(credit_mom, credit_dad, debt_mom, debt_dad = 1000, salary = 2000, bonus = 0):
    total_gain = salary + credit_dad + credit_mom + bonus
    total_loss = debt_dad + debt_mom

    return total_gain - total_loss

if __name__ == '__main__':
    # Parse the arguments
    args = parseArguments()

    # Raw print arguments
    print("You are running the script with arguments: ")
    for a in args.__dict__:
        print(str(a) + ": " + str(args.__dict__[a]))

    # Run function
    print(example(args.creditMom, args.creditDad, args.debtMom, args.debtDad, args.salary, args.bonus))
JoErNanO
  • 2,458
  • 1
  • 25
  • 27
  • 1
    Proper use of argparse wonderfully simulates terminal behaviour, so I also recommend using the built in argparse. – lennyklb Feb 12 '15 at 14:23
  • Just to clarify, 1. this piece of code must be after `__name__==`... 2. the order of `parser.add_argument` must match up the order I'm passing the arguments in the command line. 3. In case one of the argument is a string I'll do: `parser.add_argument("creditMom" help="Credit mom.", type=string)` and the corresponding command line argument will be `'something'` (not `something`). Are these considerations right? – rafforaffo Feb 12 '15 at 14:39
  • Also, the function in this case must be defined as `def example(*args)`. And the in `if __name__==`, after you lines of code, I'll simply do `example(args)`. Right? – rafforaffo Feb 12 '15 at 14:45
  • @rafforaffo `args = parser.parse_args()` returns a `Namespace()` object named `args`, which should not be "unpackable" with `*args`. However you can refer to the individual parsed arguments by doing `args.creditMom` and `args.bonus`, for example. Finally yes positional arguments, as the name suggets, must be defined with `parser.add_argument()` in the order they are to be passed to the python script. Optional arguments however need not. See my updated answer. ;) – JoErNanO Feb 12 '15 at 14:56
  • @rafforaffo My pleasure. :) Note that there might be a pythonic way to unwrap `*` or `**` `Namespace()` objects. I've never looked into this, but you could. :) – JoErNanO Feb 12 '15 at 15:07
9

While I endorse the argparse approach, here's a quick and dirty approach:

arg1, arg2, arg3 = [None, False, []]
if sys.argv[1:]:   # test if there are atleast 1 argument (beyond [0])
    arg1 = sys.argv[1]
    if sys.argv[2:]:
        arg2 = sys.argv[2]  # careful 'True' is a string, not a boolean
        arg3 = sys.argv[3:]  # rest

Mostly I use this when I'm starting to add argument parsing to a script, and my choices of arguments hasn't matured. It's more suited to 'positionals' than 'optionals' (to use argparse terminology).

hpaulj
  • 221,503
  • 14
  • 230
  • 353
3

Other than using argparse (which I do recommend), this can be solved by slicing and expanding:

    sys.exit(example(*sys.argv[1:7]))

The slice will only contain elements that actually exist, even if there aren't enough to fulfill the size of slice requested.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
1

Your function "example" is just fine. The problem is that you're trying to read sysargs that aren't there. Try to check if they are empty (in the else-clause).

Mackie
  • 186
  • 9