Skip to content
Courses

Python Scripts and Values

Programming FoundationsLesson 2 of 875 minutesLessonBuilds on: What Is a Program?No lab next

The question: what did the program receive?

Section titled “The question: what did the program receive?”

A program receives 12 from the terminal. Can it add 3 to that value? The printed characters don’t answer the question, because the terminal supplied text. We need to see what arrived before deciding what the program can do with it.

Let’s work in a dedicated directory again:

Terminal window
mkdir -p ~/underdunn-course/exercises/python-values
cd ~/underdunn-course/exercises/python-values

Python treats 12 as an integer you can do arithmetic with. "12" is a string holding two characters. A type describes the kind of value and the operations available on it. They may look the same when printed, but that doesn’t make them interchangeable.

Create values.py:

The first three lines below each associate a name with a value. 12 is an integer literal, "files" is a string literal, and False is a Boolean value: one of Python’s two truth values, True and False. Their capitalization matters. We’ll use Booleans to control decisions in the next lesson; for now, notice that they have a type of their own.

type(count) asks Python which type the current value has. In print(type(count), count), Python first obtains that type and the value, then passes both to print. The comma separates the two things to display, and print puts a space between them by default. It isn’t printing the word count, because that name has no quotation marks around it.

count = 12
label = "files"
complete = False
print(type(count), count)
print(type(label), label)
print(type(complete), complete)

Run it with python3 values.py. A variable is a name bound to a value. Python keeps track of the value’s type while the program runs; the name isn’t permanently restricted to that type.

Expected output begins like this:

<class 'int'> 12
<class 'str'> files
<class 'bool'> False

Notice the type belongs to the value rather than to the name. count currently refers to an integer, and Python will happily let a later assignment point that same name at a string instead.

Read an assignment from the right-hand side first. Python evaluates that expression, then binds the name on the left to its result. So count = count + 1 reads the old value, calculates a new integer, and updates what count refers to. It isn’t an equation claiming a number equals itself plus one.

Try that distinction at the end of values.py: assign saved = count, then count = count + 1, and print both names. Predict which one changes before running it. saved still refers to 12 while count now refers to 13. The assignment didn’t create a live formula connecting the two names. Remove these extra lines after comparing the result.

Keep Python’s built-in types nearby when a value behaves differently from its printed form. The sequence and numeric sections describe the operations we’ll use here.

Suppose we want to know whether a count is twelve. Assigning twelve would destroy the value we were trying to check. We need an operation that asks a question while leaving the value available, which is why assignment and equality have different notation.

count = 12 assigns a value to a name, while count == 12 compares two values and produces the Boolean True or False.

count = 12
print(count == 12)
print(count == "12")

Predict both lines before you run them. The second comparison comes back false: the string "12" is not the integer 12. Python doesn’t convert the text to an integer just to make this comparison succeed.

Change count to "12" and predict what count + 3 will do before trying it. Read the TypeError carefully. It names the operation and the incompatible types, which gives us a much more useful starting point than “the program broke.” Put the integer back afterward.

Strings and lists are both ordered sequences, and index zero selects the first item in either of them:

A list groups several values into one ordered collection. The square brackets in [22, 80, 443] create the list, and commas separate its elements. Square brackets after a name do a different job: ports[0] selects one element from the existing list. The index describes a position, not the number stored there.

Save the next example as a separate sequences.py and run it with python3 sequences.py. Add the following list and string experiments to that file in order. Keeping these experiments separate from values.py makes it easier to tell which assignments a run actually executed.

label = "files"
ports = [22, 80, 443]
print(label[0], label[-1], len(label))
print(ports[0], ports[-1], len(ports))

Expected output:

f s 5
22 443 3

A negative index counts back from the end, and len reports the number of elements, which here means characters for the string and integer items for the list.

Lists are mutable, meaning an element can be replaced in place:

For ports, index 1 is the middle element, currently 80. Assigning to that position changes the existing list; it doesn’t assign a completely different list to the name ports. The element count remains three. This differs from the earlier integer example, where assigning a new value changed what one name referred to.

ports[1] = 8080
print(ports)

Strings are immutable, so label[0] = "F" raises a TypeError instead. To get different text out of one, build a brand new string and then bind the old name to it:

label = "F" + label[1:]

label[1:] is a slice running from index one through to the end. Don’t try to memorize every slice form right now. The useful point here is simply that indexing can fail at a boundary. ports[3] raises an IndexError, since the only valid indexes here are 0, 1, and 2.

Here the slice produces "iles", the + combines it with "F", and only then does the assignment make label refer to "Files". The old string was never modified. Run intentional errors one at a time at the end of the file, then remove them before continuing: an unhandled error stops the run, so later print statements won’t help you inspect what happened.

There’s a useful difference between asking for one position and asking for a slice. ports[3] requires a fourth element that doesn’t exist, while ports[3:] asks for the remaining elements starting there and produces an empty list. An empty result and a failed lookup are different observations. Python’s sequence rules describe these boundaries when you need to check another index or slice.

Create arguments.py:

import sys
print("program:", sys.argv[0])
print("arguments:", sys.argv[1:])

Run:

Terminal window
python3 arguments.py alpha 27

sys.argv is a list of strings. Index zero identifies the script itself, and every later entry holds whatever argument was supplied in that position on the command line. Even 27 arrives as text, and that catches a lot of programs out.

import sys makes Python’s sys module available under that name. A module groups related Python facilities; sys exposes information about the interpreter and its environment. The dot in sys.argv selects the argument list provided by that module.

For this invocation, the list contains "arguments.py", "alpha", and "27", in that order. The slice [1:] skips the script name and keeps the supplied arguments. This is the Python side of the shell argument boundary you examined in Linux Foundations. The shell decides which arguments arrive, and Python code decides how to interpret their text.

Command-line input begins as text and must be checked before deliberate conversion
Terminal"27"string
Check shapelen(sys.argv)does index 1 exist?
Convertint(text)valid integer text?
Program value27integer

Replace arguments.py with this version to convert the first supplied argument deliberately:

import sys
count = int(sys.argv[1])
print("next:", count + 1)

Try it with no argument, then with 12, then with twelve. Check the IndexError and the ValueError when they turn up. Each failure is pointing at a different assumption the program made and then couldn’t support.

Follow the conversion without hiding the error

Section titled “Follow the conversion without hiding the error”

Replace the whole of arguments.py with this complete file:

import sys
text = sys.argv[1]
count = int(text)
print("text:", repr(text), type(text))
print("count:", count, type(count))
print("next:", count + 1)

repr shows a representation of the value, including quotes around a string. It helps distinguish the text that arrived from the integer created by conversion.

Predict the output for python3 arguments.py 12, then run it. The text remains a string after int creates the integer; conversion hasn’t changed the original value. Now predict and run python3 arguments.py twelve, followed by a run with no argument. Compare the final error line from each run.

The missing argument fails at indexing. The word twelve reaches the conversion and fails there. Neither run reaches the print statements. These failures identify two separate assumptions in the program.

Follow the successful case through the same operations. Indexing retrieves the string "12"; int(text) reads that string as an integer representation and produces 12; adding one produces 13. Conversion doesn’t decide whether twelve is an acceptable count for your application. It only establishes that the text can represent an integer. A permitted range is another question, which needs the decisions we’ll introduce next.

Modification exercise: describe one argument

Section titled “Modification exercise: describe one argument”

Replace the file with a version that takes two arguments: a label at index 1 and a count at index 2. Print the label, the converted count, and the count plus one using the assignments and print calls you’ve already used.

Try widgets 4, "" 4, widgets -1, a missing count, and widgets four. Predict which operation fails, if any, before each run. A negative integer is still an integer, so this version accepts it. We’ll add decisions for permitted ranges in the next lesson and handle expected exceptions once functions and errors have been introduced.

You’re done when you can explain the difference between a missing value, failed conversion, and a converted value a program might choose to reject. Restore the complete one-argument file before the fresh-case check.

  • IndexError: list index out of range: the program used an index that isn’t there. Print len(sys.argv) and repr(sys.argv), then supply the missing argument and repeat the same command.
  • ValueError: invalid literal for int(): the required argument exists, and its text isn’t something int will accept. Inspect the original string with repr before conversion if whitespace might be involved.
  • The shell always shows status 0: $? belongs to the most recently completed command. Print it immediately after the Python command, before you run echo, ls, or anything else at all.
  • Your working copy is thoroughly tangled: restore the last working version from your editor history, or just recreate the complete example in a new file. Change one assignment or input at a time after that.

Compare the three argument runs. 12 can be converted to an integer; twelve reaches the conversion and raises ValueError; a missing argument raises IndexError before conversion. If yours differs, inspect repr(sys.argv) to see what the program actually received.

You can now identify the values entering a program, track their types, and spot the assumptions a conversion quietly introduces. Next we’ll follow those same values through decisions and repeated work.

Knowledge check

Question 1 of 2

The argument "007" reaches int(text). What values exist afterward?