mchd
mchd

Reputation: 3163

How to run a Python file with boolean arguments

I have a .py file with a single function that basically converts a .csv file to a .pkl file. This file, when invoked from the terminal, is supposed to convert the measurements between imperial and metrics depending on whether the input is true or not. For example

python3 file.py imperial_units=True or python3 file.py imperial_units=False and if there are no arguments provided like python3 file.py, imperial units should just default to True:

This is my attempt:

import json, sys
import pandas as pd

def log_to_pickle(units):
    ...
            
            if units == True: # If units == True, convert all metric to imperial
                print('Imperial True')
                if imperial_unit == False:
                    ...

            elif units == False: # If units == False, convert all imperial to metric
                print('Imperial False')
                if imperial_unit == True:
                    ...

            ...

if __name__ == "__main__":
    if sys.argv == True or sys.argv == '':
        log_to_pickle(True)
    else:
        log_to_pickle(False)

I've added the print statements inside the if/elif block to see whether my inputs work. I ran python3 file.py imperial_units=True and the output was 'Imperial False'

What am I doing wrong?

Upvotes: 3

Views: 918

Answers (4)

tdelaney
tdelaney

Reputation: 77337

argparse results in a more user-friendly program with help text and uses an arguably more standard method of dash-delimited parameters. Since you only want one thing, the unit of measure, I think it is best implemented with a choice flag that includes a default, but you could make a single argument such as --use-metric that defaults to False.

#!/usr/bin/env python3
import argparse

parser = argparse.ArgumentParser(description='CSV to Pickle with unit conversion to metric')
parser.add_argument('-u', '--unit', default='imperial', choices=['imperial', 'metric'],
                    help='Source unit type (imperial or metric)')
parser.add_argument('from_file', help="CSV file to convert")
parser.add_argument('to_file', help="Converted Pickle file")

args = parser.parse_args()
print(args.unit)

Options for using the program are

$ ./test.py --help
usage: test.py [-h] [-u {imperial,metric}] from_file to_file

CSV to Pickle with unit conversion to metric

positional arguments:
  from_file             CSV file to convert
  to_file               Converted Pickle file

optional arguments:
  -h, --help            show this help message and exit
  -u {imperial,metric}, --unit {imperial,metric}
                        Source unit type (imperial or metric)

$ ./test.py --unit metric aaa.csv aaa.pkl
metric
$ ./test.py -u imperial aaa.csv aaa.plk
imperial
$ ./test.py aaa.csv aaa.pkl
imperial
$ ./test.py --unit=other aaa.csv aaa.pkl
usage: test.py [-h] [-u {imperial,metric}] from_file to_file
test.py: error: argument -u/--unit: invalid choice: 'other' (choose from 'imperial', 'metric')

Upvotes: 1

Michael Ruth
Michael Ruth

Reputation: 3504

There are a few wrong things here but the root cause is that the code doesn't use sys.argv correctly. Try printing sys.argv:

if __name__ == "__main__":
    import sys
    print(sys.argv)
$ python3 argv.py imperial_units=True
['argv.py', 'imperial_units=True']

You need to parse the command line arguments.

if __name__ == "__main__":
    if len(sys.argv) <= 1 or sys.argv[1] == "imperial_units=True":
        log_to_pickle(True)
    else:
        log_to_pickle(False)

Take a look at the argparse package for more robust command line argument handling.

Upvotes: 4

flakes
flakes

Reputation: 23614

I would use argparse for this type of thing. Use a boolean flag argument. I also like to provide a positive and a negative version for ease of use.

import argparse

...

if __name__ == "__main__":
    parser = argparse.ArgumentParser(prog='log-to-pickle')

    parser.add_argument('--imperial-units', dest='imperial_units', action='store_true')
    parser.add_argument('--no-imperial-units', dest='imperial_units', action='store_false')
    parser.set_defaults(imperial_units=True)

    args = parser.parse_args()

    log_to_pickle(args.imperial_units)

Using argparse you'll get some nice help messages too:

$ python3 file.py -h
usage: log-to-pickle [-h] [--imperial-units] [--no-imperial-units]

optional arguments:
  -h, --help           show this help message and exit
  --imperial-units
  --no-imperial-units

Now you can call the application like this:

# These are the same given the default.
$ python3 file.py
$ python3 file.py --imperial-units

Or to do the negative version:

$ python3 file.py --no-imperial-units

If you really want to provide a string argument to the bool flag, then I would make a custom method to transform string to boolean:

import argparse


def bool_arg(val):
    return val.lower() in ('y', 'yes', 't', 'true', 'on', '1')


if __name__ == "__main__":
    parser = argparse.ArgumentParser(prog='log-to-pickle')

    parser.add_argument('--imperial-units', type=bool_arg, default=True)

    args = parser.parse_args()

    print(args.imperial_units)

Examples:

$ python3 file.py --imperial-units y
True

$ python3 file.py --imperial-units True
True

$ python3 file.py --imperial-units 0
False

Upvotes: 4

Henry
Henry

Reputation: 3942

sys.argv provides a list of arguments. You can iterate through it to find an argument that contains "imperial_units". You can then process this to get the boolean.

if __name__ == "__main__":
    #Find arguments which contain imperial_units
    arg = [x.split("=")[1] for x in sys.argv if "imperial_units" in x]
    #If the length is 0, no args were found, default to true
    #Otherwise use value after = and convert it to bool
    arg = True if len(arg) == 0 else bool(arg[0])
    log_to_pickle(arg)

Upvotes: 1

Related Questions