Python: Basics#

This chapter assumes no prior knowledge of Python, and although not required, any experience with programming concepts or language will help.

Objectives#

  • Understand the standard Python datatypes such as int, float, string, list, dict, tuple, etc.

  • Perform arithmetic operations like +, -, *, ** on numeric values.

  • Perform basic string operations and manipulations like .lower(), .split().

  • Understand boolean values and comparison operators operations (==, !=, >, etc.) and boolean operators (and, or, not).

  • Assign, index, slice and subset values to and from tuples, lists, strings, and dictionaries.

  • Write a conditional statement with if, elif and else.

  • Identify code blocks by levels of indentation.

  • Explain the difference between mutable objects like a list and immutable objects like a tuple.

Basic Python Data Types#

A value is a piece of data that a computer program works with. There are different types of values: 7 is an integer, 3.1415 is a float, and "Hello World!" is a string. A variable is a name that points to a value. In mathematics and statistics, we would write \(x=7\) where the variable \(x\) would point to the value 7. In Python, we can use (almost) any characters, including unicodes, as a variable name as long as it starts with a letter or an underscore. However, it should not be a reserved word in Python such as for, while, class, lambda, etc. as these words encode language functionality in Python.

Warning

Python does not always protect the keywords we don’t want to overwrite!

We use the assignment operator = to assign a value to a variable:

xy = 'hello'    
Xy = 3    # case sensitive assigment
9x = 2    # SyntaxError: invalid decimal literal
x9 = 2    
_x = 3.14 # ok, but special
if = 2    # Syntax error

Tip

See the Python 3 documentation for a summary of the standard built-in Python datatypes.

Some built-in Python data types#

English name

Type name

Type Category

Description

Example

integer

int

Numeric Type

positive/negative whole numbers

7

floating point number

float

Numeric Type

real number in decimal form

3.14159

boolean

bool

Boolean Values

true or false

True or False

string

str

Sequence Type

text

"Hello World!"

list

list

Sequence Type

a collection of objects - mutable & ordered

['Ali', 8, -5]

tuple

tuple

Sequence Type

a collection of objects - immutable & ordered

('Tokyo', 3.1415, 2023)

dictionary

dict

Mapping Type

mapping of key-value pairs

{'name':'MPIA', 'code':'HD', 'zip':68117}

none

NoneType

Null Object

represents no value

None

There are many more built-in types in Python. We’ll cover this in the object programming part.

Numeric data types#

There are three distinct numeric types: integers, floating point numbers, and complex numbers. We can determine the type of an object in Python using type(). We can print the value of the object using print().

x = 7
type(x)
int
print(x)
7

In Jupyter/IPython (an interactive version of Python), the last line of a cell will automatically be printed to screen so we don’t actually need to explicitly call print().

x  # Anything after the pound/hash symbol is a comment and will not be run
7
pi = 3.14159
pi
3.14159
type(pi)
float
0x9, 0xa, 0XF    # Hex literals
(9, 10, 15)

Tip

Python (since version 3) also allows you to make large integers more readable by using _ as a separator. e.g. 10_000

10_000, type(10_000)
(10000, int)

Arithmetic Operators#

Below is a table of the syntax for common arithmetic operations in Python:

Operator

Description

Example

+

addition

1 + .4

-

subtraction

2 - 3

*

multiplication

25 * -17

/

division

2 / -3

**

exponentiation

2 ** 100

//

integer division / floor division

2 // -3

%

modulo

7.2 % 2.5

Tip

