Lesson 3: Variables and Basic Operators

Today, we're going to cover the basics of programming. As of last time, you should have fully installed Python 3, have something you can edit in comfortably (IDLE if nothing else), and know how to run your programs. This time, we'll cover variables, their various types, and the operators you can use to work with them.

Variables come in a number of types, but it's comfortable to separate them into a few categories. There are numeric types, sequence types, strings, mappings, and objects. While there are other kinds of types, they're not as important for the purposes of our lessons. Today we'll be covering numeric types and strings.

If you'd like to read more information about Python data types than is covered in this lesson, see https://docs.python.org/3/library/stdtypes.html

Numeric Types

Numbers can be represented in Python in a number of ways, including integers, floating point, complex numbers, decimals and fractions. For basics, we'll only look at integers and floating point numbers.

Integers are whole numbers, positive or negative. If you write out a number, with no commas or periods in it, Python will interpret it as an integer. For example, 3, 29, -17 are all viable ways to write an integer.

Floating point numbers are how Python handles real numbers that aren't integers. It's impossible for a computer to contain arbitrary real numbers; instead, there's a limit to the precision of these numbers.

We represent a floating point number by writing a number with decimal point . and an option exponent after it, written with an e or E followed by an integer. The exponent will be in the base of the number you typed; if there's nothing denoting the base, it's in decimal. Examples: 1.0, 3.14159265, 6.02E23, 1.602e-19

While for most of our purposes floating point numbers will work fine for calculations, they're not good for things that have to be exact. Rounding errors, especially from trying to treat decimal numbers as exact, mean that, you can get bizarre answers back from some tests. For example,

>>> .1 + .1 + .1 == .3
False

The above tests to see if the sum of three .1s gives .3; it turns out it doesn't. .1 * 3 is represented as 0.30000000000000004, not 0.3! Why is that? Because floating point numbers in binary produce rounding errors. Because of this, testing if two floating point numbers are exactly equal is usually a bad idea! Also note, using floating point numbers to store currency values, such as dollars and cents, is a bad plan. 1.99 isn't exact in floating point!

Technically, booleans are also numbers, and are treated as integers; a boolean can be either True or False, and is important to flow control code (if-elif and similar statements). Comparison operations give booleans as a result; we'll cover those at the end of this lesson. Don't worry about these being "numbers" too much, it's just how the language is put together. We'll be covering boolean operations in Lesson 5.

Arithmetic on Numbers

x + y : does what you'd expect, adding the two numbers together and giving the result.
x - y : subtracts x from y, again, pretty simple.
x * y : multiplies x by y and gives the result.
x / y : divides x by y and gives the quotient.
x // y : gives the result of x / y, but then floors it, that is, it rounds the quotient down to the nearest integer less than or equal to it.
x % y : gives the resulting remainder of dividing x by y. This obviously only makes sense for integers.
-x : gives x negated.
x ** y : gives the value of x to the power of y.

The above operators are given in ascending order of precedence. Thus, if you string the operators together, the operators at the bottom of the list will be performed first, followed by those higher up on the list, until the expression is condensed down to a single value. If you want to alter the order of operations, you can put them in parentheses () and the contents of those parentheses will be executed first.

>>> 2 + 3 * 7
23
>>> (2 + 3) * 7
35

While we won't be covering functions just yet, you've seen one in action in the previous lesson. A function is written as a name, followed by a set of arguments in parentheses. These arguments are expressions, and they are separated by commas. A function can return a value, and when it does this, that value is the result in the expression. Functions thus technically have the highest precedence, as they must be called before their value can be known.

Feel free to try these operators out in IDLE or the Python command line for a bit, to get used to how they work and in what order they will act.

Strings

Strings are simply lists containing characters. They can contain a string of any set of unicode characters; that is, any characters that a modern computer can store, in any language. Typically, strings are used to store text. But, since some of the characters they can store are not printable, we call them strings of characters. (Some of the non printing characters are from back in the days of teletype machines; one does nothing but tell the terminal to ring a bell).

There are four different ways to denote a string in Python. They are:

# Single quotes
'look like this and allow "double quoted" items inside them'
# Double quotes
"look like this and allow 'single quotes' or apostrophes, it's nice."
# Triple quoted
'''either look like this'''
"""or like this"""

Triple quoted sequences let you include ' or " characters as much as you want, so long as three in a row of the kind used to start the block don't show up inside the block. This kind of sequence can be useful when you want to get around using "escape sequences" using the backslash character.

Escape sequences work like this: you type a backslash, followed by a character. If you want to include a single quote in a single quoted string, or double quote in a double quoted string, you would have to type \' or \" respectively. Incidentally, to include a literal backslash in a string, you must type the backslash twice \\ for each single backslash you want to show up in the text.

There are special escape sequences for the "newline" character (a hard return) written as \n, and for the tab key, written as \t. We may see some of \n but no \t in these lessons, because tabs can't be trusted to print to consoles the same across platforms.

While there are other escape sequences that use more than one character after the backslash, we don't need them for our lessons. Feel free to look up how to use them on your own time.

Comments

The lines starting with # above mark comments; when a # appears outside of a string, anything from that character to the end of the line is ignored by the parser. Thus you can write a comment at the end of a line, or on its own line. Comments are intended for human reading, to give yourself notes on code that might confuse the reader. In this case, they're used to help label the different string formats.

Briefly, you should use comments whenever you had to look something up to remind yourself what a part of your code does. If you have trouble figuring out what to do with it now, imagine how you'll feel in six months if you didn't leave yourself any comments!

You should also put a comment at the start of any block of code that has a non-obvious purpose. If a block of code is quite large, you almost certainly need one of these. These comments act like section headers in writing, and help you process the code better and figure out where to make edits later on.

Some people reading this might think that they don't need comments, or that they don't need to comment everything they would need to look up. Let me tell you a story an older engineer often tells students: they were looking over old code that a team had worked on, and having a lot of trouble understanding it. The comments were sparse and very unhelpful, and they often had to turn to documentation to understand what was going on. Just as they said "I can't stand this code! These comments are horrible! Who wrote this?" they got to a part where it mentioned who edited the code and when. It was their own name, from six months ago.

When you write code, especially after you're done with this class, the most likely person to have to read it again is your future self. Please be kind to them. You'll thank yourself later.

Arithmetic with Strings

You can concatenate two strings by using the + operator. This means you take the contents of the first string and add the second string onto the end.

>>> "dog" + "house"
'doghouse'

You can also multiply a string by an integer to get that string repeated multiple times.

>>> "*" * 12
'************'

However, it might come as a surprise that you can't just add a number to a string and get it to work as you'd expect.

>>> "Entries: " + 12
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
"Entries: " + 12
TypeError: must be str, not int

What this is telling us is that concatenating a string requires another string on the other side, and here we have an integer (int), which Python didn't expect. It doesn't know what to do, so it throws an error. You can get around this by wrapping the expression you want to convert into a string inside of the str() function's argument.

>>> "Entries: " + str(12)
'Entries: 12'

As you can see, that converted the value (12 here) into a string, "12", and it concatenates as expected.

There are tons of useful string methods, but they're beyond the scope of this lesson. When we get to lists, we'll come back to strings, since Python treats both of these as the same kind of type, a sequence. For now, these two operators will do just fine.

Type Casting

The above str() function is an example of type casting. The function takes in a value (or an expression that results in one), and outputs a corresponding string, if it's possible to make a string out of that value. There are also similar functions for the other types in the Python language.

int() is a function that returns an integer. If given a floating point number, it will round towards zero. This is not the same as the "floor" function that // produces for its quotient; flooring a negative number rounds down, while using int() on a negative number rounds up. If given a string, int() will convert the string to an integer if all the characters in that string are digits.

