Lesson 5: Input and Output, Decision-making, and Other Needs

Last time we handled basic types; now, we're going to get into the meat of programming. We'll start by handling input from the console and explaining how to make output more powerful. We also briefly talk about error handling. Then, we'll explain how to make decisions based on that user input, and what to do when a user's input is wrong without crashing the program. Finally, we'll cover some oddities of how variables act depending on indentation level.

Input

The input() function takes a string as an argument, and prints that as a line, then waits for the user to type something in. Whatever they type becomes a string, which is the value input() returns. So, using what we know already, we can make a simple program that takes whatever the user inputs, and then prints it back to the user.

text = input("Gimme some text: ")
print("You typed: " + text)

Now we can take input from the user! This will prove very useful.

print() in Detail

The print() function is much more complex than we've been letting on. For instance, the above program uses

print("You typed: " + text)

but we could have instead used

print("You typed:", text)

The first part of the print() function takes a set of objects as arguments; they need not be strings, as each argument has an implicit str() wrapped around it. The line will put all those strings together, with the separator (given by keyword argument "sep") in between, and place the ending (given by keyword "end") after the strings.

Normally, the separator sep=" ", so spaces will be put between the arguments. Also, the end="\n" which is the "newline" character, meaning print() will produce one line, and more output will be on the next line after that.

So, if you wanted to stick an exclamation point after the text, you could have

print("You typed: ", text, "!", sep="")

This replaces the separator with the empty string "", so there will be no space between the user input text and the exclamation point.

If you do the same thing with the end keyword, you get rid of the newline character at the end of the print statement. Usually you don't want to do this.

String Formatting

For output arrangement, you also have the option of "string formatting." This is where you put in blanks that are meant to be filled in by entries. This is done to the string object itself. For instance, if you wanted to make a string like the output from before, you could write:

"You typed: {}".format(text)

Thus to print this you would write

print("You typed: {}".format(text))

Anywhere {} appears is treated as a "blank" to be filled, and you can enter multiple objects to the string's .format() function; though since this is a function given for an object instance, it's more properly called a "method." We'll get into how methods work much later, but for now know that they're attached to objects, and you access them using the dot operator as above.

An example with multiple objects, assuming you have entries for x, y, and z from earlier in your code:

"The entry has coordinates of [{}, {}, {}]".format(x,y,z)

There is also a mini-language associated with the format specifier, which you can use to play with some options. For instance, if you wanted to store USA zip codes as integers instead of strings, you could zero-pad for zip codes with 0 as the first digit, like so:

"{:05}".format(zip)

The above contains a : inside its format blank, which makes Python parse the rest of the blank up to the } using its formatting language. The leading 0 tells it to zero-pad, and the 5 gives it the width of the string to fill. Thus, if zip were assigned to the University of Maine's Orono zip code, via the integer 4469, the resulting output would be

04469

The string .format() function is very powerful, and has many more options than we're covering here. For more information on how to use this method, see https://docs.python.org/3/library/string.html#format-string-syntax

If you don't need to do tons of these zero-pads at once, you might find the .zfill() method more useful. This is a string method, and it takes an integer as an argument. If the len(string) is a smaller value than that argument, .zfill() returns a string with "0" characters filling the left-hand side, followed by the contents of the original string, to exactly meet that length in characters.

>>> str(zip).zfill(5)
'04469'

Getting Numbers from Input

So, there's a problem with just using input gained from the console like this. You can easily see this problem if you try making a program that sums two numbers together.

x = input("First number: ")
y = input("Second number: ")
print("The sum of ", x, " and ", y, " is ", x+y, ".", sep="")

This will just give the two strings the user gave us, stuck together. String concatenation is not what we want here, we want to add the two numbers! But these are not numbers. So how do we fix this? We need to convert them.

x = float(input("First number: "))
y = float(input("Second number: "))
print("The sum of ", x, " and ", y, " is ", x+y, ".", sep="")

Now, you'll see that it works as expected!

(The tutorial I used to check this code uses int() instead of float(), and you can if you want. I prefer to leave options open. Plus, it lets you enter numbers with decimals without getting an error!)

If statements

So, we can take input, act on it, and produce output. But how about making decisions based on that input? This is where if statements come in.

An if statement takes a boolean expression, and if that statement is true, it will execute the code in the if block.

if x + y >= 10:
print("Hey, that's not a bad sum.")

Notice that the block is indented, that is, it's not at the beginning of the line. If there were multiple lines at that same indentation level (same number of spaces or tabs at the start), they would be executed in sequence as long as the boolean expression is true.

If the expression is false, the block below the if will not be executed; however, if there is an else block below, that will be executed instead.

if x + y >= 10:
print("Hey, that's not a bad sum.")
else:
print("It's OK, I guess.")

Finally, if you want another condition to test, you can use "elif" as the name of a block under an if block, to add a test that will be checked only if the condition(s) above it are false. elif stands for "else if". You can chain multiple elif blocks in this manner; the if block will be checked first, then the next elif, and so on until the end. As with a plain if block, you can put an else statement and block after the last elif block if desired; it will only be performed if none of the if or elif statements have a True condition.

if x + y >= 1000:
print("Wow that's huge!")
elif x + y >= 100:
print("Impressive!")
elif x + y >= 10:
print("Hey, that's not a bad sum.")
else:
print("It's OK, I guess.")

You can technically indent blocks like this with either spaces or tabs. The only real rule is to pick either tabs or spaces, and stick to it, because both you and the interpreter will get confused if you mix them together.

That said, I highly recommend spaces. Why? Because tabs don't have a set size to appear to everyone. On some text editors, a tab might appear to be four spaces. On another, it may appear to be eight spaces instead. This is a potential nightmare for programming in a team, or even for going back to your old code after a few months or years. If you use spaces to indent, there's no ambiguity about how far you're indented.

Boolean Operations

if statements don't take just a single comparison or boolean as an expression, of course. You can also string a few booleans together with operators! There are three boolean operators, and, or and not. Their evaluation order is special, and will be explained in a moment.

x or y: if x is false, then value is y, else value is x.
x and y: if x is false, then value is x, else value is y.
not x: value is the opposite of x, i.e. if x is false, then True, else False.

The above are in ascending order of priority, i.e. not is applied first, then and pairs are checked, then or pairs. That said, the order in which the expressions inside are tested is important.

When or is encountered, the left side expression must resolve before the right hand will be tested at all. If the x expression is true, the y expression never gets tested, because we already know the whole result will be True. Thus, any side-effects of executing that y expression never happen.

When and is encountered, the left side expression must also be tested first. In this case, if the x expression is false, the y expression never gets tested, because we know the answer will be false.

The above conditions are called "short-circuiting" because the earlier tests cancel out the later tests that would be made, and code that doesn't need to be executed to get the boolean result is simply skipped. This is very useful for making efficient code, and also for some advanced tricks with function side effects. Their full uses will come to you in time.

As with other operators, the order of operations can be altered by putting parentheses around part of the expression. Contents of parentheses are always executed before what's found outside, unless short-circuiting lets Python skip that code altogether.

Errors and Exceptions

So, we now have a good primer on the basics of programming; however, there's still a problem to handle. What happens if the user enters something unexpected, in the above programs? Well, the result is not good. Let's go back to the sum-of-two-numbers program from before, for simplicity's sake. If we enter something that isn't a number (at least, to Python), we get this:

First number: three
Traceback (most recent call last):
File "<pyshell#71>", line 1, in <module>
main()
File "<pyshell#68>", line 2, in main
x = float(input("First number: "))
ValueError: could not convert string to float: 'three'

