Chp 1: Expressions, Names, Types, Bugs

Reminders: Language, IDE

A few reminders from the Introduction  before we begin work.

A Console Session

The standard Python console prompt is >>>. I typed each expression after the prompts below; Python replied on the line immediately after. I typed 2 + 3. Python replied 5. I typed 2 - 3. Python replied -1. Etc. Give it a try.

>>> 2 + 3

5

>>> 2 - 3

-1

>>> 2 * 3

6

>>> 2 / 3

0.6666666666666666

>>> 2 ** 3

8

>>> 2 ** 4

16

>>> 3 ** 2

9

>>> 4 ** 2

16

>>> 2 + 3 * 4

14

>>> (2 + 3) * 4

20

>>> 2 * (3 + (4 - 5))

4

>>> 12 // 4

3

>>> 12 // 5

2

>>> 12 % 4

0

>>> 12 % 5

2

The Python console is a fine little calculator!

Mathematical Operators and Order of Operations

Python knows all the common mathematical operators. We'll begin with addition, subtraction, multiplication, division and the quotient and remainder operators. (I'll only introduce these here. We'll have to say more about some later, for some are a bit more complex that they'll see at first.)

The obvious: + is addition, - is subtraction, * is multiplication,  / is  division .

The non-obvious:  ** is exponentiation,  and // and % are the integer division and modulus operators respectively. Remember long division?  a // b is the quotient and a % b the remainder; that is, a // b is the number of whole times that b goes into a, and a % b is the remainder when a is divided by b. For instance, if we divide 48 by 13, we get a quotient of 3 and a remainder of 9. So 48 // 13 is 3 and 48 % 13 is 9.  These are immensely important operations. You'll be surprised.  (Are you surprised, or annoyed, that the symbol % does not mean percent? Why would Python change what % means? It seems perverse. The problem is that the creators of Python, in their great wisdom, wanted to use only the symbols available on a standard keyboard. This inevitably means that certain symbols will have to be repurposed.)

Python follows the usual order of operations. Parentheses override that order. Note that unlike in mathematics, we cannot use brackets in place of parentheses. (If you were to try [2 + 3] * 4, you'll get a result that seems most strange. If you were to try [2 + 3] / 4, Python would throw a fit. Here's what it said to me: TypeError: unsupported operand type(s) for /: 'list' and 'int'. More on all this later.)

More on // and %

I know from experience that students often don't understand at first what // and % really do. So let's pause and discuss them for a moment. (The discussion will be mathematical, but the application to Python is immediate.)

So, as I said, a // b is the number of whole times that b goes into a. Here's another way to think about that: a // b is the largest integer k such that b * k < a. Consider 13 // 5. What is the largest integer k such that 5 * k < 13? Well, 5 * 2 = 10 and  5 * 3 = 15, but 10 < 13 and 15 > 13. So the answer is 2! 13 // 5 = 2.

But of course 25 doesn't exactly divide 13. So there's some amount left over after we divide 13 by 5, and the amount left over is 3. This is the remainder, given in Python by the % operator. So 13 % 5 = 3.