(n // m) * m + (n % m) = n

Let’s have a go at applying these operators to numeric types and observe the results.

1 + 2 + 3 + 4 + 5  # add
15
2 * 3.14159  # multiply
6.28318
3.14159 ** 2  # exponent
9.869587728099999

Division may produce a different dtype than expected, it will change int to float.

int_2 = 2
type(int_2)
int
int_2 / int_2, type(int_2 / int_2)  # divison
(1.0, float)

But the syntax // allows us to do “integer division” (aka “floor division”) and retain the int data type, it always rounds down.

5 / 2
2.5
5 // 2  # "floor division" - always rounds down
2
5.1 // 1  # but conserves the most complex type (here float) 
5.0

We refer to this as “integer division” or “floor division” because it’s like calling int on the result of a division, which rounds down to the nearest integer, or “floors” the result.

int(5 / 2)
2

The % “modulo” operator gives us the remainder after division.

100 % 2  # "100 mod 2", or the remainder when 100 is divided by 2
0
101 % 2  # "101 mod 2", or the remainder when 101 is divided by 2
1
100.5 % 2
0.5

Complex Numbers#

Complex numbers are built-in types in python, but intrinsically stored as two float types (real, imaginary) parts, respectively. To create a complex number you can use the j or J character or the complex(.., ..) function.

 j    # will not work
 1J   # or 1j will
2.+3j, 2-3J, complex(1,2)
((2+3j), (2-3j), (1+2j))
(2.+3j).real, (2+3j).imag
(2.0, 3.0)

None#

NoneType is its own type in Python. It only has one possible value, None - it represents an object with no value. We’ll see it again in a later chapter.

x = None
print(x)
None
type(x)
NoneType

Strings#

Python stores any series of characters (or text) as a data type called a string. There are not difference in python whether the string is a single character or a list of characters (unlike C for instance.).

We write strings as any characters enclosed with either:

  • single quotes, e.g., 'Hello'

  • double quotes, e.g., "Goodbye"

There’s no difference between the single or double quotes in Python. There are cases where having both is useful.

Multiline string exists as well and requires triple single/double quotes. These are used for function documentation (more on that later), e.g. """This 'function' does something cool.""".

name = "Alice"
name
'Alice'
type(name)
str
a_longer_text = "If a string contains a quotation or apostrophe, we'd combine single and double quotes to define it."
a_longer_text
"If a string contains a quotation or apostrophe, we'd combine single and double quotes to define it."
quote = 'Donald Knuth: "Premature optimization is the root of all evil."'
quote
'Donald Knuth: "Premature optimization is the root of all evil."'

Boolean#

The Boolean (bool) type has two values: True and False.

the_truth = True
the_truth
True
type(the_truth)
bool
lies = False
lies
False
type(lies)
bool

Comparison Operators#

We can compare objects using comparison operators, and we’ll get back a Boolean result:

Operator

Description

x == y

is x equal to y?

x != y

is x not equal to y?

x > y

is x greater than y?

x >= y

is x greater than or equal to y?

x < y

is x less than y?

x <= y

is x less than or equal to y?

x is y

is x the same object as y?

2 < 3
True
"Hello World!" == "Traditional entry example!"
False
"Hello World" == "hello world"
False
2 != "2"
True
2 == 2.0   # value comparison not limited by types
True

Python also offers syntax close to human language, for instance you can write

1 < 2 <= 3     # instead of (1<x) and (x<=3)
True
3 < 2 <= 1, 1 < 0.5 >= 0
(False, False)

Boolean Operators#

We also have so-called “boolean operators” which also evaluates to either True or False:

Operator

Description

x and y

are x and y both True?

x or y

is at least one of x and y True?

not x

is x False?

True and True
True
True and False
False
True or False
True
False or False
False
("Python 2" != "Python 3") and (2 <= 3)
True
True, not True, not not True
(True, False, True)

Special float values: NaN, Infinity#

In python, you also have built-in NaN and Infinity values. float('NaN') and float('Infinity')

float('NaN'), float('nan'), float('infinity'), float('Inf') # case insensitive
(nan, nan, inf, inf)

Don’t be confused by their behavior with arithmetic and comparison operators

nan = float('NaN')
nan < nan, nan == nan, nan ** 2, nan + 1, nan + 1 == nan 
(False, False, nan, nan, False)
inf = float('Infinity')
-inf < inf, inf == inf ** 2
(True, True)

Bitwise operators#

Python also has bitwise operators like & and |. Bitwise operators literally compare the bits of two integers.

operation

operator

example

AND

&

17 & 5

OR

`

`

XOR

^

17 ^ 5

bitwise complement

~

~17

bitshift left

<<

17 << 3

bitshift right

>>

0x11 >> 2

Casting#

Sometimes we need to explicitly cast a value from one type to another. We can do this using functions like str(), int(), and float(). Python tries to do the conversion, or throws an error if it can’t.

x = 5.0
type(x)
float
x = int(5.0)
x
5
type(x)
int
x = str(5.0)
x
'5.0'
type(x)
str
str(5.0) == 5.0
False
int(5.3)
5
float("hello")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[60], line 1
----> 1 float("hello")

ValueError: could not convert string to float: 'hello'

Variable manipulation#

Python variable assignments are mostly standard, as the previous example shows. But there are some points to remember:

x = 2      # Assigns variable
x + 3      # Uses variable
y = x + 3  # Creates a new variable
x = x + 1  # Assigns a new value

Note

x++ does not exist in Python. Instead, you need to use x += 1

Some stunts of python in variable manipulation

x, y = 2, 3
print(x, y)
2 3
x, y = y, x
x, y        # no need for intermediate variable!
(3, 2)
x = y = z = 0 
print(x, y, z)
0 0 0

Warning

x = y = z = 0 instanciate all these variables to the value 0. But in some cases that we will discuss later, they all point to the same memory location, i.e. share the same value instead of being independent variables.

Sequences: Lists, Tuples, strings, and sets#

Lists and tuples#

We have seen string types above. These are sequences of characters defined with single, double or triple quotes.

Lists and tuples allow us to store multiple elements in a single object. These elements are ordered and they can be of different types.

Lists are defined with square brackets [].

my_list = ["numbers", 1, "TWO", 3.01, complex(4, 5), [True, False], float('inf')]
my_list
['numbers', 1, 'TWO', 3.01, (4+5j), [True, False], inf]
type(my_list)
list

Lists can hold any datatype, and even other lists. This is similar to a N-dimensional array, but with potentially different lengths and types.

another_list = [[1, 2, 3], [4, 5, 6], [7, 8]]
another_list
[[1, 2, 3], [4, 5, 6], [7, 8]]

You can get the length of the list with the function len():

len(my_list), len(another_list)
(7, 3)

Tuples look similar to lists but have a key difference: they are immutable. That means they can’t be modified after creation. Lists are mutable and we can assign new values to any element. Tuples are defined with parentheses ().

my_tuple = ("numbers", 1, "TWO", 3.01, complex(4, 5), [True, False], float('inf'))
my_tuple
('numbers', 1, 'TWO', 3.01, (4+5j), [True, False], inf)
type(my_tuple), len(my_tuple)
(tuple, 7)

Tip

Have you noticed that when we display a, b, as in the previous line, python displays a tuple (value_a, value_b)?

Before we can explicit the immutable state of a tuple, we need to learn how we get and set individual elements.

Indexing and Slicing Sequences#

We can access values inside a list, tuple, or string using square bracket syntax. Python uses zero-based indexing, which means the first element of the list is in position 0, not position 1.

my_list
['numbers', 1, 'TWO', 3.01, (4+5j), [True, False], inf]
my_list[0], my_list[3]
('numbers', 3.01)

In python, list indexing starts from 0 (similar to many programming languages but Fortan for instance, starting at 1). Similarly to Java for instance, Python checks that you do not try to access an element outside the sequence.

my_list[27]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[72], line 1
----> 1 my_list[27]

IndexError: list index out of range

Something new from Python is that we can use negative indices to count backwards from the end of the list.

my_list[-1], my_list[-1] == my_list[len(my_list) - 1], my_list[-4]
(inf, True, 3.01)

If we want to get a subset of a list, we can slice it using the colon :. If L is a list, the expression L [ start : stop : step ] returns the portion of the list from index start to index stop, at a step size step.

my_list[1:3]
[1, 'TWO']

Note from the above that the start of the slice is inclusive and the end is exclusive. So my_list[1:3] fetches elements 1 and 2, but not 3.

slicing illustration

Fig. 2 The slicing operation uses the element position which preceed the value. So that L[2: 7] does not include the item at index 7 ‘h’. Image credit learning by examples#

Strings behave the same as lists and tuples when it comes to indexing and slicing. Remember, we think of them as a sequence of characters.

abc = "abcdefghijklmnopqrstuvwxyz"
abc[0], abc[-1], abc[22: 10: -5], abc[:-5], abc[20:]
('a', 'z', 'wrm', 'abcdefghijklmnopqrstu', 'uvwxyz')

Tip

Note that slicing steps can be negative. You can reverse a sequence by my_list[::-1]. (or use the more flexible function reversed())

List methods#

A list is an object and it has associated functions, called methods for interacting with its data.

We will come back to the object and method vocabulary in the object oriented programming part. For now, let’s identify a method as a function that we access using a period . after a variable name. For example, my_list.append(item) appends an item to the end of the list called my_list.

lst = [1, 2, 3]
lst
[1, 2, 3]
len(lst)
3
lst.append(4)
lst
[1, 2, 3, 4]
lst.extend([5, 6, 7])
lst
[1, 2, 3, 4, 5, 6, 7]

Tip

You can see the documentation for more list methods.

Mutable vs. Immutable Types#

Strings and tuples are immutable types, that means read only variables once they are created. In comparison, lists are mutable, we can assign new values to any element. This is the main difference between lists and tuples (there are other implications behind the scenes).

cities_list = ["Paris", "Madrid", "Rome"]
cities_list
['Paris', 'Madrid', 'Rome']
cities_list[0] = 'Berlin'
cities_list
['Berlin', 'Madrid', 'Rome']
cities_tuple = ("Paris", "Madrid", "Rome")
cities_tuple
('Paris', 'Madrid', 'Rome')
cities_tuple[0] = "Berlin"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[83], line 1
----> 1 cities_tuple[0] = "Berlin"

TypeError: 'tuple' object does not support item assignment

Same goes for strings. Once defined we cannot modifiy the characters of the string.

cities_tuple[0][1] = 'A'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[84], line 1
----> 1 cities_tuple[0][1] = 'A'

TypeError: 'str' object does not support item assignment

But tuples can store pointers to mutable objects

x = (cities_list, 'Berlin')
x
(['Berlin', 'Madrid', 'Rome'], 'Berlin')
x[0][0] = 'Paris'
x
(['Paris', 'Madrid', 'Rome'], 'Berlin')

Sets#

set is a particular built-in sequence in Python. It stores un-ordered and unique items. Sets are “mutable” as you can add or remove elements, but they do not support indexing.

You can define a set by a sequence of items between {} or with the set() function.

s = {2, 1, 3}
s, type(s)
({1, 2, 3}, set)
{1, 2, 3} == {3, 2, 1} == {2, 1, 3} == {3, 1, 2}
True
[1, 2, 3] == [3, 2, 1]
False

You can add elements using the associated method .add()

s.add(2)  # does nothing as 2 already exists
s
{1, 2, 3}
s.add(5)  # But this adds it
s
{1, 2, 3, 5}
s.remove(5)   # removes the item with value 5, not the 5th element.
s
{1, 2, 3}
s[0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[93], line 1
----> 1 s[0]

TypeError: 'set' object is not subscriptable

Arithmetic operators on sequences#

There are possible operations on sequences but avoid them if possible. These are not optimized for memory and speed. The examples below are mostly traps I’ve seen people falling into, especially when confusing sequences with arrays.

[1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]

The sum of sequences creates a new sequence of the same type with a shallow copy of the elements. This may generate a lot of memory overhead.

(1, 2) * 4
(1, 2, 1, 2, 1, 2, 1, 2)

Product operation generates repeated sequence copies. This could become expensive mistakes.

[1, 2] / 4
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[96], line 1
----> 1 [1, 2] / 4

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

Not all operators work on sequences. Prefer their methods, which are safer and optimized.

Warning

Arithmetic operations on sequences are often because of a confusion with manipulating arrays.

More on python strings#

String Methods#

Strings are immutable sequences of characters. There are various commonly used methods in Python, and too many to list them all here. Check out the documentation.

Some examples of frquently used methods.

all_caps = "HELLO WORLD!"
all_caps
'HELLO WORLD!'
all_caps.lower(), all_caps
('hello world!', 'HELLO WORLD!')

Note that the method lower doesn’t change the original string but rather returns a new one.

all_caps.split()
['HELLO', 'WORLD!']
all_caps.count("L")
3

One can explicitly cast a string to a list:

list(all_caps)
['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D', '!']
"*".join(list(all_caps))  # which can join multiple strings
'H*E*L*L*O* *W*O*R*L*D*!'
"*".join(all_caps)
'H*E*L*L*O* *W*O*R*L*D*!'

Warning

a string is a sequence of strings (individual characters). Hence "*".join(list(all_caps)) == "*".join(all_caps)

We can also chain multiple methods together (more on this when we get to NumPy and Pandas in later chapters):

"".join(all_caps).lower().split(" ")
['hello', 'world!']

String formatting .format(), f-strings#

String outputs is a big part of our scientific work. Wheter it is to report about a single value or a list of values, formatting the output of a code is important.

Python has multiple ways to format strings nicely. Have a look at the online documentation for details.

name = 'Alice'
takeoff = 'Berlin'
landing = 'New York'
duration_in_hours = 9.2

The manual way, which can rapidly become hard to read and cumbersome

print(name +"'s flight from " + takeoff + " to " + landing + " took " + str(duration_in_hours) + " hours")
Alice's flight from Berlin to New York took 9.2 hours

The str.format() method, uses { and } to mark where a variable will be substituted and can provide detailed formatting directives.

print("{0:s}'s flight from {1:s} to {2:s} took {3:f} hours.".format(name, takeoff, landing, duration_in_hours))
Alice's flight from Berlin to New York took 9.200000 hours.
# works also with names
print(
    "{name:s}'s flight from {takeoff:s} to {landing:s} took {duration:0.1f} hours.".format(
        name=name, takeoff=takeoff, landing=landing, duration=duration_in_hours)
    )
Alice's flight from Berlin to New York took 9.2 hours.

Positional and keyword arguments can be arbitrarily combined:

print('{0:s}: "This {food} is {adjective}."'.format(name, 
      food='spam', adjective='absolutely horrible'))
Alice: "This spam is absolutely horrible."

More powerful, if you have a really long format string, you can reference the variables to be formatted by name instead of by position. This can be done by simply passing the dict and using square brackets [] to access the keys.

table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
      'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

This opens a lot of possibilities as you can access variable attributes in the formatting instructions.

Note

The % operator (modulo) can also be used for string formatting as in other languages. This is the “old ways” called string interpolation or printf-style, i.e. "string" % values. For example:

print('The value of pi is approximately %5.3f.' % 3.1415926535) which return The value of pi is approximately 3.142.

This formatting definition is not recommended as it removes the readibility of your code. Prefer the {} and str.format() method instead.

A new powerful manner to format string is to use formatted string literals or f-string. These strings begin with f or F before the opening quotation mark (or triple quotation mark). Inside this string, you can write a Python expression between { and } characters that can refer to variables or literal values in the current running scope. (see online documentation)

year = 2016
event = 'Referendum'
yes_votes = 42_572_654
no_votes = 43_132_495
percentage = yes_votes / (yes_votes + no_votes)
print(f"""Results of the {year} {event}: 
yes: {yes_votes:,d} ({percentage:2.2%}), 
no: {no_votes:,d} ({(no_votes / (yes_votes + no_votes)):2.2%})
""")
Results of the 2016 Referendum: 
yes: 42,572,654 (49.67%), 
no: 43,132,495 (50.33%)

Note

In the formatting examples above, the notation after the colon in my curly braces is for formatting. For example, :.2f means, print this variable with 2 decimal places. See format code options here.

Dictionaries#

A dictionary is a mapping between key-values pairs and we define one using curly-brackets:

a = dict(one=1, two=2, three=3)
b = {'one': 1, 'two': 2, 'three': 3}
c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
d = dict([('two', 2), ('one', 1), ('three', 3)])
e = dict({'three': 3, 'one': 1, 'two': 2})
f = dict({'one': 1, 'three': 3}, two=2)
a == b == c == d == e == f
True

We can access a specific field of a dictionary with square brackets:

a['one'], b['three']
(1, 3)

Dictionaries are mutable objects, which means we can edit and add/remove values

a["new"] = 5
a['one'] = 'one'
a
{'one': 'one', 'two': 2, 'three': 3, 'new': 5}

We can also delete fields entirely:

del a["new"]
a
{'one': 'one', 'two': 2, 'three': 3}

Similarly to a list, a dictionary can store any types of values and the indexing uses keys instead of only integers.

{27: 'twenty-seven',
 'one': 1,
 'lst': [1, 2, 3],
 (1,2): 45}
{27: 'twenty-seven', 'one': 1, 'lst': [1, 2, 3], (1, 2): 45}

Keys can be almost any type of object. In the example above, we use the tuple (1,2) as a key. We could not use a list.

{27: 'twenty-seven',
 'one': 1,
 'lst': [1, 2, 3],
 [1,2]: 45}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[117], line 1
----> 1 {27: 'twenty-seven',
      2  'one': 1,
      3  'lst': [1, 2, 3],
      4  [1,2]: 45}

TypeError: unhashable type: 'list'

If you try to access a key that does not exist, you get an exception

a[10]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[118], line 1
----> 1 a[10]

KeyError: 10

Try to check before,

10 in a, 'one' in a  
(False, True)

or get a default value instead

a.get(10, 'ten')
'ten'
a['bacon'] = 13     # adds an item
del a['bacon']      # removes it.

Digression around variable assignement: avoiding a common trap!#

In C/C++ language for instance, the declaration of a variable sets a place in memory to use

int x, y;   // initialized variables with "random" values
x = 2;      // the memory location of x contains 2, y remains undetermined.
x = 3;      // at the x memory location we get 3, y remains undetermined
y = x;      // at the y memory location we copied the value from the x location, which is now 3.

In python, it is different. Any variable name points to a value which is allocated in memory.

planet = "Pluto"   # planet points to an object of type `str` in memory containing "Pluto".
planet = "Tiamat"  # planet now points to a different object of type `str` in memory containing "Tiamat".
# Because "Pluto" is not referenced by other names, Python will tag it for deletion (garbage)
legend = planet    # "legend" points to the same object string "Tiamat" in memory.
del planet         # will remove the name but legend persists
del legend         # will remove the name and tag "Tiamat" in memory as garbage.

Warning

the garbage collection in Python is not immediate unless manually trigged. This is important to keep in mind when codes have strict memory limitations.

l = [1,2,3]
m = [1,2,3] # l and m are different objects
l == m   # Equality (of values) tested with ==
True
l is m     # Identity (of objects) tested with is
False
l[0] = 42
print(l)
print(m)
[42, 2, 3]
[1, 2, 3]
l = [1,2,3]
m = l 
l == m, l is m
(True, True)
l[0] = 42
l, m
([42, 2, 3], [42, 2, 3])

The variable assignement in Python can be leading to some confusing results. By default, Python will try to avoid copying values (unless atomic, i.e. numerical literals). Explicitly copying a variable may be needed with copy.copy() or copy.deepcopy() (documentation).

Conditionals#

From the examples before, there are times we would like to execute one or another operation depending on the state of some variables. Conditional statements allow us to write codes that have conditional behaviors.

name = "Santa"

if name.lower() == "alice":
    print(f"{name}! That's my name too!")
elif name.lower() == "santa":
    print("That's a funny name.")
else:
    print(f"Hello {name}! That's a cool name!")
print(f"Nice to meet you, {name}.")
That's a funny name.
Nice to meet you, Santa.

Conditional coding in python uses

  • if, elif, and else keywords

  • the colon : ends each conditional expression,

  • a superior indentation defines code blocks,

  • if statements don’t necessarily need elif or else,

  • elif lets us check several conditions until one works,

  • else evaluates a default block if other tested conditions are False

  • the end of the entire if statement is where the indentation returns to the same level as the first if keyword

Tip

Indentation defines a block of instructions in Python

The top-level must not be indented. It does not matter how much blanks you use, but:

  • it must be uniform within each block

  • Most people use 4 blanks per level but tabs work too (but python does not accept mixing tabs and spaces for that)

You can look at the styleguide (PEP8) by Guido van Rossum, Barry Warsaw, and Nick Coghlan for good code styling practices.

Inline if/else#

We can write simple if statements inline, i.e., in a single line.

lst = ['a', 'list', 'of', 'values']
if len(lst) > 10:
    x = "probably a sentence."
else:
    x = "seems only a few words"
x
'seems only a few words'
x = "probably a sentence" if len(lst) > 10 else "seems only a few words"
x
'seems only a few words'

Shortcuts#

Python supports a concept known as “short-circuiting”. This is the automatic stop of evaluating boolean operation if the truth value of expression has already been determined.

undef_variable  # not defined
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[131], line 1
----> 1 undef_variable  # not defined

NameError: name 'undef_variable' is not defined
True or undef_variable
True
True and undef_variable
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[133], line 1
----> 1 True and undef_variable

NameError: name 'undef_variable' is not defined
not (False and undef_variable)
True

Expression

Result

Detail

A or B

If A is True then A else B

B only executed if A is False

A and B

If A is False then A else B

B only executed if A is True

Loops#

The while statement#

The while statement is a loop that runs until a condition evaluates to False. The while loop in Python is a control flow statement that executes a block of code repeatedly until a given condition is evaluated to be False. Each iteration evaluates the condition first before executing the code in the loop body. Once the condition evaluates to False, the execution of the loop is terminated. The syntax for a while loop is as follows:

while condition:
    # loop body
x = 0
while x <= 5:        # Bool. expr.
    print (x, x**2)   # Indentation
    x += 1            # update state

print(x) # Unindented again
0 0
1 1
2 4
3 9
4 16
5 25
6

How much is the smallest / largest positive float

Use a while loop, compare to 0 and float(‘inf’) and find an approximative answer

How much is the smallest / largest positive float

Use a while loop, compare to 0 and float(‘inf’) and find an approximative answer

The following compares the value of x to 0 and inf, respectively. The while loop stops when they become equal for the computer’s float precison.

val = x = 1.
while x > 0:
    val = x
    x /= 2.
print(x)
5e-324
val = x = 2 ** 32.
while x != float('inf'):
    val = x
    x *= 2.
print(val)
8.98846567431158e+307

Strictly, we found the actual smallest and highest values to a factor of 2. We would need to adjust the multiplicative factor to find it with more precision.

import sys
print("Strictly speaking")
print("highest: ", sys.float_info.max, "smallest: ", sys.float_info.min)
Strictly speaking
highest:  1.7976931348623157e+308 smallest:  2.2250738585072014e-308

We did not see imports yet.

How precise is your installation?

Add smaller and smaller numbers to 1 while comparing the sum to 1

How precise is your installation?

Add smaller and smaller numbers to 1 while comparing the sum to 1

The following compares the value of x to 0 and inf, respectively. The while loop stops when they become equal for the computer’s float precison.

val = x = 1.
while 1. + x > 1.:
    val = x
    x /= 2.
print x
1.1102230246251565e-16

Strictly, we found the actual smallest and highest values to a factor of 2. We would need to adjust the multiplicative factor to find it with more precision.

import sys
print("Strictly speaking")
print("precision: ", sys.float_info.epsilon)
Strictly speaking
precision: 2.220446049250313e-16

We did not see imports yet.

For loops#

A for loop in Python is a type of loop that iterates over a sequence of values, such as a list, tuple, or string as see above. The for loop has two parts: the initialization and the condition, where the loop starts and the condition to meet to execute the code (similarly to while) When the condition is met, the loop stops and the code after the loop is executed. A for loop is very useful when you need to repeat a set of instructions a certain number of times. It is also helpful in iterating efficiently through sequences.

For example, you can use a for loop to print out each character in a string:

for char in "Hello World":
  print(char)
for color in ['red', 'green', 'blue', 'yellow', 'cyan']:
    print(color)
red
green
blue
yellow
cyan

The range function#

The range(start, stop, step) function generates a sequence of values which range from start to stop-1 with a step of step.

range(10)
range(0, 10)
list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(range(0, 10)) == list(range(10))
True
for k in range(10, 16, 2):
    print(k)
10
12
14

Inline for loop#

Sometimes we can write a for instruction inline, i.e. more condensed.

result = []
for i in range(10):
    if i % 2:
        result.append(i)
result
[1, 3, 5, 7, 9]

This can be condensed into

[i for i in range(10) if i % 2]
[1, 3, 5, 7, 9]

We will come back to this particular syntax, but note that we obtain a list with the entries we selected. In this case, Python is smart and optimizes the memory allocations.

{warning} Inline code does not always make your code readable. Readability is a primary goal for Python

Break Statement in loops#

The Python break statement is used to exit from a loop immediately after a certain condition is met.

for color in ['red', 'green', 'blue', 'yellow', 'cyan']:
    print(color)
    if color == 'green':
        break
red
green

In this example, the condition of color=='green' happens and the loop stops (we do not print the other colors).

Bracket the value 2.4 with elements from [1, 1.41, 1.65, 1.73, 2.24, 2.65, 2.71, 3.14]

Either a for loop or while would work

Bracket the value 2.4 with elements from [1, 1.41, 1.65, 1.73, 2.24, 2.65, 2.71, 3.14]

Either a for loop or while would work

  • for loop solution

value = 2.4
lst = [1, 1.41, 1.65, 1.73, 2.24, 2.65, 2.71, 3.14]

for i in range(len(lst) -1 ):
    if lst[i] <= value < lst[i+1]:
        break
print(f'{lst[i]} <= {value:f} < {lst[i+1]}, found at i={i}')
2.24 <= 2.4 < 2.65, found at i=4
  • while loop solution

value = 2.4
lst = [1, 1.41, 1.65, 1.73, 2.24, 2.65, 2.71, 3.14]

i = 0
while not (lst[i] <= value < lst[i+1]):
        i += 1
print(f'{lst[i]} <= {value:f} < {lst[i+1]}, found at i={i}')
2.24 <= 2.4 < 2.65, found at i=4

The continue instruction in loops#

The function of the continue statement is to skip the current iteration of a loop and continue with the next one.

for color in ['red', 'green', 'blue', 'yellow', 'cyan']:
    if color == 'green':
        continue
    print(color)
red
blue
yellow
cyan

The green value does not appear. We skipped this iteration.

Else in loops#

Python enables an else clause at the end of a for/while loop. The else code runs if the loop terminates naturally.

for color in ['red', 'green', 'blue', 'yellow', 'cyan']:
    if color == 'green':
        continue
    print(color)
else:
    print('--[Done]--')
red
blue
yellow
cyan
--[Done]--

Which is different from

for color in ['red', 'green', 'blue', 'yellow', 'cyan']:
    if color == 'green':
        break
    print(color)
else:
    print('--[Done]--')
red

Note that the else clause did not run.

It is similar for while loops.

i = 0
lst = list(range(10))
while lst[i] < 3:
    print(lst[i])
    i += 1
else:
    print('--[done]--')
0
1
2
--[done]--

enumerate, accessing the iteration index in for Loops#

Sometimes you need to iterate over the indices in a sequence. We use the enumerate() function.

for i, color in enumerate(['red', 'blue', 'green', 'yellow'], 1):
    print(i, color)
1 red
2 blue
3 green
4 yellow

zip, looping through multiple sequences in parallel#

The zip() function in Python takes in sequences (can be zero or more) and returns a new sequence which contains tuples of elements taken from each sequence. The i-th tuple contains the i-th element from each of the arguments. The sequence stops when the shortest input is exhausted. This function is used to map the similar index of multiple containers so that they can be used just using as single entity.

for k, v in zip(range(3), ['red', 'blue', 'green', 'yellow']):
    print(k, v)
0 red
1 blue
2 green

In this example, the first sequence contains only 3 elements. zip stopped after the third iteration.

zip is very useful to avoid manipulating indexes manually. You can focus on the elements directly.