Python basics


This tutorial gives a brief overview of the most essential features of Python. I recommend to additionally read (at least) chapters 3, 4 and 5 from the <a href=https://docs.python.org/3/tutorial/>official Python documentation</a>. Excellent Python tutorials are available at <a href=https://www.python-course.eu/python3_interactive.php>python-course.eu [EN]</a> or <a href=https://www.python-kurs.eu/python3_interaktiv.php>python-kurs.eu [GER]</a>. Another tutorial starting from zero is available at <a href=http://inventwithpython.com/invent4thed/chapter1.html>inventwithpython.com</a> When searching the web for specific Python related topics, keep in mind that we are looking at Python 3.6.4. Python 2.7 is still very popular but shows some major differences in comparison with Python 3.

Table of contents

Basic interpreter rules
Variables [Data types / Naming variables]
Operations [Arithmetic operations / Boolean operations / Comparisons]
Conversions
Decisions
Built-in data types [Lists / Tuples / Dictionaries / Sets]
Loops [While loop / For loop / Break and continue]
Functions
Built-in functions
Classes
Assertions
Common errors
Catching errors
Lambda expressions
Mixed topics

Basic interpreter rules


The interpreter acts like a translator of code for the computer. When learning to program it’s very important to understand how the interpreter works. In this first chapter we will learn the basic rules including syntax and execution order. Three essential rules of the interpreter, are:

  1. Execute code statement-wise (top to bottom)
    • Statements are usually separated by linebreaks
  2. Execute statements by evaluating all expressions
    • Start with the most nested expression
    • Follow precedence rules
  3. Ignore comments (#), blank lines and whitespace

Terminology:

  • Statements can be seen as the instructions, any program consists of.
  • A statement consist of at least one expression.

Note:

  • Cells are executed by selecting them first and then pressing [CTRL]+[ENTER] (in the Jupyter Notebook)
  • The output of a cell will be displayed below the cell.
a = 1         # 1 expression:  'assign the value 1 to variable a!'
b = a + 1     # 2 expressions: 'compute the sum of a and 1!' and 'assign result to the variable b!' 
c = a + b - 1 # 3 expressions: 'a + b' and 'X - 1' and c = X
print(c)      # 1 expression:  'call function print with argument c!'
print(c*10)   # 2 expressions: 'c*10' and print(X)
2
20

In Python the statements are usually separated by line breaks. Separating statements with a semicolon is also allowed but less common / readable. The following two cells are equal in terms of the statements but differently formatted. The arguments of print are texts (denoted by '):

print('Hello')  # first statement
print('World')  # second statement
# .. blank lines are ignored
print('!')      # third statement (.. white spaces between symbols / variables are ignored as well)
Hello
World
!
# statements separated be semicolons -> valid but less readable
print('Hello'); print('World'); print('!') 
Hello
World
!

Multi line statements are less common but also possible in Python. To continue a statement in the next line, we add \ before the line break. Line continuation is also implied inside parentheses ( ), brackets [ ] and braces { }.

a = 1 + 2 + \
    3 + 4 + 5
print(a)

b = (1 + 2 + 
     3 + 4 + 5)
print(b)
15
15

The most significant difference between Python and other languages is the mandatory indentation of blocks. Blocks are grouped statements (e.g. used in loops, conditions or functions). The following cell will demonstrate the indentation syntax. The if-keyword (details later!) introduces a block. Usually the indentation size is four spaces. Most IDEs (also the Jupyter server) will convert tabs to spaces so we can use tabs as well.

A simple indentation rule is: Whenever a statement ends with a colon, (at least) the next non-blank line has to be indented.

if True:   # 'if' is here just used to introduce a block. Statements that introduce blocks end with a colon!
    print('Hello')
    print('World') # two statements inside a block (indented with four spaces / one tab!)
print('!') # outside the block (no indentation) 
Hello
World
!

Additional information about indentation and blocks is available <a href=https://www.python-course.eu/python3_blocks.php>here</a>.

Variables


When creating a variable in Python, the data type is usually not given explicitly. Instead Python will automatically detect the variables type from the assigned value. To create and assign a variable the syntax is x = v, where x is the variables name and v the explicid value or an existing variable. When a variable already exists, it will be overwritten (and possibly also get a new data type).

Note that variables in Jupyter Notebooks exist across all cells.

x = 1           # create x and assign the value 1
x = 3.5         # x already exists, so it will be overwritten with 3.5
y = x           # create y and assign the value of x
x = y = z = 1   # it is valid to assign a value to multiple variables
x,y,z = 1,2,3.5 # or implicitly using tuples (covered later)

print(x,y,z)    # print() can display multiple variables/values, separated by commas
1 2 3.5

To list all variables that are currently in the memory we can call the Python functions dir(), locals() and globals(). However, in Jupyter notebooks the IPython command whos gives a more readable view.

whos
Variable   Type     Data/Info
-----------------------------
a          int      15
b          int      15
c          int      2
x          int      1
y          int      2
z          float    3.5

To delete a variable we can use the function del() or the magic command %reset -f.

del(x)
%reset -f

Data types

The following cell introduces the basic ‘primitive’ data types in Python, which are listed in the following table.

Type Keyword Values Examples
Integer int integral numbers (no decimals) -135 / -15 / 0 / 1 / 9 / 999
Float float real numbers (with decimals) -15.3 / 0.0 / 1.5 / 1 / 9.142
Complex complex complex numbers 5+1j / -2+0j / 0-15j
Boolean bool binary / logical values True / False
String str complex numbers 'Hello' / "it's a text" / 'C' / '123'
None Type NoneType single value for "nothing" None

The Python function type(x) will return the data type of a variable x. Also the data type is displayed with the command whos. Notice that in the following assignments the data type is never given explicitly! Instead Python will automatically set the data type, that fits best to the assigned value.

i = 1317         # Integer (int) [e.g. -1, 0, 1, 2, 3]
f = 2.31         # Floating point number (float) [e.g. -1.0, -0.1, 0.0, 3.212, 1235.213, 13.5e2]
c = 5 + 1j       # Complex number (complex) [e.g. 3 + 2j, cmath.sqrt(-1)]
b = True         # Boolean (bool) [True,False or 0, 1]
s = 'abcd'       # String (str) [e.g. 'Hello World!', 'a name', "quotation marks used for apostrophes"]
n = None         # None (NoneType) [None]

print(type(i))   # example usage of type(). However the command 'whos' will show the data type as well. 
<class 'int'>
whos
Variable   Type        Data/Info
--------------------------------
b          bool        True
c          complex     (5+1j)
f          float       2.31
i          int         1317
n          NoneType    None
s          str         abcd

Naming conventions

Names must only consist of letters and numbers. Actually letters from most alphabets like Ä,ö,Ø,И,ﮙ,λ etc. are allowed, but it’s best practice to use only the latin alphabet and numbers! The only valid special character is the underscore. In addition, names must not start with a number and should not start with an underscore. Usually:

  • class names are written in ‘CamelCase’
  • functions and variables in ‘lower_case_with_underscores’
  • constant values are written in ‘BIG_LETTERS_WITH_UNDERSCORES’.

Try to use names that are meaningful and not longer than necessary. Some examples are given in the following cell.

# GOOD NAMES
i = 1            # single letters are okay in a small context
max_value = 255  # meaningful and self explaining name 
PI = 3.141       # big letters for constant values

# BAD NAMES
l = 1            # l and O are similar to 1 and 0
O = 0
λ = 7            # don't use other alphabets!
 = 8
print()         # even if it's valid
8

Note that variable names must not equal the Python keywords:

False, def, if, raise, None, del, import, return, True, elif, in, try, and, else, is, while, as, except, lambda, with, assert, finally, nonlocal, yield, break, for, not, class, from, or, continue, global, pass

Additional information regarding naming and conventions available <a href=https://www.python.org/dev/peps/pep-0008/#naming-conventions>here</a>.

Operations


Arithmetic operations

These are applied to numbers (integers, floats or complex). Usually we would assign the result a variable. If the result of the last expression in a cell is not assigned, it will be printed directly. We use this to make the examples cleaner.

1 + 3       # addition
4
3 - 2       # subtraction
1
2 * 2       # multiplication
4
4 / 2       # division (x will be a float because dividing two Integers will always return a float)
2.0
9 // 2      # floor division (returns the quotient of the euclidian devision)
4
9 % 2       # modulo (returns the remainder of the euclidian devision)
1
2**3        # exponentiation
8
9**0.5      # root (using the exponentiation operator)
3.0

Execution order:

The execution order of operations depends on the precedence of the involved operations. Operations with same precedence will be executed from left to right (except for the ‘power’ operator). A detailed overview of the precedences is available <a href=http://www.informit.com/articles/article.aspx?p=459269&seqNum=11>here</a>. Below two examples:

a = 1 + 2 * 3**2  # exponents before multiplication before addition
x = 1 / (2 * 2)   # multiplication and division have same precedence (usually left to right, but parentheses used here)
print(a, x)
19 0.25

Boolean operations

Boolean operations are applied to booleans. The essential operations are and (&), __or ( ), xor __(^) and not.
b1 = True
b2 = True

# and (result is true, if both conditions are true)
b3 = b1 and b2
print('b1 and b2 = ', b3)

# or (result is true, if at least one condition is true)
b3 = b1 or b2
print(' b1 or b2 = ', b3)

# xor (result is true, if exactly one condition is true)
b3 = b1 ^ b2
print('  b1 ^ b2 = ', b3)

# not (will negate the boolean value)
b3 = not b1
print('   not b1 = ', b3)
b1 and b2 =  True
 b1 or b2 =  True
  b1 ^ b2 =  False
   not b1 =  False

Comparisons

Comparisons are operations that compare two values and return a boolean value.

Note that in the following examples the result of the comparison is assigned to the variable b:

b = 1 <= 5       # greater, smaller, greater or equal, smaller or equal
print(b)
True
b = 1 == 1.0     # equal (here true because same value after implicit cast)
print(b)
True
b = 'aa' < 'ab'  # comparing strings is valid (result depends on alphabetic order)
print(b)
True
b = True > 0     # booleans can be seen as 1 (True) and 0 (False) 
print(b)         # -> comparison with integers is valid
True

Conversions / casting


Conversion or casting means changing the data type of a variable. This can happen either explicitly or implicitly. The following cells will demonstrate this.

Explicit conversions

The syntax for explicit conversions is

v2 = newtype(v)

where v is the variable to convert, newtype the new data type (e.g. str for string, int for integer or bool for booleans) and v2 the variable, the result should be assigned to. Between the primitive data types, almost any conversion is valid. The following cell shows some examples:

f = float(123)   # integer to float
print('float(123) =', f)

f = float('5.1') # string to float (scientific notations like '2.3e3' or '-13.5e-5' are also convertable)
print("float('5.1') =", f)

i = int(141.6)   # float to integer (this will drop decimals e.g. 1.9 -> 1 and -2.9 -> -2)
print('int(141.6) =', i)

i = int('-2341') # string to integer 
print("int('-2341') =", i)

s = str(15.3)    # float or integer to string
print("str(15.3) =", s)

b = bool(-100.3) # float or integer to boolean (every number that is not zero or minus zero will be converted to 'True')
print('bool(-100.3) =', b)

i = float(True)  # boolean to float or integer ('True' will be converted to 1.0, 'False' to 0.0) 
print('float(True) =', i)

c = complex(1.5) # float to complex
print('complex(1.5) =', c)

c = complex('1.5+1j') # string to complex
print("complex('1.5+1j') =", c)
float(123) = 123.0
float('5.1') = 5.1
int(141.6) = 141
int('-2341') = -2341
str(15.3) = 15.3
bool(-100.3) = True
float(True) = 1.0
complex(1.5) = (1.5+0j)
complex('1.5+1j') = (1.5+1j)

Invalid conversions will raise a TypeError:

#float(1.5+1j)   # this would raise a TypeError

Nested casts are also possible:

int(bool(complex(float(str(1)))))
1

Implicit conversions

At some points conversions happen implicitly e.g. when the program requires a specific data type but gets another one, or when performing operations using variables of different data types:

# adding float and integer will result in float (even if the result has no decimals)
f = 1 + 1.0           
print("1 + 1.0 =", f)

# booleans can implicitly be converted to Floats or Integers
f = 1.5 + True        
print("1.5 + True =", f)
i = 5 * False
print("5 * False =", i)

# vice versa can floats and integers also be converted to booleans if they are connected by a boolean operation
b = -5 and True
print("-5 and True =", b)
b = not 0.0
print("not 0.0 =", b)
1 + 1.0 = 2.0
1.5 + True = 2.5
5 * False = 0
-5 and True = True
not 0.0 = True

Note that with both, explicid and impicid conversions we will not not modify a casted variable v, but instead create a temporary value, that represents v as a specific data type:

x = 1                # x is an integer
y = float(x)         # we assign y the float repr. of the value of x

print('x:', type(x)) # x is still an integer
print('y:', type(y)) # y is a float
x: <class 'int'>
y: <class 'float'>

Decisions


Decisions and conditions are an essential part of programs. They are used to control the computation flow of the program. The keywords used for conditions are if, elif and else.

Single condition

The most simple usage is the if statement, followed by a condition and a colon. The conditional statements follow in a block. The statements inside the block are only executed, when the condition is (evaluated to) True.

condition = True
if condition:                   
    print('condition is True!')
    print('still inside block!')
print('outside block!')
condition is True!
still inside block!
outside block!

The following example has a more complex condition which will be evaluated to a boolean value. We use the else keyword to introduce another block, that is only executed, when the condition is False.

x = 7     

if x > 5 and not x%2 == 0:       # statement will be evaluated to a boolean value
    print('x is greater than 5 and odd!')
else:
    print('x is smaller/equal to 5 or even!')
x is greater than 5 and odd!

Note that all kind of blocks can be nested. For example the condition in the example above can be separated. The result of the conditional expressions can be stored to improve readability (but this also adds two more lines to read):

x = 7     

is_greater_5 = x > 5
is_odd       = x % 2 == 1

if is_greater_5: 
    print('x is greater than 5', end=' ')
    if is_odd:
        print('and odd!')
    else:
        print('and even!')
else:
    print('x is smaller/equal to 5!')
x is greater than 5 and odd!

Complex decisions

More complex structures can be built with the elif (short for else if) and else keywords. The conditions are checked, top-down untill one condition is True. If no condition is True, the else-block is executed (if it exists). The following cell demonstrates the usage.

cond1 = False  # some boolean variables used as conditions
cond2 = True
cond3 = False

if cond1:
    print('cond1 is True')
elif cond2:                 # will be checked if first condition is False
    print('cond2 is True, cond1 is False')
elif cond3:                 # will be checked if 1st and 2nd condition are False
    print('cond3 is True , cond1 is False, cond2 is False')
else:
    print('no condition is True')
cond2 is True, cond1 is False

Ternary if statement

This expression can be used for single line if-else statements.

# possible implementation to get the sign of a value
v = -3
s = 1 if v >=0 else -1
print(s)
-1

Built-in data types (lists, tuples)


Besides the primitive data types presented before, Python offers many built-in data types. We will take a brief look at lists, tuples, which are very essential (also for the labs). In the additional topics you will find chapters about dictionaries and sets.

Lists


A list is a ordered collection of elements. The elements data types can vary. A list is created using brackets:

list1 = [1.0, 'name', 15, True]

print('Type:  ', type(list1))
print('Values:', list1)
Type:   <class 'list'>
Values: [1.0, 'name', 15, True]

Indexing

The elements of a list are accessed by typing the name of the variable, followed by the index in brackets. Note that the first element has the index 0 which is usual for most programming languages but not matlab. Python supports negative indices, where -1 will give the last element of the list, -2 the second last and so on.

#     0    1    2    3    4    5    <- indices
L = ['a', 'b', 'c', 'd', 'e', 'f']

first_element  = L[0]   # access first element of L
second_element = L[1]   # access second element of L
last_element   = L[-1]  # access last element of L (in this case equiv. to L[5])

print('first: ', first_element)
print('second:', second_element)
print('last:  ', last_element)
first:  a
second: b
last:   f

Trying to access any element after the last one will raise an IndexError.
For example our created list has currently 6 elements, so the last element has the index 5. Trying to access the element at index 6 will fail:

# print(L[6])       # this would rise an IndexError

Slicing

Python supports slicing, similar to matlab. Slicing means accessing a part of a list. The syntax is

List[start:end:step]

where start is the start of the slice (included), end the end of the slice (excluded) and step the step width in between. When start is omitted, the slice will start from the first element. Omitting end will make the slice end with the last element. Omitting step will result in a step width of 1.

#     0    1    2    3    4    5    <- indices
L = ['a', 'b', 'c', 'd', 'e', 'f']

print(L[1:3])     # access elements from index 1 to 3 
print(L[1:5:2])   # access all elements from 1 up to 5 with step width 2
['b', 'c']
['b', 'd']
print(L[:3])      # access all elements up to index 3 
print(L[3:])      # access all elements from index 3
['a', 'b', 'c']
['d', 'e', 'f']
print(L[:])       # access all elements (creates a copy of L)
print(L[::-1])    # access all elements with reversed order
['a', 'b', 'c', 'd', 'e', 'f']
['f', 'e', 'd', 'c', 'b', 'a']

Editing

Editing means changing values of a list without changing its shape.

list2 = [1, 1, 1, 1]
print(list2)

list2[0] = 0         # assign a new value to the first element
print(list2)

list2[2:4] = [2, 3]  # slices can be edited as well
print(list2)
[1, 1, 1, 1]
[0, 1, 1, 1]
[0, 1, 2, 3]

Manipulation

A list can be manipulated using append, extend, del or remove.

list2 = []              # create an empty list
print(list2)

list2.append(1)         # appends one element
print(list2)

list2.extend([2, 3, 4]) # appends a sequence
print(list2)

list2.insert(2, 0)      # inserts 0 at index 2
print(list2)

del(list2[2])           # removes the element at index 2
print(list2)

list2.remove(1)         # removes the first element that is equal to the argument.. 
print(list2)            # .. raises an error if argument not in list
[]
[1]
[1, 2, 3, 4]
[1, 2, 0, 3, 4]
[1, 2, 3, 4]
[2, 3, 4]

More list operations

The following cells should give an overview about the usage and capabilities of lists. In general it is better to learn what is possible first and then focus on the details (because the details can be quikly looked up).

L = [1, 2] * 4                       # repeats the given list 4 times
print(L)
[1, 2, 1, 2, 1, 2, 1, 2]
L = [1, 2] + [3, 4] + [5, 6]         # the + operator can be used to concatenate lists
print(L)
[1, 2, 3, 4, 5, 6]
L = [1, 2, 3]
L.reverse()                          # reverse the elements in the list
print(L)
[3, 2, 1]
L = [1, 2, 1, 3]
n = L.count(1)                       # count occurences of a value
print(n)
2
L = ['a', 'b', 'c']
b = 'a' in L                         # x in L returns true if x matches at least one element in L
print(b)
True
L = [5,8,2,6,4,2.5,1]
L.sort()                             # sort list (only possible if values are comparable)
print(L)
[1, 2, 2.5, 4, 5, 6, 8]
L = [5,8,2,6,4,2.5,1]
min_v  = min(L)                      # min(L) / max(L) will return minimum / maximum value in L
max_v  = max(L)
length = len(L)                      # len(L) will return the number of elements in L 
print('minimum =', min_v)
print('maximum =', max_v)
print(' length =', length)
minimum = 1
maximum = 8
 length = 7

More information about lists available <a href = https://docs.python.org/3/tutorial/datastructures.html>here</a>.

Tuples


Tuples are very similar to lists with the essential difference, that they are not mutable. A tuple is created with the values in parentheses:

T = (False, 1.0, '2', 3)      # creation of a tuple
print('Type:  ', type(T))
print('Values:', T)
Type:   <class 'tuple'>
Values: (False, 1.0, '2', 3)

Once a tuple is initialized, it is neither valid to change its values nor to add/remove elements. E.g. trying to assign a new value to the first element in T will raise a TypeError:

print(T[0])                   # accessing elements is valid
#T[0] = 1.0                   # will raise a TypeError
False

Any list functionality does not modify the entries can also be applied to tuples. E.g.

  • indexing / slicing
  • the functions max() / min() / len()
  • the in-operator.

Note: In many cases the parentheses are not required to create a tuple, but it is best practice to use perenthesis. Nevertheless the following cell is valid.

T = False, 1.0, '2', 3        # no parenthesis used to create the tuple
print(T, type(T))
print('Values:', T)
(False, 1.0, '2', 3) <class 'tuple'>
Values: (False, 1.0, '2', 3)

More information about tuples is available <a href = https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences>here</a>.

Dictionaries


A dictionary is basically a list of key-value pairs where every key is unique and points to exactly one value. Like with lists and tuples the data type of both the keys and the values can vary. A dictionary is constructed using braces. Inside the braces the keys and values are separated by a colon, the key-value pairs are separated by commas:

D = {'a':1, 'b':2.0, 15:'x'}   # a dictionary with 3 key-value pairs
print('Type:  ', type(D))
print('Values:', D)
Type:   <class 'dict'>
Values: {'a': 1, 'b': 2.0, 15: 'x'}

The value for a specific key in a dictionary D is accessed using the syntax D[k], where k is the key. An error will be raised if the key is not in the dictionary.

D = {'a':1,'b':2}
v = D['b']         # access the value that corresponds to the key 'b'
print(v)
2

Modifying values of existing keys and adding new key-value pairs works in the same way:

D = {'a':1,'b':2}
D['c'] = 3         # add a new key 'c' with the value 3
print(D)
D['c'] = 5         # change the value of key 'c' to 5
print(D)
{'a': 1, 'b': 2, 'c': 3}
{'a': 1, 'b': 2, 'c': 5}

The items() method of dictionaries can be used to iterate over each key value pair. Alternatively it can be used to convert a dictionary to a list of tuples.

for d in D.items():
    print(d)
('a', 1)
('b', 2)
('c', 5)
D_as_list = list(D.items())
print(D_as_list)
[('a', 1), ('b', 2), ('c', 5)]

More information about sets available <a href = https://docs.python.org/3/tutorial/datastructures.html#dictionaries>here</a>.

Sets


A set contains unique elements in an unordered fashion (varying data types allowed). Unique means, that every element will not occur more than one time in the set. Adding a element to a set, that already contains such a element will not modify the set. Unordered means, that the elements in a set don’t have indices and though indexing or slicing is not possible. Sets are created similar to dictionarys but without colons:

S = {1,2,3,1}     # the element 1 will only be once in S, because sets do not allow dublicate entries 
print('Type:  ',type(S))
print('Values:',S)
Type:   <class 'set'>
Values: {1, 2, 3}

Albeit sets don’t support indexing, they allow some useful operations like computing the intersection, the union or the disjoints:

S1 = {'a', 'b', 'd', 1, 2, 4}
S2 = set((1, 3, 4, 'a', 'c'))   # alternative constructor (same as casting a tuple to a set)

print('Elements in S1:               ', S1)
print('Elements in S2:               ', S2)

S_intersect = S1 & S2 # = S1.intersection(S2)
print('Elements in S1 and S2:        ', S_intersect)

S_union = S1 | S2 # = S1.union(S2)
print('Elements in S1 or S2:         ', S_union)

S_xor = S1 ^ S2 # = S1.difference(S2).union(S2.difference(S1)))
print('El. in S1 or S2, not in both: ', S_xor)
Elements in S1:                {1, 2, 4, 'a', 'b', 'd'}
Elements in S2:                {'c', 1, 3, 4, 'a'}
Elements in S1 and S2:         {1, 4, 'a'}
Elements in S1 or S2:          {'c', 1, 2, 3, 4, 'a', 'b', 'd'}
El. in S1 or S2, not in both:  {'c', 2, 3, 'b', 'd'}

More information about sets is available <a href = https://docs.python.org/3/tutorial/datastructures.html#sets>here</a>.

Loops


While loop

The most simple loop is the while loop. The syntax is:

while cond:
    inside loop
    still inside loop
outside the loop

Where cond is a condition. The loop will repeat until the condition is evaluated to False. The following example runs a while-loop until there are no more elements in the list L. In each iteration the first element of L is removed.

L = ['a', 'b', 'c']  # create a list with 3 elements

while len(L) > 0:    # iterate as long as L is not empty (number of elements greater than zero)
    print(L)         # print whole list
    del(L[0])        # delete the first element in L
    
print('done')
['a', 'b', 'c']
['b', 'c']
['c']
done

For loop

Python offers a very simple for-in syntax to iterate over each element in a collections like a list, a tuple or a set:

for a in B:
    inside loop
    still inside loop
outside the loop

Here B is a collection of elements (like a list, set, tuple etc.) and a the variable that will take the values of the elements in B. The indented lines after the colon are executed in every loop. In the next cell we will use a for loop to iterate over the elements in a list and print each element:

L = ['e1', 2, 3.5]  # creating a list
for elem in L:
    print(elem)     # elem will take the value of each element in L
e1
2
3.5

For loops can also be used to execute a block n times, having a variable that holds the number of the iteration. To create such a loop, we can use Pythons range() method, which will create something similar to a list:

n = 3
for i in range(n):    # range(3) creates the sequence [0, 1, 2]
    print('inside the loop..')
    print('i =', i)
    
print('done')
inside the loop..
i = 0
inside the loop..
i = 1
inside the loop..
i = 2
done

If range(x) is called with only one argument, the loop will start at 0 and incrementing up to x (not included) with a stepwith of 1. More general, one can call range() with up to three arguments, which denote the start, stop and step with of the sequence. Two arguments denote start and stop (step width will be 1).

start = 1; stop = 6; step = 2

for j in range(start, stop, step):
    print(j)
1
3
5

List comprehension

The following example demonstrates a powerful feature in Python which uses the for-in syntax called list comprehension. More information available <a href=https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions>here</a>.

lst_sq = [x**2 for x in range(1, 11)] # 'for' inside brackets used to create a list
print(lst_sq)                              
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Enumeration

The enumerate() function is very helpful when iterating over sequences and keeping track of the index

L = ['a','b','c']
for index, elem in enumerate(L):
    print('Element {}: {}'.format(index, elem))
Element 0: a
Element 1: b
Element 2: c

Looping over two lists

When it is necessary to iterate over two lists simultaneously one can use the built-in zip() function. It takes multiple lists as arguments and creates a list-like object of touples, holding the corresponding elements of all lists. This is demonstrated in the next cell:

L1 = ['a', 'b', 'c']
L2 = [1, 2, 3]
L3 = ['I', 'II', 'III']

for x,y,z in zip(L1, L2, L3):
    print(x, y, z)
a 1 I
b 2 II
c 3 III

Break and continue

When using loops, there are two essential keywords: break and continue.

  • break (immediately step out of the loop)
  • continue (immediately start the next iteration)

The following cells will demonstrate the usage of these keywords.

L = []                # create an empty List

for i in range(10):   # i will have the values [0,1,...,9]
    if i % 2 == 0:    # if i is even..
        continue      # ..jump to the next iteration
    L.append(i)       # append the current value of i to L
    
print(L)
[1, 3, 5, 7, 9]
# example for getting the index of a specific value
L = ['a','b','c','d']  
t = 'd'
i = 0
while True:           # no condition (would run till infinity)
    if L[i] == t:     # check if we found the value
        break         # leave the loop
    i += 1            # increment search index pos
    
print(i)
3

Of course the example above is not a good way to get the index of an element, because it would result in an error, if t is not in L. Besides that, Python has already a solution for this task:

i = L.index(t)   # to be fair, this also raises an error if t is not in L
print(i)
3

Functions


A function is a piece of code that can be used/called multiple times. A function is declared with the keyword def, followed by the functions name and the parameters in parentheses. It is followed by a colon and the function body. Functions can have a varying number of parameters (including zero parameters) which can be used for whatever computation in the body of the function. In the following cell, a function is defined and then used two times.

#---------------DEFINITION OF say()--------------

def say(text):      # definition of a function with one parameter 
    print(text)     # calling print()
    
#------------------USAGE OF say()----------------

say('hello')        # calling say() 
say('world')        # calling say() again with a different argument
hello
world

In general, functions are used to take some arguments, do some computation with them and then return a value. Python functions will always return exactly one value. The following function takes two arguments and returns the absolute distance of them. We declare, which value should be returned with the keyword return followed by the variable name.

#---------------DEFINITION--------------

def distance(x, y):    # definition of a function with two parameters
    dist = abs(x-y)    # abs() is a python built-in function that returns the absolute value
    return dist        # return the value of 'dist'

#------------------USAGE----------------

a = -1.5
b = 12.7
c = distance(a,b)   # calling distance() with arguments a and b. The result is assigned to c
print(c)
14.2

You may wonder what the function say() returned, because we did not use return at all. By default any function will return the special value None. The following implementations are equivalent to the previous say() function. All of them return None.

def say1(text):   # first implementation, no return statement
    print(text)
    
def say2(text):   # 2nd implementation, return without value
    print(text)
    return
    
def say3(text):   # 3rd implementation, explicitly returning None
    print(text)
    return None

r1, r2, r3 = say1('1'), say('2'), say('3') # storing the return values of all functions

print(r1,r2,r3)
1
2
3
None None None

Multiple return types

Functions can return multiple elements by implicitly using tuples (here one usually doesn’t use the parentheses to declare tuples).

def divide_euclidian(dividend,divisor):
    quotient = dividend // divisor
    remainder = dividend % divisor
    return quotient, remainder         # same as: return (quotient, remainder)

q, r = divide_euclidian(15, 7)         # same as: (q, r) = divide_euclidian(15,7)
print('15 / 7 = {} R: {}'.format(q, r))
15 / 7 = 2 R: 1

Info: Python already offers this function:

q, r = divmod(15,7)
print('15 / 7 = {} R: {}'.format(q, r))
15 / 7 = 2 R: 1

Keyword parameters

Functions can have optional keyword parameters. These keyword parameters are always the last (rightmost) parameters of all. They are declared by a following equal sign and the default value. The following function has one obligatory and one optional parameters. In this example the optional parameter is used as a so called ‘flag’ (like an on-off switch). In general keyword arguments can have any type.

def square(a, verbose=False):     # optional keyword parameter (here a bool)
    if verbose:
        print('square({}) was called!'.format(a))
    return a**2

sq1 = square(1)                   # nothing will be printed
sq2 = square(2, verbose=True)     # will print the message
square(2) was called!

Built-in functions and modules


Python provides many built-in functions. Some essential functions are used in the next cell. A complete list is available at <a href=https://www.programiz.com/python-programming/methods>programiz.com</a>. Note that different Python versions may provide more/less built-in functions.

print('max(1, 9, -5) =', max(1, 9, -5))    # max(a, b, ..) returns the biggest value
print('min(-3, 3, 0) =', min(-3, 3, 0))    # min(a, b, ..) returns the smallest value
print('abs(-2)       =', abs(-2))          # abs(x)        returns the absolute value
max(1, 9, -5) = 9
min(-3, 3, 0) = -3
abs(-2)       = 2

Note that there are built-in functions like del() which are Python keywords and others like max(), which are no keywords. Unlike keyword built-ins, the non-keyword built-ins can be overwritten! This is demonstrated in the following cell:

# Example, that built-ins can be overwritten!

print('max:        ', max)
print('type of max:', type(max))
print('max(1,2):   ', max(1,2))
#del(max)                             # ERROR (name 'max' is not defined)

print('\noverwriting max!\n')
max = 12345

print('max:        ', max)
print('type of max:', type(max))
#print('max(1,2):   ', max(1,2))      # ERROR ('int' object is not callable)
del(max)
max:         <built-in function max>
type of max: <class 'builtin_function_or_method'>
max(1,2):    2

overwriting max!

max:         12345
type of max: <class 'int'>

Importing modules

We can additionally import modules like the math module to have access to the functionalities of that module. We can use the dir() function to list the available functionalities.

import math     # import the math module to use its features
#dir(math)      # list functionalities of a module

At this point we will introduce another helpful IPython feature. We can append a question mark to any module, variable, function or class, to show additional information and the documentation about it (alternatively click on a function press [SHIFT]+[TAB]):

math.log10?
math.pi?

To access / use a functionality we use the dot-operator (a dot between the module name and the function / variable).

x = math.log10(10000) # logarithm with base 10
print(x)

p = math.pi           # the constant pi
print(p)

y = math.cos(2*math.pi)
print(y)
4.0
3.141592653589793
1.0

Classes


Classes can be seen as definitions of particular objects including constructors, attributes and methods. The general idea is that we define a class in order to create multiple instances of it, that all share some functionalities. Today the object oriented programming (OOG) is used in most programs.

Class definition

The following cell defines the class dog. The class contains multiple methods (methods are functions of a class).

class dog:                        # the definition of a class starts with the class keyword and the name of the class
    def __init__(self, name):     # the init method defines the creation of an instance of this class
        self.name = name      
        self.age = 0              # our dogs will have two attributes: name and age. 'self' refers to the new dog-object
        
    def celebrate_birthday(self): # this method will increase the age of the corresponding object by one
        self.age += 1
    
    def rename(self, new_name):   # this method takes a new name as argument and assignes it to the dogs name
        self.name = new_name
    
    def __str__(self):            # definition how to cast a dog to a printable string
        return '{}, {} years old'.format(self.name, self.age)

Class usage

Now we will create two instances of that class and use some of their methods.

dog1 = dog('Bonny')               # initialization of a dog. Note that only one argument is given (the name)
dog2 = dog('Clyde')               # 'self' is always passed implicitly

dog1.rename('Bonnie')             # call of dog1s rename method with dot operator 

for i in range(5):
    dog1.celebrate_birthday()
    dog2.celebrate_birthday()     # call celebrate_birthday() 5 times for both dogs

print(dog1)
print(dog2)                       # print will implicitly use our implemented string conversion

print(dog1.age)                   # access attribute of dog1 with dot operator
Bonnie, 5 years old
Clyde, 5 years old
5

Additional information about classes is available e.g. at the <a href=https://docs.python.org/3/tutorial/classes.html>official python documentation</a>.

Assertions


Assertions are a nice way to prevent unexpected behavior of code. Let’s consider an example function that divides two numbers. The division is only valid, if the divisor is not zero. So in our code we can simply make the assertion that this is not the case and add a reason to document this. The syntax for assertions is

assert statement, "Message(optional)"

Where the statement is a variable or expression, that can be evaluated to a single boolean value.

The division example could be implemented like this:

def divide(dividend, divisor):
    assert divisor != 0, "Division by zero not valid!"
    return dividend/divisor

Let us now try to call that function with an invalid divisor:

result = divide(1, 0)
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-84-bfb753171451> in <module>()
----> 1 result = divide(1, 0)


<ipython-input-83-c62598d57140> in divide(dividend, divisor)
      1 def divide(dividend, divisor):
----> 2     assert divisor != 0, "Division by zero not valid!"
      3     return dividend/divisor


AssertionError: Division by zero not valid!

This will raise an AssertionError with our message, so we directly know what we did wrong. Maybe this is not the best example, because without the assertion the call would raise a ZeroDivisionError, which is also pretty obvious, but the general idea should be clear.

Common errors


This chapter provides some code examples that raise different errors. Understanding the reasons for these errors might help you debugging your first programs! There are much more error types, than the ones listed below. If you are confronted with an unknown error, you may take a look at <a href=https://docs.python.org/3/library/exceptions.html>this documentation</a> or simply google for it.

SyntaxError

y =                      # incomplete statement
  File "<ipython-input-85-fbe96dcb0c90>", line 1
    y =                      # incomplete statement
                                                   ^
SyntaxError: invalid syntax
x = (1+2) * ((3+5)/3))   # additional parentheses
  File "<ipython-input-86-7d4e5ab904ff>", line 1
    x = (1+2) * ((3+5)/3))   # additional parentheses
                         ^