Notice this lovely little equation: 13 = (13 // 5) * 5 + 13 % 5. (Work it out if you don't see it immediately.) This is one example of a general rule: for any integers a and b, a = (a // b) * b + a % b. That is, for any a and b, to get a, take the whole number of times that b goes into a, multiply that by b, and then add the remainder left when a is divided by b.

Before we leave // and % behind (I promise they'll come up again), notice this lovely little pattern for the example of division by 5:

0 % 5 = 0, 1 % 5 = 1, 2 % 5 = 2, 3 % 5 = 3, 4 % 5 = 4, 5 % 5 = 0, . . ., 9 % 5 = 5, 10 % 5 = 0, . . .

(You see, don't you, why 0 % 5 = 0? How many whole times does 5 go into 0? 0. How much is left over when we take out those 0 5's? 0. And why is 1 % 5 = 1? 0 whole 5's go into 1, and when we take out those 0 5's, we're left with a remainder of 1.)

Notice the pattern: 0 up to 4, then back to 0 again, then up to 4, then back to 0, etc. This patterns holds for any value of b. We begin at 0, go up to b - 1, then go back to 0 and continue on as before. So, if our b is 24 (and in the problem set, you'll see that value), the remainders begin at 0, go up to 23, drop to 0 again, etc.

Powers

I don't know how much you know. But I need you to know what I'm about to say, so please forgive me if you know it already. A number n raised to the fractional power p/q (where p and q are integers) is the qth root of n raised to the pth power. So for instance, 5**(3/7) is the seventh root of 5 cubed.

This gives us an easy way to take square roots. Simply raise to the 1/2 power! So if we wanted the square root of 101, we'd do:

>>> 101 ** (1/2)

10.04987562112089

Notice that the 1/2 has to be contained in parentheses. Why? Python follows the usual order of operations, which puts exponentiation before division. So if we did

>>> 101 ** 1/2

we'd first raise 101 to the first power, which is 101, and then we'd divide that by 2.  So we'd get:

50.5

That's not the square root of 101. That's 101 divided by 2.

Round

I'll mention one other nice built-in math function. You'll use it in the problem set for the chapter. It's the round function. Let's have some examples:

>>> round(1.4)

1

>>> round(1.6)

2

>>> round(2.4)

2

>>> round(2.6)

3

So, round apparently rounds to the nearest integer.

Note that way in which the function is used. It's function name, then open parenthesis, then input, then close parenthesis. This will be the most common way in which functions are called, both functions that are built in to Python and those we will write ourselves.

I said that round rounds to the nearest integer. But you have to be careful about that. For consider:

>>> round(1.5)

2

>>> round(2.5)

2

So 1.5 rounds up, but 2.5 rounds down. Curious. So what's the rule that Python uses? If the number ends in ".5", Python rounds in such a way that the final digit is even. (You'll want to know why it does that. For an answer, Google "bankers rounding".)

You can also use the built-in round function to round to a digit after the decimal point. To do so, give the round function a second input after the number you wish to round. That second input will tell Python how many digits after the decimal point to retain.

>>> round(1.23, 1)

1.2

>>> round(1.234, 2)

1.23

>>> round(1.2345, 3)

1.234

>>> round(1.23456, 4)

1.2346

More Math

So Python knows quite a bit of basic math. But what if you need more? Like say the log function. (Don't know what that is? Google it.) Can Python do log? Yes, yes it can; and lots more too. But those are extra functions. They don't come built in.

Where are they then? In a module. What's a module? A set of definitions that extends default Python. We'll see many modules before the class is done. (Inded we'll learn how to write and use our own.) What's the name of the module with more math? The name is "math". (Brilliant name, that.) How do we get the "math" module. With the import command. Like this:

>>> import math

After this line is executed, Python will then "know" all the functions in the math module. What math functions are in the math module? Google it; the search query should be "python math module". How do you use those extra math functions? The syntax is math.<function name>(<input>). (I use the angle brackets - the < and the > -  to describe a bit of code; they are not themselves used, but will be replaced by an example of that they describe.) Examples:

>>> import math

>>> math.sqrt(16)

4.0

>>> math.log(16)

2.772588722239781

>>> math.floor(16.1)

16

>>> math.ceil(16.1)

17

You should read math.sqrt as "the sqrt function contained in the math module"; and the complete line math.sqrt(16) is thus read as "the sqrt function contained in the math module applied to the number 15". So we now have a second way to take square roots!

What are math.floor and math.ceil? The idea behind them is really quite simple, and I'm confident that if you read the documentation you'll understand them immediately. Read the documentation.

No doubt you'll wonder why Python uses the dot syntax (math DOT sqrt) to access the sqrt function in the math module. My answer is two part: (i) it's clear, and (ii) it's tradition. Expect to see dots all throughout the course. I'll talk more about this later.

I know that this is quite a bit of information to take in here at the start of our study. But I did want you to know how to get these extra math functions; and I did want you do see the command that will do it.

Expressions and their Values

In the Python console sessions above, the console prompt >>> precedes expressions. Below the expression is its value. This is how the console works. It reads the expression contained in a line, evaluates it, prints the value that results to the screen, and then loops back to the prompt. Read-Evaluate-Print-Loop. REPL.

How do expressions become values? Substitution. For each expression (or sub-expression) we substitute its value; and we continue on until no non-trivial substitution can be done. (In a trivial substitution, we replace an object with itself.)

Example: for 2 * 2 we substitute 4.

Another: for (2 + 3) * 4, we first substitute 5 for 2 + 3 to get 5 * 4; and then for that we substitute 20.

A third: for 2 * (3 + (4 - 5)), we first substitute -1 for 4 - 5 to get 2 * (3 + -1); that becomes 2 * 2; and finally we have 4.

Literals

When after one or many substitutions no more can be done, what remains is a literal. 2 is a literal. 2 + 2 is not.

A literal is still an expression, and like all expressions it has a value. What's the value of a literal? Itself of course.

If you type a literal into the console, you just get it back again. Like this:

>>> 2

2

Names

We can name values if we want. Like this:

>>> a_name = 2

On the left is a name; on the right is an expression. The expression is evaluated and the name becomes the name of the value that results. Thus a_name names 2.

We can place a non-literal on the right if we want. Like this:

>>> another_name = 3 ** 4

We read from right to left. Evaluate the expression on the right, and then make the name on the left a name of the value that results. In this case, another_name becomes a name of 81.

Do be careful here. The = of Python is not the = of mathematics. The Python = is a process: evaluate the expression on the right, and then make the name on the left a name of the value that results. The = of mathematics is static. It says merely that the object named on the left and object named on the right are the same object.

So do not read Python's = as "equals". Instead read it as "is a name of", or if you like call it "the assignment operator".

(I know you're curious why we'd want to name values. Be patient! I'll explain in a moment.)

How to Spell a Name

We choose what names we'll use. But we can't spell them however we like. The rules:

Some examples and non-examples:

Variables

The traditionalists will call names variables. They will say that = is the assignment symbol (or assignment token). They will say that an expression of the form name = expression assigns a value to a variable and that the variable then holds that value. I'll speak that way too sometimes. Old habits die hard.

The Value of a Name

What's the point of all this? Why give names to values? So the values won't be lost! How do we recover the value named? Simply write the name! Watch:

>>> my_age_in_secs = 50 * 365 * 24 * 60 * 60

>>> my_age_in_secs

1576800000

The right side of the first line first computed my age in seconds. It then assigned that value (1576800000) to the variable my_age_in_secs. On the second line (>>> my_age_in_secs) we asked Python to give us the value of that name, and it dutifully returned the object that the name names.  So the value is remembered! Just use the name and it'll be replaced by its value in any expression that contains the name.

Remember this: the value of a variable is the value (sometimes called "object") it names. In variable-assignment talk, we say that the value of a variable is the object assigned to it.

Now you understand why we need names. We need to remember values. But honestly, if you're new to all this, you don't yet really understand just how powerful is this ability to give a name to a value. You'll see. All our code will be littered with value names.

Names in Expressions

As I said, the value of a name is the object that it names; or if you prefer, the value of a variable is the object assigned to it. Let's put this to use:

>>> my_age = 50

>>> days_in_a_year = 365

>>> secs_in_a_day = 24 * 60 * 60

>>> my_age_in_secs = my_age * days_in_a_year * secs_in_a_day

>>> my_age_in_secs

1576800000

Let's think this through.

Reassignment

We can change the object that a name names. Like this:

>>> a_var = 12

>>> a_var

12

>>> a_var = 12 * 12

>>> a_var

144

You might of course wonder why we'd want to do this. All will become clear in time. But know that it's common. Indeed it's nearly ubiquitous. Most programs update the values of variables.

Reassignment by Self

If we wish we can use a variable to update that variable. Consider:

>>> a_var = 12

>>> a_var = a_var + 1

>>> a_var

13

Once again we see that the = of Python is not the = of mathematics. Interpreted mathematically, line 2 above is a contradiction. It says that a certain number equals that number plus 1. But this isn't mathematics. It's Python; and as Python, it's a process, not a statement. We first evaluate the right. So a_var is replaced with its current value, which 12; and a 1 is then added to that 12 with the result of 13. Finally, the 13 is made the new value of a_var.

We say in such a case that we have incremented a_var by 1. We took its old value, added 1 and made the result its new value. Lines of code like this will prove quite useful in the future. (If we instead had decreased the value of the variable, we say that it has been decremented.)

Aliases

One object can have two names. (Why would you want that? Well now, that's a good question. We'll return to it later.)

>>> a_var = 12

>>> b_var = a_var

>>> a_var

12

>>> b_var

12

I'll say it again. (It's that important.) In a variable assignment statement (like the first two lines above), we first evaluate the right and then assign the object that results to the variable on the left. So on the second line above, b_var is assigned the object that's assigned to a_var, for the value of a variable is the object that it's been assigned. Thus b_var becomes an alias of a_var. Both name 12; and we say that b_var is an alias.

It's exactly the same with "Superman" and "Clark Kent". Those are two names of one man. a_var and b_var are two names of one number.

Classes

I've said a bit about expressions. I've said a bit about names. I now need to say a bit about classes. I'll only barely scratch the surface, but I cannot pass them by. Know that we'll return to them. (We'll even, at the end, learn how to make our own.)

No doubt you noticed that some of the numbers returned by Python have no decimal point and that some do. For instance, compare the numbers returned by / and //:

>>> 12/3

4.0

>>> 12//3

4

We call a number with the decimal point a float; we call one without an int. These are our first examples of classes. (Classes are often also called types. I'll use "type" and "class" interchangeably.) Every value is a member of a certain class, and (as will we find) the class of which a value is a member determines what may be done with it.

We can have Python tell us the class in which a value lies. We do this with the type function. Thus:

>>> type(4.0)

<class 'float'>

>>> type(4)

<class 'int'>

Floats Ain't Exact!

You might wonder why we need two classes of number. Why not have just one generic class that might perhaps be called real? Well, I can only begin to answer here, but begin to answer I must. 

We cannot lump ints and float into a single, generic numeric class, for ints are exact and floats are approximations. Burn this into your brain. If you don't, you'll make absolutely fatal mistakes in later chapters.

You'll be puzzled by this I suspect. Students often are. They often feel that it's a defect that one of our primary numeric types - the floats - gives only approximations.

But there's a good reason for this. Many good reasons actually. One concerns numbers like pi. Pi, you will recall, is the ratio of circumference to diameter in a circle. Likely you also know that pi is an irrational number; that is, its decimal expansion is infinite, and that infinite expansion cannot be created by the repetition of some finite block of digits. Thus of course we cannot exactly represent in Python (or indeed in any language); for to do so would require that we store an infinite sequence of digits in computer memory. In Python, the best we can do is give a float approximation of pi. Here's the one in the math module:

>>>  import math

>>> math.pi

3.141592653589793

The same is true of all irrationals. They can be at best approximated by a float.

But this would seem to leave open the possibility that rationals might be exactly represented in Python. But even this is often beyond Python (or indeed any language). Consider this:

>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1

0.9999999999999999

That's 10 of 0.1, which in mathematics is exactly 1. But, as you see, that's not what Python gives us; it gives us 0.9999999999999999 instead. I won't delve into the reason for this. (Please read this if you're curious. It has to do with the form in which number are stored in computer memory.) Instead all I want to do is point out the fact: the 0.1 of Python is not the 0.1 of mathematics, for the former is an approximation and the later is exact.

The lesson here is that we should never assume that a float is exactly some real or other. Assume instead that it's a best an approximation. This won't be of much importance here at the start of TL!. But later on it will become absolutely crucial.

The String Class

Let me take just a moment to introduce a third class, the str (short for "string") class. A later chapter is devoted to strings, but we'll need them before then.

What's a string? A string is a sequence of characters - first this character, next this one, etc. How do we make one? We make a string with quotation marks (single or double, just be consistent). For instance, "abc" is the string that contains the characters "a", "b" and "c" is that order.  So too is 'abc'

>>> type("abc")

<class 'str'>

>>> type('abc')

<class 'str'>

We'll find in a later chapter that Python provides us with a plethora of string functions. One is of such great use that I'd like to introduce it now. We call it "concatenation"; its symbol is +. Concatenation is  the stick-together function; to concatenate one string to another is to stick that one onto the end of the other.  Thus concatenation is (unlike addition of floats or ints) not commutative. Order matters! Watch:

>>> 'abc' + 'def'

'abcdef'

>>> 'def' + 'abc'

'defabc'

A string, like a float or an int, is a value. Thus a string can be given a name (or as we sometimes say it can be assigned to a variable); and if it is, the value of the name is the string to which it refers. Console time:

>>> a_str = 'abc'

>>> a_str

'abc'

>>> b_str = 'def'

>>> c_str = a_str + b_str

>>> c_str

'abcdef'

>>> d_str = b_str + a_str

>>> d_str

'defabc'

As I believe I might have mentioned, assignment proceeds from right to left. Thus in c_str = a_str + b_str, we first do the concatenation on the right; and then the value that results is assigned to the variable on the left.

A Few Little Programs

Let's put what we've learned to use in a few simple programs. Type each program below into your code editor and then run it. 

The first program is demanded by tradition. 

print('Hello, World!')

We told Python to print the string "Hello, World!" to the screen. It dutifully did so.

The second program computes and then prints circle area. Note that the print function can print multiple values on a line; to do so, simply separate those values with commas. The print function below first prints a string, then a number, then a second string, then a second number.

radius = 12

PI = 3.14159

circle_area = PI * (radius ** 2)

print('radius =', radius, 'and area =', circle_area)

Note the capitalization of PI. This is common (though not required) for variable names whose value will never change - constants they're called. Note too how well (if you'll excuse a bit of self-praise) the variable names are chosen. For instance, the name radius tells you precisely what the variable does. It holds the value of the radius.

When I ran the code, I got this output in the console:

radius = 12 and area = 452.38896

Let's write one final program and then call it quits. This third program will compute tax and final price. Note that one of the lines of code begins with a hash tag - #. Anything after # is a comment. It's ignored by Python; its sole purpose is to explain the code to a human reader.

pre_tax = 12.95

tax_rate = 5.2

tip_percent = 15

# tax and tip will be divided by 100 since each is a percent

total_cost = pre_tax + pre_tax * (tax_rate / 100)

print("The pre-tax price was", round(pre_tax, 2)) # round to nearest cent

print("The total cost is", round(total_cost, 2))

# tip isn't given on tax paid

with_tip = total_cost + pre_tax * (tip_percent / 100)

print("Price with tip is", round(with_tip, 2))

Here we have three instances of the print function. The second and third print to new lines. (That can be changed. Curious how? Please go read about Python's print function.)

Here's my output:

The pre-tax price was 12.95

The total cost is 13.62

Price with tip is 15.57

The input Function  and Type Conversion

So far, we've not yet seen a way for a program to interact with a user as it runs. Wouldn't it be great if Python could ask the user at some point to input a value and then use that value in some computation? Well, it can. To do, we make use of the input function. (You'll need it for the program you're asked to write for the chapter.)

I'll first show you how to use the input function in the console. Try this:

>>> answer = input("Tell me your name: ")

You should see in the console after you hit enter:

Tell me your name:

Now go to the console and type in whatever you like after the color. I typed in Franklin and then hit enter. (That is indeed my name.) The string "Franklin" then became the value of the variable answer. We can see this if we type answer into the console:

>>> answer

'Franklin'

So, what the input function does is this:

It's really fairly simple. But there is a twist. That twist is that, no matter what the user types in, the input function will return a string. That's true even if you type in, say, 2. It won't be the integer 2 that's returned. It will be the string '2'. Let me prove that to you:

>>> user_input = input("Gimme input: ")

Gimme input: 2

>>> user_input

'2'

>>> type(user_input)

<class 'str'>

There it is. We have an int, not a string!

But that can definitely be a problem. What if we wanted to use the user's input as ints are used? What if, for instance, we wished to divide it by 5? If we try, we get an error:

>>> user_input / 5

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: unsupported operand type(s) for /: 'str' and 'int'

What's the solution? How do we get ints (or any other non-string type) out of the input function? We do type conversion. For example, we turn the string '2' into the integer 2; and we do that with the int function. Here's an example of how it works:

>>> a_string = '2'

>>> type(a_string)

<class 'str'>

>>> an_int = int(a_string)

>>> an_int

2

>>> type(an_int)

<class 'int'>

Do you see what happened there? We gave the int function a string, and it converted that string to an int. '2' became 2.

Let me show you how to put this to work in a simple program. Here's a copy-past out of my code editor:

to_square = input("Give me an integer and I'll square it: ")

to_square = int(to_square)

print(to_square ** 2)

Here's what I saw in my console after I hit Run:

Give me an integer and I'll square it: 3

9

The first value of to_square was the string '3'. The line to_square = int(to_square) converted that string to an int and made that int the new value of to_square. (Please do remember that we read variable assignment statements right to left.) After that was done, we printed the square of to_square.

I'll end this section with a curious, and really quite beautiful, way to shorten the program above. Here it is:

to_square = int(input("Give me an integer and I'll square it: "))

print(to_square ** 2)

We went from three lines of code to two. Note that the int function is now called on the first line. How do we read that first line? From the inside out. First Python calls the input function. Its output is a string. That output is immediately fed into the int function and converted to an integer, and that integer becomes the value of to_square.

Bugs of Two Types

I'm sure you already written code that doesn't work. We call code that doesn't work buggy code, and we call the mistake made a bug. To debug code is to find and eliminate the mistakes. (The Wikipedia article on bugs in great. I knew only a little piece of the history.)

Don't think that identification and elimination of bugs is some small task. It's what you'll mostly do. Programmers are bug catchers!

You'll encounter bugs of two basic sorts. Let's call them stop bugs and go bugs. (This isn't the usual way to categorize bugs, but I think here at the start it's the most useful way. I'll say more in later chapters.) A stop bug will quite literally stop execution of your code; Python has encountered a command that, for one reason or another, it cannot carry out. Perhaps you've used a variable before it was defined. Perhaps you misspelled a variable. Perhaps you attempted to combine objects of different data types in ways that they cannot be combined. There are lots of possibilities here; and with each new bit of Python we learn, we'll have to contend with new stop bugs.

Stop bugs are sometimes called crash bugs. They cause the program to crash.

Fortunately for us, stop bugs are always accompanied by an explanation of why Python had to stop. These  explanations - error messages they're called - are often quite helpful. READ THEM! (Don't worry if you don't understand everything Python says. You'll understand what's most important.)

Here are a few deliberate console crashes.

>>> s pam = "s pam"

  File "<stdin>", line 1

    s pam = "s pam"

        ^

SyntaxError: invalid syntax

We tried to put a space in a name. No spaces!

A second:

>>> spam + eggs

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: name 'spam' is not defined

We can't use a variable before we define it.

A third:

>>> 3 + "three"

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Whelp, guess we can't add a number to a string. (What would that mean, anyway?)

One last example of a stop bug:

>>> 3/0

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ZeroDivisionError: division by zero

Did you really think that Python would know how to divide by zero?

So a stop bug stops execution. As you might have guessed, a go bug does not. The program continues execution until the very end, but (and this is a big "but") the program doesn't do what you wanted it to do. With a stop bug, Python knew how to do everything it was told to do. And it did it. But what you told it to do is not what you really wanted it to do. Bugs like this are sometimes called semantic or logic bugs.  Your Python is fine, but your logic is off.

Here's a little program  with a go bug. Can you find it?

# Compute the side length of a cube given its volume.

volume = 125

side_length = volume ** 1/3

print(side_length)

It runs just fine and outputs a value. But that value is wrong.

(What's wrong with the code? Python follows the usual order of operations. So in  volume ** 1/3, it first raises volume to the power of 1 and then divides the result by 3. That's not what we wanted. We wanted to raise volume to the one-third power instead. We do that with volume ** (1/3). Divide first, then raise to a power.)

Got Lint?

Have you noticed that the your code editor will sometimes place a colored squiggle under a line of code? (Replit does.) Like the red squiggle you see to the side. (If you're at replit and you don't see the red squiggle when you type in this program, that means Code Intelligence is turned off. Go to the cog on the left of the replit IDE, click and then turn on Code Intelligence.) The linter did that. The linter detects problems with your code before you run it and alerts you to them with squiggles. (What's the problem with this little two-line program? We misspelled an_int on line 2. We should have an_int = an_int + 1, not an_int = a_int + 1.)

Pay attention to the squiggles! Fix them before you run the code. In this case, if you run before you fix it, the program will crash.

If you wonder why a squiggle is there, hover over it with the cursor and a message box should pop up. If you do that for the squiggle to the side, you should see undefined name 'a_int'.

Name Choice

Comp sci is certainly science-like. (The name is a little hint.) But it's also art-like is many and various ways. I'll end with a few words about these two aspects.

So, you know what variables are for. They're to store away values. You also know the rules for valid variable names (can use letters, can use digits, can use the underscore, can't use anything else, can't begin with a digit, can't use a keyword). But there's more to variable choice than that. I implore you to choose variable names are both succinct and descriptive.

The reason your variables should be succinct is obvious I suppose. Succinct means you have less to type, and it means you're less likely to spell it incorrectly. The reason your variables should be descriptive is perhaps a little less obvious. So what's the reason? It's that your code should be understandable by a human reader; that is, it should be readable. I once had a student write a longish program, and he chose the variable names a, b, c, d etc. What a terrible choice! (I don't mean to insult the student. He was really quite strong. It was a beginner's mistake.)  This made the code so very difficult to read. 

An example should make what I mean clear. Consider these two little programs.

# Program 1

a = 12

b = 16

c = (1/3) * a * b

print(c)

Now a rewrite:

# Program 2

base_area = 12

height = 16

pyramid_volume = (1/3) * base_area * height

print("if a pyramid has base area", base_area, "and height", height, "then its volume is", pyramid_volume)


If you'd read only the first, you'd have no idea what the computation meant. The second however makes that quite clear.

Of course the first is a perfectly correct computation; it does correctly compute pyramid volume. But a human reader would find it quite mysterious. Code should not be mysterious! So practice the art of the descriptive variable choice.

My second point concerns comments. Comments serve the same purpose as descriptive variables. They make the code readable. Like this:

# the coefficients in the quadratic equation x**2 - x - 1:

a = 1

b = -1

c = -1

x = (-b + (b**2 - 4*a*c)**(1/2)) / 2  # the positive solution give by the quadratic equation

(Here the variable names a, b and c are just fine since they're the usual names for the coefficients in a quadratic equation.) The comments make quite clear what the code does. So just as I implore you to choose descriptive variable names, I also implore you to comment your code.

I'll leave with a note about the human reader. That human might be you at a future time.  Don't do to yourself what I've done to myself numerous times. I worked hard and finally got the code right, but I didn't comment; and then when I came back to the code later, I had no idea how the code worked. So show a little love to your future self. Comment your code.