Expressions

0. Learning objectives


1. Expressions

You probably used a calculator before to perform simple computation like 2 + 3 (and probably more complex ones as well!). Well, the Python interpreter can be used just like a calculator as well! We can type in expressions and, as long as we are using correct Python syntax, the interpreter will show the result. Try it out with Brython at the bottom of the page! Enter 2 + 3 then hit enter. You should see a 5 printed on the next line.

Note: we mentioned before that, in a moment of extreme creativity, it was decided to name Python both the language and the interpreter. I'll leave it ot you to deduct form the context which one we are talking about.

When we typed 2 + 3, we typed in an expression. Python read it, evaluated it (from left to right) and printed the result of the evaluation. This is important: an expression gives a result.

Python keeps doing this over and over again, as long as there are expressions to evaluate. It is almost like a cycle, a loop. This cycle is known as the Read-Evaluate-Print Loop (this where REPL comes from!). This is kind of like the eat-sleep-repeat meme that you might have seen before. If you ask me, that sounds unhealthy. Programming is fun, but it is more fun when it is part of a balanced lifestyle! What do you think?

What happens if you put a bunch of spaces in an expression? Try typing 2 +       3 in Brython. It gives the same result! Python doesn't care about spaces, but it does care about indentations - we will see this later.


2. Types

When Python sees the 2 + 3 in the example above, it looks at the types of the elements in the expression (2 and 3) and checks if it know how to perform the required operation (+) between those elements. Depending on the types, Python may need to do something different in order to add them together. For example, try 2 + 'apple' in Brython. You should receive an error: TypeError: unsupported operand type(s) for + : 'int' and 'str'.

Python is telling us that we have a TypeError. Specifically, it is telling us that it doesn't know how to use the operator + with the operands of type int and type str.

What does that mean??