SyntaxError: invalid syntax
x = (1+2) * ((3+5)/3     # missing parentheses
  File "<ipython-input-87-f684cc964df7>", line 1
    x = (1+2) * ((3+5)/3     # missing parentheses
                                                  ^
SyntaxError: unexpected EOF while parsing

IndentationError

if True:
x = 1                    # missing indentation
  File "<ipython-input-88-34b81d45b027>", line 2
    x = 1                    # missing indentation
    ^
IndentationError: expected an indented block
a = 1
 b = 2                   # invalid indentation
  File "<ipython-input-89-c9c618659aa0>", line 2
    b = 2                   # invalid indentation
    ^
IndentationError: unexpected indent

TypeError

abs()                    # calling abs() with no arguments (one argument required)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-90-db1c3457168d> in <module>()
----> 1 abs()                    # calling abs() with no arguments (one argument required)


TypeError: abs() takes exactly one argument (0 given)
a,b = abs(1)             # trying to assign the result of abs() to a tuple (only one return value)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-91-bfe63526b606> in <module>()
----> 1 a,b = abs(1)             # trying to asign the result of abs() to a tuple (only one return value)


TypeError: 'int' object is not iterable
a = 1
a[0]                     # trying to access first element of integer (non sequence object)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-92-dd699019f98a> in <module>()
      1 a = 1
----> 2 a[0]                     # trying to access first element of integer (non sequence object)


TypeError: 'int' object is not subscriptable
a = 1
a()                      # parentheses indicate that a() is a method, but it is not
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-93-e083f2705fea> in <module>()
      1 a = 1
----> 2 a()                      # parentheses indicate that a() is a method, but it is not


TypeError: 'int' object is not callable

ValueError

int('1.5')               # string cannot be converted to int
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-94-eee59e071eac> in <module>()
----> 1 int('1.5')               # string cannot be converted to int


ValueError: invalid literal for int() with base 10: '1.5'

Catching errors


Programs (or functions) that cause errors are not always wrong. E.g. imagine a program that tries to download some content from the Internet. The connection could be interrupted which could result in an unavoidable error. In that case it would be necessary to have a program that expects and such errors. In Python this is done via the try-catch-finally blocks.

try:                     
    int('1.0')              # the try block contains statements that may raise an error
    a = 1/0
except ValueError:
    print('conv. failed')   # if the declared error occurs, the statements in the except block are executed
except ZeroDivisionError:
    print('division by 0')  # multiple different errors can be excepted
finally:
    print("don't care!")    # the finally block (optional) contains statements, that are ALWAYS executed after the try-except
conv. failed
don't care!

Lambda expressions


Lambda expressions are used to create anonymous functions. These can be useful to replace functions:

  • that are only used in a small context (e.g. only in one statement)
  • whose return value can be computed in one statement.

The following cell shows the usage of lambda expression to define a function:

def add(x, y):          # common way to define a function
    return x + y

addL = lambda x,y : x+y  # using lambda to define a function

print('Type of add: ', type(add))
print('Type of addL:', type(addL))

print('\nResult of add(1,2): ', add(1,2))
print('Result of addL(1,2):', addL(1,2))
Type of add:  <class 'function'>
Type of addL: <class 'function'>

Result of add(1,2):  3
Result of addL(1,2): 3

Usually lambda functions are not stored in variables, but instead directly passed as arguments. E.g. the built-in function max() has the keyword parameter key of type function. We can e.g. use this keyword, to make the function search for the biggest absolute value:

values = [-3, 8, -15, 2]

abs_max = max(values, key = lambda x: abs(x))
print('{} has the highest absolute value in {}.'.format(abs_max, values))
-15 has the highest absolute value in [-3, 8, -15, 2].

Mixed topics


This is a small collection of additional Python knowledge.

  • In an interactive shell, the last result will be stored to _

  • Tripple apostrophs introduce multi line comments, tripple quotes introduce multi-line strings:
'''
a 
multi-line
comment
'''
s = """a 
multi-line
string"""

print(s)
a 
multi-line
string
  • Strings are similar to tuples of single characters so indexing and slicing can be applied to strings
s = ('xyz'*5)
print(s)
print(s[:5])
print(s[-1])
xyzxyzxyzxyzxyz
xyzxy
z
  • Conversion between different integer systems
x = 0xFF        # hexadecimal
print(hex(x),'=',x)
0xff = 255
b = 0b1000       # binary
print(bin(b),'=',b)
0b1000 = 8
o = 0o11         # octal
print(oct(o),'=',o)
0o11 = 9
  • Since Python 3 integers can have arbitrary size
f = 2.0**1023   # limit of float value
print(f)

i = 3**5000     # int has no limit
# print(i)
# print(bin(i)) # simple way to produce many zeros and ones
8.98846567431158e+307
  • Creation of a char from its unicode number (<a href=unicode-table.com>see unicode table</a>) with \u in strings
lmbda = '\u03BB'
print(lmbda)
λ
  • Creation of a char from its ascii number (<a href=https://www.asciitable.com/>see ascii table</a>) with chr()
heart = chr(60) + chr(51)
print(heart)
<3
  • Tricky list augmentation. Although the following four implementations have the same result, the first one is about 1000 times slower than the others (because new memory space is allocated in each iteration)!
L = [1,2]

L = L + [42]   # impl 1   VERY SLOW !!
L += [42]      # impl 2
L.append(42)   # impl 3
L.extend([42]) # impl 4

print(L)
[1, 2, 42, 42, 42, 42]
  • Python supports an else block after loops (and try-catch blocks). That is only reached, when the while condition is not fulfilled (and not reached when the loop is terminated with the break statement)
i = ''
while i != 'exit':
    i = input('enter exit or break: ')
    if i == 'break':
        break
else:
    print('else block')
print('done')
enter exit or break: exit
else block
done

Author: Dennis Wittich
Last modified: 13 / 08 / 2018