(Don't worry about "main" up there, it's a function I used to simplify things in testing. We'll cover how to create functions in Lesson 9.)

Obviously, Python does't know how to convert a written name for a number into a number. You would get this error for anything else that isn't a number in digits. So how do we prevent this from crashing our program?

Welcome to Exception Handling! We promise it won't hurt, much.

First, we need to put a try statement before the block we want to test for exceptions. Any exception that occurs will abort the block, but will check the except blocks after it before giving up. The error screen above only shows up for unhandled exceptions.

So, to handle this situation, we could do:

try:
x = input("First number: ")
y = input("Second number: ")
print("The sum of ", x, " and ", y, " is ", x+y, ".", sep="")
except ValueError:
print("That's not a number! Can't continue.")

The except statement takes a single error type, or a tuple of error types (don't worry about what a tuple is, it's just a set of things in () round brackets, separated by commas). Error types will turn up when you test out how statements can fail, for instance, in the command line or REPL. All exception error types are listed in the documentation at https://docs.python.org/3/library/exceptions.html#bltin-exceptions

You can also give a bare except statement by just typing except: for its line. This will capture all exceptions, regardless of what the error type is. However, you most likely want to just use the Exception type, since that covers all error types.

If you want to keep the value of an exception to use in the statement, you can write it as

except Exception as e:

where e can be any name you want to give the variable. You can ask for various information from this error object, or you can just cast it to a string or print it out. This might not be very useful to you right now, but you can probably think of some uses for it in time.

Similar to elif statements, you can chain multiple except statements, and the first one that matches the error type of the current exception will cause its block to execute.

Note that the above solution does avoid an unhandled exception, but it doesn't entirely solve our problem. If the user enters the wrong thing, the exception goes off and the program stops running. We'd have to re-run the program to try again. We can fix that with a loop, but that'll be something to cover in Lesson 7. For now, we're done with the lesson; it's time for you to show what you've learned.

Further Information:

While this won't be very useful for most students, there are some powerful operators we can use only on integers, and these let us perform logical operations on each bit in one integer with the corresponding bit in another integer. Bitwise operators are performed after any arithmetic operations, but before comparisons.

What do we mean by "operations on each bit"? Well, if we assume 8 bit numbers (because those are nice and short) we can take, say, x = 0b00101100 and y = 0b00100011, and if we were to "or" these bitwise, we take any bit position where either x or y is 1, and the resulting number will have a 1 there. Where both bits are 0, that place would have a 0.

>>> x | y
47
>>> bin(x | y)
0b101111

Which is the same as 0b00101111, which is what we would expect.

For reference, the bin() function takes an integer as its single argument, and outputs the string that would be its binary representation as a Python number.

The bitwise operators are as follows, in ascending order of priority:

x | y : bitwise or; bits with either x or y as 1 will result in a 1, else 0.
x ^ y : bitwise exclusive or, also known as xor; bits that are the same for x and y give 0, different will give 1.
x & y : bitwise and; bits where both x and y are 1 will result in a 1, else 0.
x << n : gives x shifted left by n bits. Fills in the right side with 0s.
x >> n : gives x shifted right by n bits. Fills in the left side with 0s.
~x : gives the bits of x inverted, so all 1s become 0s and all 0s become 1s. ~x + 1 == -x, by definition, because of two's complement negative integers.

The bottom-most bitwise operations will be performed first in this set, followed by those above in the order given. Thus, inversion is performed before any other bitwise operators, then bit-shifts, then and, then xor, then or.

Bitwise operators see less usage nowadays outside of cryptography, emulating old computers, or artistic purposes, but they used to be very common for storing a large number of boolean flags in a tight space. Some drivers and game engines still use bit flags, and pygame is among them, so we may come back to these when we get to that. The boolean type in Python is a single number, and its size depends on implementation, but is often the same size as any other integer.

While it's tempting to try to use bitwise operations to store a lot of booleans like this, keep in mind that such "bit flag" integers can be difficult to work with, and costly in terms of compute time compared to booleans. Basically, if you need to store a huge pile of booleans in a file, bit flags will do the trick, but for live code that's already loaded, you probably want booleans.

Exercise: Programming Assignment L5

Produce a program that asks the user for a number greater than 100. The program should do the following:

  1. if it received a number less than 100, it should tell the user the number was too small.

  2. if it received a number equal to 100, it should remind the user it wanted one greater than 100.

  3. if it received a number greater than 100, it should thank the user!

  4. if it received something that isn't a number at all (as far as Python understands numbers), it should say that's not a number!

Examples runs of such a program are given below for the above four cases.

Give me a number greater than 100: 90
Too small!
Give me a number greater than 100: 100
Nice, but I want one greater than 100.
Give me a number greater than 100: 1000.7
Thank you!
Give me a number greater than 100: infinity plus one
That's not a number!
Previous
Next