The int means that the operand we wrote to the left of the + was an integer and the operands we wrote to the right of the +) was a string. (We'll talk more about strings in a few lectures - for now, just think of it as sequence of character - some text). Basically, we asked Python to add an integer (a number) with a string (some text) and it said "hey, I don't know how to do this!". In other words, nobody has told Python how to add something of type int to something of typestr.

You will probably come across TypeError errors (and not only) when working on your code (I still do!). Make sure that you read Python's error messages carefully! The error message is usually very helpful and gives you information about which file and line number caused the error and it is one of the first hints about how to debug your code.

Luckily, there is a way for us to figure out the type of a variable. Python has this really useful function called type(). This function (we'll talk about functions soon) takes and argument and returns its type. For now, we will mostly use the following types:

  • int: represents an integer, i.e. numbers like $\dots,-4,-3,-2,-1,0,1,2,3,4,\dots$.
  • float: represents a floating-point number, i.e. numbers that have a decimal point somewhere. One thing to be careful about: as soon as you add a decimal point to a number, Python will represent this as a float (e.g., Python will represent 10. as a floating point. Since as humans it is very easy for us to miss the . after the 10, make sure you are explicit while typing and use 10.0 if you want the number to be represented as a floating point).
  • str: represents a string, i.e. some kind of text. Note that you need to enclose strings within quotes (you can use either single or double as long as you have a matching pair, which means you would need to type 'apple' (or "apple") and not apple (this will be interpreted as a variable with name apple. More about variables in a bit).
  • bool: represents a Boolean, i.e. something that is either True or False.
  • NoneType: represents nothing. I know this sounds useless but it will make sense when we talk about functions. (Try to run type(None) in Brython or in the code editor below).

We can also use type() to determine the type of the result of an expression. Python will evaluate the expression between the parentheses, then feed the result of the evaluation to the function type(). Try typing type(2 + 3) in Brython - you should still see <class 'int'>.

It is also possible to convert between types. Try the following in Brython and look at the type() of the results:

int(), float(), and str are examples of other functions.

And for something even more interesting, try to check what happens if we convert strings and numbers to Boolean using the bool() function: bool(0) vs. bool(42) (or any other number that is not 0), or bool('hello!') vs. bool('') (or any other string that is not the empty string ''). More about strings later!

Reminder: in the example on the right, the pound symbol # (colloquially, the hashtag) is a comment and tells Python not to try to interpret anything after the hashtag. Just ignore it! Comments are extremely useful for humans, but useless to Python. You should use comments in your codes as much as possible.
It's not only useful for someone else reading your code, but useful for you once you get back to your code later on and you try to figure out what you were thinking!

3. Operators

In the last example, we were just adding numbers together, but now let's try doing something a little more interesting. To do more complex calculations, we need additional operators like +. We can start by thinking of arithmetic operators, comparative operators and logical operators. Most of these operators will have left-hand-side and right-hand-side operands, perform a specific operation on them, and then "return" the result of the operation they performed. Parentheses () and negation - are a little different, since negation only takes in a right-hand side operand and parentheses allow us to alter the operator precedence by grouping expressions.

If we simply evaluate left-to-right, as we stated Python does, then we should get:

5 * 2 + 6 / 3 - 4 =
=  10 + 6 / 3 - 4 = 
=      16 / 3 - 4 = 
=        5.33 - 4 = 1.33

But we know that is not the case and that the right result is:

5 * 2 + 6 / 3 - 4 =
=  10 + 6 / 3 - 4 = 
=    10 + 2.0 - 4 =
=        12.0 - 4 = 8.0
Note the 6 / 3 = 2.0. The division operator (/) always results in a floating point number!

But why is the result 8.0 instead of 1.33? Operator precedence!

When using these operators, you need to be careful about operator precedence, i.e. the order in which Python will execute operations if you do not use parentheses to alter that order.

Python follows a specific order when evaluating operators in expressions. The table below illustrates, in order from the highest precedence to the lowest the order in which operators are evaluated within an expression. (Note that operators at the same level of precedence are evaluated left to right ).

Symbol Type Name Example
() arithmetic grouping (2 + 3)
** arithmetic exponentiation (raise to the power) 2**4
- arithmetic negation (not subtraction) -3 + 2
*, /, %, // arithmetic multiply, divide, modulo, floor division 4 * 5, 5/2, 11 % 2, 11 // 2
+, - arithmetic addition, subtraction 2 + 3, 7 - 3
<, >, <=, >=, ==, != comparison less, greater, less or equal, greater or equal, equal, not-equal 2 < 3, 2 <= 3, 2 == 'dog' , 2 != 'dog'
not logical logical NOT not 2 == 'dog'
and logical logical AND (2 == 3) and (2 == 4)
or logical logical OR (2 == 3) or (2 == 4)
= assignment assignment x = 2

Try each of the examples above in Brython and guess the result before you hit enter!

Arithmetic operators allow us to create expressions that involve addition, multiplication, division, negation, exponentiation. We can also group expressions using parentheses () to override or clarify the order in which expressions are evaluated. If there are no parentheses, the order follows the mnemonic PEMDAS: Parentheses are first evaluated, then Exponents, Multiplication, Division, Addition and Subtraction. You might or might now have heard of PEMDAS (since it is based on the English language). If not, it is a nice mnemonic that, in doubt, will help you decide which operator should be executed first in your expression. Having said that, if you are writing long and complex expressions, do not rely on the order of operations! It really takes absolutely no time to use parentheses in your code (they are free!) to make it much easier to read for everyone without having to try to remember which operator comes first. Unless is obvious, use parentheses to specify the order you want!

In Python, exponentiation uses the ** operator. For example, if you type 2**4 in Brython you should see 16

The negation operator needs little introduction: it will negate the number that follows.

-16 because exponentiation has precedence! If you want to use -2 as base, then you need to use parentheses: (-2)**4.

Next, we have the multiplicative operators, which includes multiplication (*), division (/), modulo (%) and integer (floor) division (//).

Multiplication and division do exactly what they sound like.

The modulo operator can be a bit confusing. For example a % b returns the remainder after evaluating the integer division a // b. (Make sure to use two forward slashes to distinguish it from regular division!) The integer division is the smallest integer (hence the name floor division) you obtain when dividing the two operands.

For example, 5 // 2 = 2 and 5.999999999 // 2 = 2.0 (Notice that, differently from the division operator / which always returns a floating point value, the floor division // will return an integer if both operands are integers and a floating point otherwise.).

To understand how the modulo and integer division operators are related, it might help to think about the following formula:

a = b * ( a // b ) + ( a % b )

Pick two numbers and try the different parts of the expression on the right side of the equation above in Brython! For a = 7 and b = 2, you should get 7.

Be careful when using the modulo with negative numbers since the results might not be what you expect. In This case it is useful to remember that Python will always round the result of the floor division down to the smallest integer (i.e., towards negative infinity). For example, -5 // 2 = -3 (and not -2) since -3 is smaller than -2. Also, the result of the modulo will always have the same sign as the divisor (the second operand). For example, -5 % 2 = 1 (and not -1) since the divisor is 2 (positive) and not -2 (negative).

Comparison operators allow us to compare two operand and the result of the comparison is going to be either True or False. For example, 2 < 3 is True since 2 is "less than" 3. Also, 2 > 2 is False (2 is not "greater than" 2), but 2 >= 2 is True since 2 is "less than or equal to 2".

When checking if two variables are equal you must use two equals (==) symbols. Not doing so, would be an assignment and is a very common source of bugs! For example 2 == 2 is True but 2 == 3 is False, and 2 = 2 results in a SyntaxError.

We can also compare strings: 'dog' == 'apple' is False but 'dog' == 'dog' is True. If you want to check if two things are not equal, then you can use an exclamation mark with an equals sign (!=). For example 2 != 2 is False but 2 != 3 is True.

Python also follows the lexicographic order for strings (more about strings later) that, for us, means that it can evaluate the alphabetical ordering of words (just like in a dictionary). So, if you ask Python if 'cat' < 'dog', the answer is going to be True (don't assign to the %lt; operator more meaning than it holds! Python really doesn't have a favorite between cats and dogs!). Try other examples in Brython to get a feeling about how this ordering works. Since you can also have numbers (and more) in string (e.g., '123ab23AF' is a valid string), try to do some detective work and answer these questions. What come first?

We will get an explanation for this order once we discuss how computers represent data!

Logical operators allow us combine expressions from which we expect a Boolean value. These operators actually look like words in Python: and, or and not. Even though these are words, you should still think of them as having operands on both sides (except for the not operator which only has a right-hand side operand). The behavior of these operators is better understood if we evaluate a logic operators based on all the possible operands values. This is typically done using something that we call a truth table. These can be applied to a single operator or to a more complex logic expression (an expression where all the operators are logic operators and all the operands are Boolean values). In the example below, we list all possible Boolean values of A and B (our operands) in the first and second columns, and then the value resulting from applying the logical not, and and or operators to these values.

A B not A A and B A or B
False False True False False
False True True False True
True False False False True
True True False True Ture

Observe that:

Ok, here is an interesting problem. Now that you have the truth table of the fundamental logic operators, can you write the truth table for the following expression?

(A and (not B)) or ((not A) and B)

How do we start? First you should identify the operands. What are the values that might change in this expression? Then we start a table where we have one column for each of the operands (same name, same operand). Then we need a column for the value of the entire expression. As the operands take all the possible values of True and False, you evaluate the value of the expression.

To make your life easier, you can definitely add more columns to the table each representing a smaller part of the expression, just like the interpreter would do. So, for example, you could have a first column for not B, then maybe a second on for A and (not B), then a third for not A, a fourth for (not A) and B, and finally the one for the entire expression. In this way you isolate simple logic operations for which you already know the truth table and you can use the results that you already know to find your final result by composing the intermediate results.Composition is a powerful concept in Computer Science that allows you to build more complex "things" starting from simpler ones. It goes hands in hands with abstraction.

A B not B A and (not B) not A (not A) and B (A and (not B)) or ((not A) and B)
False False True False True False False
False True False False True True True
True False True True False False True
True True False False False False False

And, if you look close enough, this behaves like an exclusive or: either one, or the other, but not both!

What about that last row about assignment? This introduces the concept of variables, which we'll talk about next.