Why it matters
Many programs need input before they start. A tool that compresses a file needs the file name. A script that checks a score needs the score. You can ask the user for these values while the program runs, which you will learn with input() in another lesson, or you can take them from the line the user typed to start the program. The second way is called command-line arguments. An argument is one word, or one quoted group of words, that follows the script name.
Arguments are common in tests and scripts. A judge or a build tool often runs your program as python3 main.py 5 12, with no prompts in between. If your program reads the arguments wrongly, it fails in a way that is hard to see. If it reads them carefully, it prints a clear message and stops.
This lesson covers three steps: reading the arguments, turning text into numbers, and checking that the numbers are usable. It uses the basic types from the variables lessons and the if and try statements from earlier in the course.
The idea: words typed after the name
Start with a program that does nothing except show what it received. Save this file as echo.py:
import sys
print("len(sys.argv) =", len(sys.argv))
for i, text in enumerate(sys.argv):
print(f"sys.argv[{i}] = {text!r}")The module sys holds the list sys.argv. Python fills it before your code runs. Each word typed after the script name becomes one entry in the list. The !r in the f-string shows each entry in quotes, so an empty entry is visible.
Run the file three ways. Here is what the program printed when it was tested:
$ python3 echo.py
len(sys.argv) = 1
sys.argv[0] = 'echo.py'
$ python3 echo.py 3 "hello world" 4.5
len(sys.argv) = 4
sys.argv[0] = 'echo.py'
sys.argv[1] = '3'
sys.argv[2] = 'hello world'
sys.argv[3] = '4.5'
$ python3 echo.py "" " 7"
len(sys.argv) = 3
sys.argv[0] = 'echo.py'
sys.argv[1] = ''
sys.argv[2] = ' 7'Several facts are visible in this output.
- The list always has at least one entry.
sys.argv[0]is the script name, so with no arguments the length is 1. The number of real arguments islen(sys.argv) - 1. - The first real argument is
sys.argv[1]. In Java,args[0]is the first argument, and the program name is not in the list. Python includes the script name, so the indexes are one higher. - The shell splits the line on spaces. The text
"hello world"in quotes stays as one argument. Without quotes,hello worldwould be two arguments. - An empty pair of quotes is still an argument. The empty text
""takes a slot in the list, and so does" 7", which keeps its leading space. - Every argument is text, a
str. The4.5in the output is not a number yet. It is three characters, and Python does not know it is a number until you convert it.
The shell is the program that reads what you type in the terminal. It does the splitting and the quote handling before Python sees anything. Python only receives the finished list of words.
Turning text into numbers
Arguments arrive as text, so you need a conversion step before arithmetic. For whole numbers, use int. The word parse means to read text and turn it into a value. The function takes a str and returns an int:
43The text "42" became the number 42, and the addition then worked as a number.
The conversion can fail. If the text does not look like a whole number, int raises an exception. An exception is Python's signal that something went wrong during the run. It stops the normal flow of the code and jumps to a handler, or, if nothing handles it, the program ends with an error message. The exception type for a bad number is ValueError.
The course ran the same conversion on a set of inputs. Each line below shows the text and what int returned.
' 42' 42
'+5' 5
'-17' -17
'4.5' ValueError: invalid literal for int() with base 10: '4.5'
'' ValueError: invalid literal for int() with base 10: ''
'abc' ValueError: invalid literal for int() with base 10: 'abc'
'2147483648' 2147483648
'0x1F' ValueError: invalid literal for int() with base 10: '0x1F'
'1_000' 1000
'42 ' 42Read the table for the patterns:
- A plus or minus sign at the start is allowed.
- Spaces at either end are allowed, so
" 42"and"42 "both convert. Java'sInteger.parseIntrejects them, so this is a difference to remember when you read Java code. - Underscores are allowed between digits, so
"1_000"is 1000. This is the same rule as the digit separator in source code. - A decimal point is not allowed. Use
floatfor numbers like4.5. - The prefix
0xis not allowed in base 10. To read hexadecimal text, pass the base:int("1F", 16)is 31. - Python
inthas no upper limit, so"2147483648"converts. A fixed-size language would reject it.
The float function accepts a decimal point, and it also accepts spaces at the ends:
4.5
could not convert string to float: 'four'The first line came from " 4.5 " with spaces at both ends. The second line is the error for "four".
Example 1: a greeting with a usage check
The first example is short. It takes one argument, a name, and prints a greeting. Its job is also to print a usage line, a short message that shows what to type, when the argument is missing. The file is saved as greet.py.
import sys
if len(sys.argv) < 2:
print("usage: python3 greet.py <name>")
sys.exit(1)
name = sys.argv[1]
print(f"Hello, {name}!")The check len(sys.argv) < 2 must come before any use of sys.argv[1]. The reason is that sys.argv[1] on a one-entry list is an error, as the next sections show. The function sys.exit(1) ends the program right away and gives the operating system the number 1. By convention, 0 means success and any other number means an error. A script that runs the program can read that number.
The course ran it three times. Here is the output:
$ python3 greet.py
usage: python3 greet.py <name>
$ python3 greet.py Ana
Hello, Ana!
$ python3 greet.py Ana Lee
Hello, Ana!The third run gave two arguments, Ana and Lee. The program reads only sys.argv[1] and ignores the rest. A program that expects one value does not have to reject extra values, but it should say so if the extra values matter. The next example checks the count exactly.
Example 2: adding two numbers
The second example reads numbers from the arguments and adds them. It uses a loop over the arguments after the script name, so the same code works for any count. It also prints a usage line when the count is wrong, and it reports bad entries without stopping. The file is saved as sum_args.py.
import sys
if len(sys.argv) != 3:
print("usage: python3 sum_args.py <number> <number>")
sys.exit(1)
total = 0
for text in sys.argv[1:]:
try:
value = int(text)
total += value
print(f"{text} -> {value}")
except ValueError as error:
print(f"not a whole number: {error}")
print(f"total = {total}")Three parts of this program need a note.
- The slice
sys.argv[1:], a part of a list written with a colon, takes every entry from position 1 to the end, which is the list of real arguments. - The
tryblock holds code that might raise. Theexceptblock handles the exception if one happens. Without theexcept, the bad text would end the program with a long error message. - The variable
errorholds the exception object, and the f-string prints its message.
Here are two runs. The first gives the wrong count, so it prints the usage line and exits with code 1. The second gives one valid number and one bad one.
$ python3 sum_args.py 1
usage: python3 sum_args.py <number> <number>
exit=1
$ python3 sum_args.py 10 x
10 -> 10
not a whole number: invalid literal for int() with base 10: 'x'
total = 10
exit=0The exit code is 1 in the first run because the program called sys.exit(1). You can see it in a shell with echo $? on Linux and macOS.
The second run shows the design choice. The program reports x and keeps going, so total is the sum of the good values only. Whether that is correct depends on the task. In a real tool you might stop instead. The point of the example is that the conversion error is caught and printed, not that this is the only correct behaviour.
Example 3: checking ranges
Parsing text tells you the text is a whole number. It does not tell you the number is sensible. An age of -4 is a whole number but not a valid age. The third example adds a range check, uses strip(), and collects the good values in a list. The file is saved as ages.py.
import sys
if len(sys.argv) == 1:
print("usage: python3 ages.py <age> [<age> ...]")
sys.exit(0)
valid = []
for text in sys.argv[1:]:
try:
age = int(text.strip())
except ValueError:
print(f'skipped "{text}": not a whole number')
continue
if age < 0 or age > 150:
print(f"skipped {age}: outside 0 to 150")
continue
valid.append(age)
print("valid ages:", valid)
if valid:
print("average:", sum(valid) / len(valid))This example has the most moving parts, so read it in layers.
text.strip()removes the spaces at each end before the conversion. Python'sintalready accepts those spaces, so the call only makes the intent clear.- The
continuein theexceptblock skips to the next argument, so the range check never runs with anagethat was not set. - The list
validholds the good values in the order they were typed.valid.append(age)adds one value at the end. sum(valid) / len(valid)divides as decimals, because/always gives a float in Python 3. The integer division//would drop the fraction, so53 // 2would give 26 and53 / 2gives 26.5.
The course ran this with a mix of inputs:
$ python3 ages.py 20 abc -4 151 " 33 "
skipped "abc": not a whole number
skipped -4: outside 0 to 150
skipped 151: outside 0 to 150
valid ages: [20, 33]
average: 26.5
$ python3 ages.py
usage: python3 ages.py <age> [<age> ...]Each bad argument is handled at its own point, and the good ones still count. A user can see exactly which entry went wrong.
What happens without a check
Errors can also be left unhandled. Here is a short program with no checks at all, saved as crash.py:
import sys
n = int(sys.argv[1])
print(n)With no arguments, the program fails before the conversion runs. Here is the output the course captured:
$ python3 crash.py
Traceback (most recent call last):
File ".../crash.py", line 3, in <module>
n = int(sys.argv[1])
~~~~~~~~^^^
IndexError: list index out of rangeWith a text that is not a number, the failure is at the conversion:
$ python3 crash.py twelve
Traceback (most recent call last):
File ".../crash.py", line 3, in <module>
n = int(sys.argv[1])
ValueError: invalid literal for int() with base 10: 'twelve'Read the last line first. It names the exception type and the reason. The line above it, with the file name and line number, is the line in your code that failed. The ^ and ~ marks show which part of that line was involved. These messages are useful for finding bugs while you develop. They are not what a finished tool should show a user. The examples above add checks so that the user sees a short usage line instead.
Common mistakes
Treating sys.argv[0] as the first argument. sys.argv[0] is the script name. The first real argument is sys.argv[1]. A program that reads sys.argv[0] as a number fails with ValueError on the script name.
Reading an index before checking the count. Using sys.argv[1] when only the script name is there gives IndexError: list index out of range. Check len(sys.argv) first, as the examples do.
Parsing a decimal with int. The text 4.5 fails with ValueError: invalid literal for int() with base 10: '4.5'. Use float for numbers with a decimal point. The text 1,000 also fails, because the comma is not a digit. Remove the commas before the conversion.
Comparing text with the wrong operator. In Python, == compares the letters of two strings, so sys.argv[1] == "yes" is True when the argument is yes. The mistake to avoid is is. It compares objects, and it can give False for two equal strings that were built separately.
Forgetting that quotes group words. A user who types python3 greet.py Ana Lee gets two arguments. Your program sees "Ana" and "Lee", not one name. If the name should be one value, the user must type python3 greet.py "Ana Lee". The program cannot tell the difference after the shell has split the line, so document the expected form in the usage line.
Assuming a bad number is zero. Some languages turn a bad number into 0. Python does not. The conversion raises ValueError, so you must catch it or stop. Catching and then using 0 as a default is allowed, but make it a decision you write down, not a side effect.
Assuming a whole number is in range. int("-4") works and is still wrong for an age. A conversion proves the text is a number of the right kind. Your own checks prove the value makes sense.
Practice
Work each exercise yourself before you open the answer. Run each program with the arguments named in the exercise.
- Write a program
echo_args.pythat prints every argument on its own line, each prefixed with its position, starting at 1. Run it withpython3 echo_args.py a "b c". - Write
twice_it.pythat prints twice the value of its first argument. If there is no argument, printusage: python3 twice_it.py <whole number>and exit with code 1. If the argument is not a whole number, printnot a whole numberand exit with code 2. - Write
scores.pythat takes any number of arguments. Each argument is a score. Count how many are valid, where valid means a whole number from 0 to 100. Print the count of valid scores and the count of invalid ones. Do not stop at the first bad value. - A program is run with
python3 pair.py 3 4 5. It needs exactly two numbers. Write the check that printsusage: python3 pair.py <a> <b>and exits with code 1 when the count is wrong. Then say why the check must come before any use ofsys.argv[2]. - A user types
python3 greet.py "Ana Lee"with two spaces between the names. Explain whatsys.argv[1]contains, and how the program can split it into the two names.
Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer.
- The loop prints the position and the argument. The position starts at 1, so
enumerategetsstart=1.
Show the answer
import sys
for i, text in enumerate(sys.argv[1:], start=1):
print(f"{i}: {text}")The output for python3 echo_args.py a "b c" is 1: a on the first line and 2: b c on the second.
- The first check handles the missing argument, and the
tryhandles the bad one. Each path ends with the right exit code.
Show the answer
import sys
if len(sys.argv) < 2:
print("usage: python3 twice_it.py <whole number>")
sys.exit(1)
try:
value = int(sys.argv[1])
except ValueError:
print("not a whole number")
sys.exit(2)
print(value * 2)Running python3 twice_it.py 21 prints 42. Running python3 twice_it.py x prints not a whole number and exits with code 2.
- Count the valid and invalid values in one loop. A bad value adds to the invalid count and does not stop the loop.
Show the answer
import sys
valid = 0
invalid = 0
for text in sys.argv[1:]:
try:
score = int(text.strip())
except ValueError:
invalid += 1
continue
if 0 <= score <= 100:
valid += 1
else:
invalid += 1
print("valid:", valid)
print("invalid:", invalid)Run with python3 scores.py 50 101 abc 0, the output is valid: 2 and invalid: 2.
- The check uses the length of
sys.argv, which includes the script name. It must come first, becausesys.argv[2]on a list with fewer than three entries raisesIndexError.
Show the answer
import sys
if len(sys.argv) != 3:
print("usage: python3 pair.py <a> <b>")
sys.exit(1)
a = int(sys.argv[1])
b = int(sys.argv[2])
print(a + b)The run python3 pair.py 3 4 5 has four entries in sys.argv, so the check fails and the program prints the usage line and exits with code 1. The check guards the reads, and it is only useful if it runs before them.
sys.argv[1]holds the whole textAna Leewith both spaces kept, because the quotes group it into one argument. The program can split it at one or more spaces withsplit(), which takes any run of whitespace as one separator.
Show the answer
import sys
first, last = sys.argv[1].split()
print(first)
print(last)The output is Ana and then Lee. The call split() with no argument removes the extra spaces, so the two names come out clean.
What you now know
- Command-line arguments are the words typed after the script name, and Python gives them to the program as the list
sys.argv. sys.argv[0]is the script name. The first real argument issys.argv[1], andlen(sys.argv) - 1is the number of arguments.- Arguments are text. Convert them with
intorfloat, andstrip()the text first if spaces may be present. - A failed conversion raises
ValueError. Catch it, or stop with a usage message, instead of letting the program crash. - Check
len(sys.argv)before readingsys.argv[i], and check that each converted value is in the range the task allows. - Use
sys.exitwith a nonzero number for an error. Compare text with==, notis.