float() is a function that returns a floating point number. It will convert any string with a valid Python representation of a floating point or integer number; it will also convert integers into floating point (this results in a .0 appearing in the result's representation).

str() is a function that returns the string representation of the value it receives. We've covered how this works above.

None

There is one more simple type we need to cover: None type. None is the value of an empty object; its type is NoneType, and it represents a lack of a value. This is useful when you want to reference an empty value, or have a container for an object that is left blank. Mostly, it won't be useful to you until you get to custom Functions and Objects.

Variables

A variable is a container for data. Essentially, you pick a name for the variable, and assign a value to it. You don't need to put any special words on the variable name. As long as the name isn't a reserved word like, say, if, you can assign it a value. Try to stick to standard English letters for most of your variables, though!

To assign a value, put the variable name on the left, follow that with = and then the value you want on the right.

x = 2
s = "string"

You can also perform an expression and set the variable to its result.

y = 2 + 3

You can even include the variable itself in the expression to set its new value! The variable will be set to the end result of the expression, so this doesn't cause a contradiction, even though it's pretty weird to think about in math notation.

x = x + 1

Because of the special form of the above, you can even compress that down into

x += 1

In fact, all of the standard arithmetic operators have an assignment shortcut like the above, with the same rule of "assign the thing on the left to the result of this operator on itself and the expression on the right". So there are operators for += -= *= /= %= **= and //= which all work this way. += and *= also work on strings.

You can reuse a variable name to override its original value. The type of the new value doesn't need to be the same as the original value for the variable. So, out of the above variables, you could set x to a string. This would work, but x would now be string type instead of integer type.

Lastly, there is a special "operator" (which only exists in the interactive REPL mode, not for scripts) which is just the underscore character, _

This operator acts like a variable, and contains the last value printed in the console, so you can reference it immediately.

>>> 2 + 3
5
>>> _ + 4
9
>>> _ + 5
14

Comparison Operators

Finally, there are operators that compare two values. If the comparison statement is true, the result is True; otherwise, the result is False. These operators are:

x < y : True if x is less than y
x > y : True if x is greater than y
x == y : True if x is equal to y
x >= y : True if x is greater than or equal to y
x <= y : True if x is less than or equal to y
x != y : True if x is not equal to y

The above statements not only have no priority compared to one another, they can be chained arbitrarily. So, if you for instance type

x < y <= z

Then this will give a result of True if x < y and y <= z, evaluating y only once. This notation is pretty close to what you'd expect from mathematics, as a result.

Comparing numbers this way does what you would expect. Comparing True and False will only make sense with == and != operations, but again does what you'd exiect. Comparing strings will give you a comparison of the value of their characters; this can be useful for alphabetical ordering (if the strings are all one case) or for ordering the information in general, but isn't of much use to us right now. Other types vary on how their comparisons work.

Further Information:

This section contains some choice information about what's going on under the hood. You won't need it for day to day programs and assignments, but it could come in handy later.

Numeric types in Binary

Integers have no maximum range in Python; however, it's a good idea to keep the number low enough to be represented in binary with a reasonable number of digits. If you go over the number of digits your computer can handle efficiently, the number will take longer to calculate, because Python will start working with it in "long" number notation, which is slower.

Keeping integers between –9,223,372,036,854,775,808 and 9,223,372,036,854,775,807 will keep the integer's size down to 64 bits, and it's hard to imagine most cases needing a value outside the 32 bit range (–2,147,483,648 to 2,147,483,647) which would be even better. If you're not sure if you'll be running code on 32 bit or 64 bit computers, stick to the 32 bit range for anything you want to run fast.

Why binary?

You might wonder why we're having to keep track of binary numbers at all, when decimal ranges would be easier to handle. Mentally, do what you want to keep track of this better, but inside your computer, base 10 makes no sense! Unlike Charles Babbage's engines, which were usually based on decimal notation, modern computers use binary. This is because the circuitry to make binary operations work is simpler than in any other base, which makes for faster and less expensive computers. Every digit in your computer's memory is thus either a 0 or a 1.

Negative numbers are stored by making the "two's complement" of the positive number that is its inverse; you get this by flipping every 0 for a 1 and every 1 for a 0 in the number, then adding 1 to it.

-0b10010111 == 0b01101000 + 1 == 0b01101001

Notice that when you do this to 0, you get itself!

0b00000000 inverted: 0b11111111

0b11111111 + 1 = 0b100000000, lowest 8 digits: 0b00000000

-0 == 0

Floating point is handled internally as a set of binary digits, where the precision point can "float"; it's pinned to just after the 1 at the start of the binary number, e.g. 1.0101010101... (which incidentally is approximately 1 and 1/3, or 1.333333...) This is then multiplied by 2, or divided by 2, a number of times given by the exponent. Thankfully, while we can work with numbers and exponents in binary if we want to, nothing forces us to do so.

If you have ten minutes, go ahead and watch this video. It explains some of the oddities of floating point precision visually, using one of the earliest 3D platformers (Super Mario 64) to help out.

https://www.youtube.com/watch?v=9hdFG2GcNuA

Note that this video is talking specifically about the value of floats in the N64's hardware, which was 32 bit, but 64 bit "double" floats act similar, just with more digits of precision. Also, if Pannen's talk of PUs confuses you, it's just a name for the "shadows" of the game map that show up at even intervals, as a consequence of how the game's physics work. These normally don't render any visible geometry, but the level still acts solid for Mario in the shadow PUs. Those intervals are pretty far apart, but there are tricks to get fast enough to shoot through the entire level and end up in one of the PUs.

Exercise: Programming Assignment L3

  1. For the below cases, add one set of parentheses to the expression so that it will return True.

    1. 1 + 2 * 3 + 4 == 13

    2. 3 / 6 + 4 - 7 == 1

    3. 3 + 5 / 4 == 2

    4. 1 + 2 + 3 + 4 / 3 == 4

    5. 4 * 5 == 6 * 7 - 2 - 10

    6. 2 + 4 == 4 - 2 * 4 - 2

  2. Give an expression that produces the following line. You should only include two = characters in your expression; remember how string multiplication works.

    === Hello! ===

  3. Write an expression that will produce a string that displays the following two lines, if the variable x has previously been set to 112 (if the value of x is a different number, it should display that number instead). Remember string conversion and escape sequences. The expression you produce should only include the = symbol once.

    ============
    Value: 112
    

    Test that you're getting the right result by feeding the string into print().

  4. Write an expression that will round numbers to the nearest integer, using only the operators and functions you've been introduced to in Lesson 3. The results should round down for values after the decimal point less than .5, and round up for .5 and up, as is normal for rounding.

    The number you're rounding should be on the left-most part of the expression. Use this set of numbers to test, and show the results of your expression on them:

    • 6.4
    • 7.8
    • 5
    • -1.2
    • 0
Previous
Next