Lecture 2 (Variables and Conditions)
Download the original Jupyter notebookIn this lecture we will start our journey with Python programming. Potentially the simplest way to interact with Python is to use a concrete Python environment as an overpowered calculator. We can directly type a mathematical expression involving integers into a cell (in notebook) or python shell, and evaluate this expression, to see the result:
2+2*614
5+712
This works even with when dealing with large integers:
123213214254343142111312*321837823213231231231231217383965467266672327053149535457191353048772501722900256
We can combine the expressions in natural way, and use parenthesis as needed:
(2+2)*6 + 3*436
Variables
To see aspects of programming beyound just evaluating simple mathematical expressions, we will start with a concept of variable. We can initialize a variable with a given name, and assign a value to it; after the assignment we will be able to use this variable refering to it by the name in further expressions.
first_side = 45 + 27Note that assignment operation itself does not return any value: no value has been printed by the notebook/shell after executing the cell above. We can check what is the value of the variable first_side, as follows
first_side72
In further code, we can use first_side in more complex expressions as above:
first_side * 2 - 10134
We can also assign a new value to the same, already existing variable first_side. After having executed this assignment operation, the value will have changed:
first_side = 11first_side * 2 - 1012
Let us assign a new value to an existing variable first_side and create a new variable second_side, for the side lengths of some room. We can calculate the area of the room by just multiplying the side lengths:
first_side = 11
second_side = 22
first_side * second_side242
Note: The single equality sign in Python = has very different meaning than in mathematics. Instead of asserting that two values are equal, or checking if they are equal, it is used to assign the value of the expression on the right-hand side, to the variable named on the left hand-side. For instance, the following code does not make any sense:
22 = first_sideCell In[102], line 1 22 = first_side ^ SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?
first_side * second_side1728
Values and their types
Each expression or variable in python has specific value, and each value has a type; those values can also be assigned to variables. Let us start with discussing few basic types of variables: int is used to represent whole number for example (-5, -3, 0, 2, 1, 102321):
type(10)int
type(first_side)int
We can use a number of standard arthemtic operations on variables of type int: for example multiply, add, and substract them (we will discuss division at the next lecture)).
first_side * second_side242
The type float is used to store approximations of real numbers up to some finite precisision. For example
type(2.5)float
x = 2.5
y = 4.7x*y11.75
type(x)float
x/y0.5319148936170213
x / 0--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Cell In[113], line 1 ----> 1 x / 0 ZeroDivisionError: division by zero
Finally str is used to store strings (pieces of text).
type("Hello World")str
hello = "Hello World"hello'Hello World'
type(hello)str
For variables of type str we can also use the + operator, but it has a very different meaning: instead of additions it is now concatenation of strings:
name = "Jaroslaw"hello + " " + name'Hello World Jaroslaw'
Remembering that values have different types, and being aware at all time what are the types of values in particular variables is crucial to ensure that we write a correct code. For example in the snippet below
number = 123
string = "123"We declared two variables: number which has a value $123$ (of type int), and string which value is a text, with three symbols "1", "2" and "3". Those happen to be digits of a number $123$, but python do not treat it as a number: for the sake of all operations it is going to be treated as a string. As such, the results of the two expressions below are very different.
number + number246
string + string'123123'
As a particular curiosity, the Python allows using a ‘*’ operator for a string and a number of type int. This operator will concatenate the given string to each other a given number of times, for example:
"Hello" * 4'HelloHelloHelloHello'
Sometimes this might be an undesired behavior. Maybe we read a specific value from a file, and we intent to multiply it by four, but unintentionally, we keep the value as a value of the type str in our variable, instead of converting it to the type int. We will get:
string * 4'123123123123'
Instead of:
number * 4492
First exercise: finding roots of quadratic equation.
Before we proceed with the first exercise, let’s very briefly see how to calculate a sequare root of a number. We need to execute first:
import math(This imports the math library. It should not output any value. We will discuss imports in more details later, for now you can treat it as something you need to do.)
Now, to calculate an (approximate) square-root of a non-negative number we can just write:
math.sqrt(7)2.6457513110645907
Again, this can be used in more complicated expressions as well:
x = math.sqrt(6) * math.sqrt(3) + 11Exercise 1
Assume that at the beginning of your code, three variables a, b and c are set up to some values of type float. For example:
a = 1.0
b = -10.0
c = 2.0Calculate roots of the quadratic equation $a x^2 + bx + c = 0$.
To do this, calculate $\Delta := b^2 - 4ac$, and $x_0 = \frac{-b - \sqrt{\Delta}}{2a}, x_1 = \frac{-b + \sqrt{\Delta}}{2a}$
Solution.
delta = b*b - 4 * a * c
first_root = (-b - math.sqrt(delta)) / (2*a)
second_root = (-b + math.sqrt(delta)) / (2*a)first_root0.2041684766872809
second_root9.79583152331272
Subtle mistake
Here is a piece of code that seems almost identical. It is rather easy to make this kind of bug, and it takes some time to spot the issue and correct it:
delta = b*b - 4 * a * c
first_root = (-b - math.sqrt(delta)) / 2*a
second_root = (-b + math.sqrt(delta)) / 2*afirst_root0.2041684766872809
second_root9.79583152331272
What’s wrong here? We didn’t put the parenthesis around (2*a), so the expression was interpreted by python as first_root = ((-b - math.sqrt(delta)) / 2)*a or in mathematical notation $\frac{-b -\sqrt{\Delta}}{2} a$. When written in code, especially by ourselv, we tend to guess our intention, and overlook mistakes like that.
Printing values We might be tempted to calculate both of the roots in a single cell, and output both of those results; a code below, while working well, unfortunately will print out only the second root
delta = b*b - 4 * a * c
first_root = (-b - math.sqrt(delta)) / (2*a)
second_root = (-b + math.sqrt(delta)) / (2*a)
first_root
second_root9.79583152331272
In general, when we have several lines in a single cell, notebook will only print out the result of the last expression (if it returns some result). If we want to print more information, especially inside a more complicated piece of code, we need to use explicitly a print function. Here are some examples:
print("Hello World")Hello World
print("Hello", 2, delta)Hello 2 92.0
So a solution to our problem that prints both of the roots could look like this:
delta = b*b - 4 * a * c
first_root = (-b - math.sqrt(delta)) / (2*a)
second_root = (-b + math.sqrt(delta)) / (2*a)
print("First root is: ", first_root)
print("Second root is: ", second_root)First root is: 0.2041684766872809
Second root is: 9.79583152331272
Boolean expressions, and if statement:
Let us introduce a new type of value, bool. bool (or Boolean) is a type, which has only two values: True and False, and those values are typically returned when we want to check if some condition is satisfied. For example:
10 > 7True
type(10 > 7)bool
As with any other value, we can assign it to a variable:
result_of_comparison = 10 > 7result_of_comparisonTrue
Moreover, we can combine boolean values using three operators: and, or, not
result_of_comparisonTrue
True and TrueTrue
True and FalseFalse
True or TrueTrue
True or FalseTrue
False or FalseFalse
The value of expression x and y is True if both x is True and y is True. The value of expression x or y is True if at least one of x, y is True. We can make arbitrarily complicated expressions, further combining the results that are themselves obtained by expressions involving or, and, not, of some bool values (which, in turn, we could have obtained for example by comparisons of int values). For example:
a = 5in_interval = ((a >= 10) and (a <= 20)) or (a < 0)in_intervalFalse
The value in_interval is True if at least one of the two things happen: either a is between $10$ and $20$ inclusive, or a is smaller than zero.
Note: You might ask yourself how Python interprets a combination like this
in_interval = (a >= 10) and (a <= 20) or (a < 0)Is it equivalent to
in_interval = ((a >= 10) and (a <= 20)) or (a < 0)or to
in_interval = (a >= 10) and ((a <= 20) or (a < 0))(Make sure that you understand the difference: in one case we look at the and of the expression (a >= 10) and ((a <= 20) or (a < 0)),
in the other case we look at the or of expressions ((a >= 10) and (a <= 20)) and (a < 0). Find values of a for which the truth-values of those expressions differ.)
Going back to the expression (a >= 10) and (a <= 20) or (a < 0): you can experiment with it to figure out which is it, but I recommend not writing this kind of code at all. If it is unclear to you now, it is going to be unclear in the future as well (and to others reading your code). Instead just put explicitly paranthesis where you want them, so make it either ((a >= 10) and (a <= 20)) or (a < 0) or (a >= 10) and ((a <= 20) or (a < 0)) depending on what is your intention
Control flow operations: if statement
The main reason bool variables are going to be useful (and what makes python a programming language, not just a calculator), are control flow instructions. The code in python, by default is executed from top to bottom one instruction at a time, but this is not necessary. We can execute some instructions only if a specific bool value is True. For example:
a = 4if a > 10:
print("a")In the code above, the instruction print("a") will be executed only if a>10 expression returns True. In this case value of the variable a is 4, so this instruction has not been execcuted.
a = 11
if a > 10:
print("a")a
More general syntax for the if instruction is:
if condition:
instruction_1
instruction_2
instruction_3
other_instructionsWhat is going to happen here, is that if condition is True the python will jump in and start executing the entire block of instructions instruction_1, instruction_2, instruction_3. After it is done executing instruction_3, it will continue executing instructions outside of this block, i.e. other_instructions.
On the other hand, if condition is False, python will immediately jump to other_instructions.
Very important Blocks of code in Python are delineated by indentation (spacing in front of an instruction). This is in contrast with many other programming languages. Where a given instruction is aligned changes the meaning of the code. Make sure that you keep in track in which block each instruction you wrote is, and do it intentionally.
Example 1
In this case the condition is False, so the program will skip instruction print(a) and print("Hello") (both are in the same block), and jump directly to print("World")
a = 10if a > 10:
print(a)
print("Hello")
print("World")World
On the other hand, here the condition is True. The interpreter will check the condition, and execute the code in this block, then proceed to execute the following code:
a = 11if a > 10:
print(a)
print("Hello")
print("World")11
Hello
World
Note that the condition is only checked once, as soon as the interpreter encounters if instruction. If the condition stops being true at some point inside this block, it will keep executing instruction in this block.
a = 11
if a > 10:
print(a)
a = 3
print("Hello")
print("World")11
Hello
World
But after this code the value of variable a is 3. Running the same if code again, we will not enter the block:
if a > 10:
print(a)
a = 3
print("Hello")
print("World")World
Reiterating importance of indentation The following two pieces of code differs only in spacing at the beginning of the third line, but have very different meaning. Make sure you keep track of it:
if a > 10:
print(a)
print("Hello")if a > 10:
print(a)
print("Hello")Hello
Code executed inside the if block is allowed to be arbitrary python code. In particular it can itself contain an if statement (so-called nested if statements.). Let’s look at the following piece of code with three diffrent values of a
a = 3
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
print("Hello")a = 11
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
print("Hello")a greater than 10
but it is smaller than 20
Hello
a = 22
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
print("Hello")a greater than 10
Hello
Try following it line-by-line and make sure that you understand why it behaves the way it does.
if-else statment
A more general form of a conditional statement is if-else statement: it executed one block of code if the condition is satisfied, and other block if it is not. In both cases, after complieting execution of the relevant block of code, the python will conditinue with the code that follows after the entire if-else; in the abstract code below
if condition:
instruction_1
instruction_2
else:
instruction_3
instruction_4
instruction_5
...when condition evaluetes to True python will execute instruction_1, instruction_2, instruction_5, ... in this order. If condition evaluates to False python will execute instruction_3, instruction_4, instruction_5, ... in this order.
Note that else: clause is paired with the previous if statemnet on the same level of indentation. Follow the three pieces of code below line-by-line and make sure you understand why it behaves the way it does:
a = 11
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
else:
print(" and even larger tha 19")
print("Hello")a greater than 10
but it is smaller than 20
Hello
a = 22
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
else:
print(" and even larger tha 19")
print("Hello")a greater than 10
and even larger tha 19
Hello
a = 3
if a > 10:
print("a greater than 10")
if a < 20:
print(" but it is smaller than 20")
else:
print(" and even larger tha 19")
print("Hello")While loop: first look
The while loop has very similar syntax to the if statement:
while condition:
instruction_1
instruction_2
instruction_3It provides a very different behavior. After encountering while condition: line, if condition is False python will just skip the entire block with instruction_1 and instruction_2 and proceed to evaluate instruction_3 the same way it does for an if statement. In contrast, when condition is satisfied, the interpreter will execute the entire inner block instruction_1, instruction_2, but instead of skipping directly to instruction_3 after having executed this block, it will go back and check if the condition is satisfied; if it is, it will execute the instruction_1, instruction_2, and so on and on… until it reaches the check while condition: with condition unsatified.
Example Here, we set a = 1 initially, and write a simple while loop: it check if a<10, then executes print(a), and a=a+1. (Now a has value 2). After completing all instruction in this block, it goes back to check if a < 10 (it still is), and executes print(a), a = a+1, and so on, until we reach value a=10
a = 1
while a < 10:
print(a)
a = a+1
print("Hello")1
2
3
4
5
6
7
8
9
Hello
You can try to run this piece of code in the debug mode in VS code and follow the execution instruciton-by-instruction to see by yourself how it works.
Note
The code above looks similar to the one below, but here the instruction a = a+1 is outside of the while loop. I.e. the code will check if a<10 (which it is true), then proceed to print(a), then goes back to check if a<10 (still true), proceeds to print(a), and so on. In no place inside this loop any of the variables involved in the loop condition changes value, so the condition will stay true forever, and the program will keep printing value of the variable a (which is 1) until we interrupt it. We can do it by pressing the “stop” button (usually a black square in your python environment)
a = 1
while a < 10:
print(a)
a = a+1
print("Hello")1
1
1
1
1
1
1
1
1
1
1
--------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) Cell In[205], line 3 1 a = 1 2 while a < 10: ----> 3 print(a) 4 a = a+1 6 print("Hello") File ~/.python3.14_env/lib/python3.14/site-packages/IPython/core/interactiveshell.py:3045, in InteractiveShell._tee.<locals>.write(data, *args, **kwargs) 3043 def write(data, *args, **kwargs): 3044 """Write data to both the original destination and the capture dictionary.""" -> 3045 result = original_write(data, *args, **kwargs) 3046 if any( 3047 [ 3048 self.display_pub.is_publishing, (...) 3051 ] 3052 ): 3053 return result File ~/.python3.14_env/lib/python3.14/site-packages/ipykernel/iostream.py:691, in OutStream.write(self, string) 689 # only touch the buffer in the IO thread to avoid races 690 with self._buffer_lock: --> 691 self._buffers[frozenset(parent.items())].write(string) 692 if is_child: 693 # mp.Pool cannot be trusted to flush promptly (or ever), 694 # and this helps. 695 if self._subprocess_flush_pending: KeyboardInterrupt: