8   Dive into Functions

8.1   Exercises

8.1.1   Exercise

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

Hint locals print a dictionary with local variable as keys and their respective values.

_images/cf_exo_1.png
x = 4

def func():
   y  = 5
   print(locals())

>>> func()
{'y': 5}
>>> print(x)
4
_images/spacer.png

8.1.2   Exercise

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

Hint locals print a dictionary with local variable as keys and their respective values.

_images/cf_exo_2.png
x = 4

def func():
   y = 5
   x = 8
   print(locals())
   x = x + 2

>>> y = func()
{'y': 5, 'x': 8}
>>>
>>> print(y)
None
>>> print(x)
4
_images/spacer.png

8.1.3   Exercise

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

Hint locals print a dictionary with local variable as keys and their respective values.

_images/cf_exo_3.png
x = 4

def func(a):
   y = x + 2
   print(locals())
   x = y
   return y

>>> y = func(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in func
UnboundLocalError: local variable 'x' referenced before assignment

Unlike what we might think in y = x + 2 x is not get from the global scope. As soon as you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. even if the assignment appear later in code. Here x = y make x as local variable whatever you are in func. so at line y = x + 2 we try to use the local variable x but we have to asign it a value (it is done later) so Python raise an UnboundLocalError (see python faq for details)

_images/spacer.png

8.1.4   Exercise

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

Hint locals print a dictionary with local variable as keys and their respective values.

_images/cf_exo_4.png
x = 4

def func(a):
   x = x + 2
   print(locals())
   return x

y = func(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in func
UnboundLocalError: local variable 'x' referenced before assignment

print y
print y == x
_images/spacer.png

8.1.5   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_5.png
x = 4

def func(x):
   x = x + 2
   return x

y = func(x)

>>> print(x)
4
>>> print(y == x)
False
_images/spacer.png

8.1.6   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_6.png
def func():
   y = x
   return y

>>> x = 4
>>> z = func()
>>>
>>> print(x)
4
>>> print(z)
4
>>> print(id(z) == id(x))
True
_images/spacer.png

8.1.7   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_7.png
x = 4

def func(x=5):
   x = x + 2
   return x

>>> y = func(x)
>>>
>>> print(x)
4
>>> print(y)
6
_images/spacer.png

8.1.8   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

Hint locals print a dictionary with local variable as keys and their respective values.

_images/cf_exo_8.png
x = 4

def func(a):
   global x
   def func2():
      print(locals())
      y = x + 4
      return y
   z = func2()
   return z

y = func(x)
{}
>>> print(x)
4
>>> print(y)
8
_images/spacer.png

8.1.9   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_9.png
x = {'a' : 4}

def func(a):
   a['b'] = 5
   return a

y = func(x)

>>> print(x)
{'a': 4, 'b': 5}
>>> print y
{'a': 4, 'b': 5}
>>> print(x is y)
True
_images/spacer.png

8.1.10   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_10.png
x = {'a' : 4}

def func(a):
   a['b'] = 5

y = func(x)

>>> print(x)
{'a': 4, 'b': 5}
>>> print(y)
None
_images/spacer.png

8.1.11   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_11.png
x = {'a' : 4}

def func(a):
   x['b'] = 5
   def func2():
      a['b'] = 6
   return a

y = func(x)

print x
{'a': 4, 'b': 5}
print y
{'a': 4, 'b': 5}
a refer to same object as x
in func x does not exist, so x refer to global variable x
we mutate x
func2 is never executed
we return a so the same object referred by x
so y refer also the same object as x
_images/spacer.png

8.1.12   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_12.png
x = {'a' : 4}

def func(a):
   x['b'] = 5
   def func2():
      a['b'] = 6
   func2()
   return a

y = func(x)

print(x)
{'a': 4, 'b': 6}
in this code func2 is executed
a is not in func2 namespace so python find it in enclosing namespace func
a refer the same object as x
_images/spacer.png

8.1.13   Exercice

Without executing the code in Python interpreter, can you determine what the code below print out. help you by drawing diagram.

_images/cf_exo_13.png
x = {'a' : 4}

def func(a):
   x['b'] = 5
   def func2(x):
      x['b'] = 6
   func2(a.copy())
   return a

y = func(x)

print(x)
{'a': 4, 'b': 5}
in this code x a variable x is defined locally in func2 and hide the global variable x
this local x refer to a shallow copy of the func local variable a which refer to the same object as global variable x
so func2 does not do a side effect on dictionary referred by x as in previous example.
_images/spacer.png

8.1.14   Exercise

Write a function translate that have a nucleic sequence as parameter, and return the translate sequence. We give you a genetic code :

code = {  'ttt': 'F', 'tct': 'S', 'tat': 'Y', 'tgt': 'C',
           'ttc': 'F', 'tcc': 'S', 'tac': 'Y', 'tgc': 'C',
           'tta': 'L', 'tca': 'S', 'taa': '*', 'tga': '*',
           'ttg': 'L', 'tcg': 'S', 'tag': '*', 'tgg': 'W',
           'ctt': 'L', 'cct': 'P', 'cat': 'H', 'cgt': 'R',
           'ctc': 'L', 'ccc': 'P', 'cac': 'H', 'cgc': 'R',
           'cta': 'L', 'cca': 'P', 'caa': 'Q', 'cga': 'R',
           'ctg': 'L', 'ccg': 'P', 'cag': 'Q', 'cgg': 'R',
           'att': 'I', 'act': 'T', 'aat': 'N', 'agt': 'S',
           'atc': 'I', 'acc': 'T', 'aac': 'N', 'agc': 'S',
           'ata': 'I', 'aca': 'T', 'aaa': 'K', 'aga': 'R',
           'atg': 'M', 'acg': 'T', 'aag': 'K', 'agg': 'R',
           'gtt': 'V', 'gct': 'A', 'gat': 'D', 'ggt': 'G',
           'gtc': 'V', 'gcc': 'A', 'gac': 'D', 'ggc': 'G',
           'gta': 'V', 'gca': 'A', 'gaa': 'E', 'gga': 'G',
           'gtg': 'V', 'gcg': 'A', 'gag': 'E', 'ggg': 'G'
      }

8.1.14.1   bonus

This function have to take the phase as parameter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
genetic_code = { 'ttt': 'F', 'tct': 'S', 'tat': 'Y', 'tgt': 'C',
           'ttc': 'F', 'tcc': 'S', 'tac': 'Y', 'tgc': 'C',
           'tta': 'L', 'tca': 'S', 'taa': '*', 'tga': '*',
           'ttg': 'L', 'tcg': 'S', 'tag': '*', 'tgg': 'W',
           'ctt': 'L', 'cct': 'P', 'cat': 'H', 'cgt': 'R',
           'ctc': 'L', 'ccc': 'P', 'cac': 'H', 'cgc': 'R',
           'cta': 'L', 'cca': 'P', 'caa': 'Q', 'cga': 'R',
           'ctg': 'L', 'ccg': 'P', 'cag': 'Q', 'cgg': 'R',
           'att': 'I', 'act': 'T', 'aat': 'N', 'agt': 'S',
           'atc': 'I', 'acc': 'T', 'aac': 'N', 'agc': 'S',
           'ata': 'I', 'aca': 'T', 'aaa': 'K', 'aga': 'R',
           'atg': 'M', 'acg': 'T', 'aag': 'K', 'agg': 'R',
           'gtt': 'V', 'gct': 'A', 'gat': 'D', 'ggt': 'G',
           'gtc': 'V', 'gcc': 'A', 'gac': 'D', 'ggc': 'G',
           'gta': 'V', 'gca': 'A', 'gaa': 'E', 'gga': 'G',
           'gtg': 'V', 'gcg': 'A', 'gag': 'E', 'ggg': 'G'
      }

def translate(nuc_seq, code):
    
    prot_seq = ''
    n = 0
    # to avoid to compute len(seq)/3 at each loop
    # I compute it once and use a reference
    # it could be expensive if the sequence is very long.

    # another way to determine the end of looping
    # stop_iteration = len(nuc_seq)
    # while (start + 2) < stop_iteration:
    cycle = len(nuc_seq)//3
    while n < cycle:
        start = n * 3
        end = start + 3
        codon = nuc_seq[start:end]
        codon = codon.lower()
        if codon in code:
            prot_seq += code[codon] 
        else:
            raise RuntimeError("unknow codon: " + codon)
        n += 1
        # if use the other looping solution
        # n += 3
    return prot_seq
        
def translate2(nuc_seq, code, phase = 1):
    prot_seq = ''
    if 0 < phase < 4 :
        start = phase - 1
        nuc_seq = nuc_seq[start:]
    elif -4 < phase < 0:
        start = -phase - 1
        nuc_seq = nuc_seq[::-1]
        nuc_seq = nuc_seq[start:]
    prot_seq = translate(nuc_seq, code)
    return prot_seq

translate.py .

8.1.15   Exercise

Write a program that calculates the similarity of 2 RNA sequences.

  • To compute the similarity you need to parse a file containing the similarity matrix.

    Hint: use the module containing the functions that handle a matrix from previous chapter. put this matrix.py file in a directory named “my_python_lib” in your home or Desktop and import it in your current program (the similarity script must be placed elsewhere).

  • The similarity of the 2 sequences is the sum of base similarities. so you have to compare the first base of two sequences and use the matrix to get the similarity from the similarity table, on so on for all bases then sum these similarities.

First implementation (list of columns)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
"""
Implementation of simple matrix 
"""


def create(row_num, col_num, val=None):
	"""
	:param row_num: the number of rows
	:type row_num: int
	:param col_num: the number of columns
	:type col_num: int
	:param val: the default value to fill the matrix
	:type val: any (None by default)
	:return: matrix of rows_num x col_num
	:rtype: matrix
	"""
	matrix = []
	for i in range(col_num):
		col = [val] * row_num
		matrix.append(col)
	return matrix


def _check_index(matrix, row_no, col_no):
	"""
	check if row_no and col_no are in matrix bound
	
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:param rows_no: the index of row to check
	:type rows_no: int
	:param col_no: the index of column to check
	:type col_no: int
	:raise: IndexError if row_no or col_no are out of matrix bounds
	""" 
	row_max, col_max = size(matrix)
	if (row_no < 0 or row_no >= row_max) or (col_no < 0 or col_no >= col_max):
		raise IndexError("matrix index out of range")
	
	
def size(matrix):
	"""
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:return: the size of matrix (number of rows, number of cols)
	:rtype: typle of 2 int
	"""
	return len(matrix[0]), len(matrix)


def get_cell(matrix, row_no, col_no):
	"""
	:param matrix: the matrix 
	:type matrix: matrix
	:param rows_no: the row number
	:type rows_no: int
	:param col_no: the column number
	:type col_no: int
	:retrun: the content of cell corresponding to row_no x col_no
	:rtype: any
	"""
	_check_index(matrix, row_no, col_no)
	return matrix[col_no][row_no]


def set_cell(matrix, row_no, col_no, val):
	"""
	set the value val in cell specified by row_no x col_no
	
	:param matrix: the matrix to modify
	:type matrix: matrix
	:param row_no: the row number of cell to set
	:type rows_no: int
	:param col_no: the column number of cell to set
	:type col_no: int
	:param val: the value to set in cell 
	:type val: int
	"""
	_check_index(matrix, row_no, col_no)
	matrix[col_no][row_no] = val


def to_str(matrix):
	"""
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:return: a string representation of the matrix
	:rtype: str
	"""
	s = ""
	# by design all matrix cols have same size
	for row in zip(*matrix):
		cells = [str(cell) for cell in row]
		s += "\t".join(cells) + "\n"
	return s


def mult(matrix, val):
	"""
	:param matrix: the matrix
	:type matrix: matrix
	:param val: the value to mult the matrix with
	:type val: int
	:return: a new matrix corresponding the scalar product of matrix * val
	:rtype: matrix
	"""
	new_matrix = []
	for col in matrix:
		new_col = [cell * val for cell in col]
		new_matrix.append(new_col)
	return new_matrix


def mult_inplace(matrix, val):
	"""
	compute the scalar product of a matrix and a value
	do this operation in place

	:param matrix: the matrix
	:type matrix: matrix
	:param val: the value to mult the matrix with
	:type val: int
	"""
	for col in matrix:
		for row_nb, cell in enumerate(col):
			col[row_nb] = cell * val


def get_row(matrix, row_no):
	"""
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:param rows_no: row number
	:type rows_no: int
	:return: the row of matrix corresponding to row_no
	         a shallow copy of the row
	:rtype: list
	"""
	_check_index(matrix, row_no, 0)
	row_max, col_max = size(matrix)
	row = []
	for col_n in range(col_max):
		row.append(get_cell(matrix, row_no, col_n))
	return row
	
	
def set_row(matrix, row_no, val):
	"""
	set all cells of row row_no with val
	
	:param matrix: the matrix to modify
	:type matrix: matrix
	:param row_no: the row number
	:type row_no: int
	:param val: the value to put in cells
	:type val: any
	"""
	_check_index(matrix, row_no, 0)
	row_max, col_max = size(matrix)
	for col_n in range(col_max):
		set_cell(matrix, row_no, col_n, val)


def get_col(matrix, col_no):
	"""
	:param matrix: the matrix get row
	:type matrix: matrix
	:param col_no: the column number
	:type col_no: int
	:return: the column corresponding to col_no of matrix
	         a shallow copy of the col
	:rtype: list
	"""
	_check_index(matrix, 0, col_no)
	col = matrix[col_no][:]
	return col
	
	
def set_col(matrix, col_no, val):
	"""
	set all cells of col col_no with val
	
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:param col_no: the column number
	:type col_no: int
	:param val: the value to put in cells
	:type val: any
	"""
	_check_index(matrix, 0, col_no)
	for row_n in range(matrix):
		set_cell(matrix, row_n, col_no, val)


def replace_col(matrix, col_no, col):
	"""
	replace column col_no with col
	
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:param col_no: the column number to replace
	:type col_no: int
	:param col: the list of values to use as replacement of column 
	:type col: list
	"""
	row_max, col_max = size(matrix)
	if len(col) != col_max:
		raise RuntimeError("the size of col {0} does not fit to matrix size {1}x{2}".format(len(col),
																						row_max,
																						col_max))
	_check_index(matrix, 0, col_no)
	matrix[col_no] = col


def replace_row(matrix, row_no, row):
	"""
	replace row row_no with row
	
	:param matrix: the matrix to compute the size
	:type matrix: matrix
	:param row_no: the column number
	:type row_no: int
	:param row: the list of value to use as replacement of row 
	:type row: list
	"""
	row_max, col_max = size(matrix)
	if len(row) != row_max:
		raise RuntimeError("the size of row {0} does not fit to matrix size {1}x{2}".format(len(row),
																							row_max,
																							col_max))
	_check_index(matrix, row_no, 0)
	for col_no, value in enumerate(row):
		set_cell(matrix, row_no, col_no, value)


if __name__ == '__main__':
	m = create(5, 3)
	print(m)
	set_cell(m, 0, 0, 1)
	set_cell(m, 0, 2, 2)
	set_cell(m, 4, 0, 12)
	set_cell(m, 4, 2, 15)
	print(to_str(m))
	print("get row 0",  get_row(m, 0))
	print("get col 0", get_col(m, 0))

	m2 = create(3, 2, 4)
	mult_inplace(m2, 2)
	print(to_str(m2))

matrix.py .

Second implementation (bad variable naming, no documentation)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# matrix is implemented by list of list
def matrix_maker(ligne, col, val=None):
	m = []
	for i in range(ligne):
		c = [val] * col
		m.append(c)
	return m

#---- functions that depends on the matrix srtructure 

def matrix_size(m):
	return len(m), len(m[0])

def matrix_get(matrix, i, j):
	_check_matindex(matrix, i, j)
	return matrix[i][j]

def matrix_set(matrix, i, j, val):
	_check_matindex(matrix,i,j)
	matrix[i][j] = val

def to_str(m):
	m_str = []
	for row in m:
		m_str.append('\t'.join([str(cell) for cell in row]))
	m_str = '\n'.join(m_str)
	return m_str



#---- independant regarding matrix structure  
def _check_matindex(matrix,i,j):
	imax, jmax = matrix_size(matrix)
	if (i < 0 or i >= imax) or (j < 0 or j >= jmax):
		raise IndexError("matrix index out of range")


def matrix_get_line(matrix, i):
	_check_matindex(matrix,i,0)
	im, jm = matrix_size(matrix)
	line = []
	for n in range(jm):
		line.append(matrix_get(matrix, i, n))
	return line


def matrix_set_line(matrix, i, val):
	_check_matindex(matrix,i,0)
	im, jm = matrix_size(matrix)
	for n in range(jm):
		matrix_set(matrix, i, n, val)


def matrix_get_col(matrix, j):
	_check_matindex(matrix,0,j)
	im, jm = matrix_size(matrix)
	col = []
	for n in range(im):
		col.append(matrix_get(matrix, n, j))
	return col


def matrix_set_col(matrix, j, val):
	_check_matindex(matrix,0,j)
	im, jm = matrix_size(matrix)
	for n in range(im):
		matrix_set(matrix, n, j, val)
	

if __name__ == '__main__':
	m = matrix_maker(5, 3)
	matrix_set(m,0, 0, 1)
	matrix_set(m,0, 2, 2)
	matrix_set(m,4, 0, 12)
	matrix_set(m,4, 2, 15)
	print(to_str(m))
	print("get line 0",  matrix_get_line(m, 0))
	print("get col 0", matrix_get_col(m, 0))

matrix2.py .

Third implementation (dict {(row, col): value})

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
"""
Implementation of simple matrix
"""


def create(nb_rows, nb_cols, val=None):
    """
    :param nb_rows: the number of rows
    :type nb_rows: int
    :param nb_cols: the number of columns
    :type nb_cols: int
    :param val: the default value to fill the matrix
    :type val: any (None by default)
    :return: matrix of nb_rows x nb_cols
    :rtype: matrix
    """
    matrix = {}
    for i in range(nb_rows):
        for j in range(nb_cols):
            matrix[(i, j)] = val
    return matrix


import sys
major, minor = sys.version_info[:2]
if major < 3 or (major == 3 and minor < 7):
    raise NotImplementedError(
        "Implementation missing for this Python version:"
        "{}.{}".format(major, minor))
else:
    def size(matrix):
        """
        :param matrix: the matrix of which we want the size
        :type matrix: matrix
        :return: the size of matrix (number of rows, number of cols)
        :rtype: typle of 2 int
        """
        # Not robust, and needs a Python version where dicts preserve
        # key insertion order.
        # Only works because matrix was created
        # using a dict where the last inserted key corresponded
        # to the the last row and last column
        (last_row, last_col) = list(matrix.keys())[-1]
        return (last_row + 1, last_col + 1)


def _check_bounds(matrix, row, col):
    """
    Check whether row and col are compatible with the matrix size.

    :param matrix: the matrix to check
    :type matrix: matrix
    :param row: the index of row to check
    :type row: int
    :param col: the index of column to check
    :type col: int
    :raise: IndexError if row or col are out of matrix bounds
    """
    (nb_rows, nb_cols) = size(matrix)
    if (row < 0 or row >= nb_rows) or (col < 0 or col >= nb_cols):
        raise IndexError("matrix index out of range")


def get_cell(matrix, row, col):
    """
    :param matrix: the matrix
    :type matrix: matrix
    :param row: the row number
    :type row: int
    :param col: the column number
    :type col: int
    :return: the content of cell corresponding to row x col
    :rtype: any
    """
    _check_bounds(matrix, row, col)
    return matrix[(row, col)]


def set_cell(matrix, row, col, val):
    """
    Set the value val in cell specified by row x col.

    :param matrix: the matrix to modify
    :type matrix: matrix
    :param row: the row number of cell to set
    :type row: int
    :param col: the column number of cell to set
    :type col: int
    :param val: the value to set in cell
    :type val: int
    """
    _check_bounds(matrix, row, col)
    matrix[(row, col)] = val


def mult(matrix, val):
    """
    :param matrix: the matrix
    :type matrix: matrix
    :param val: the value to mult the matrix with
    :type val: int
    :return: a new matrix corresponding the scalar product of matrix * val
    :rtype: matrix
    """
    new_matrix = []
    for (coords, value) in matrix.items():
        new_matrix[coords] = value * val
    return new_matrix


def mult_inplace(matrix, val):
    """
    Compute the scalar product of a matrix and a value
    do this operation in place

    :param matrix: the matrix
    :type matrix: matrix
    :param val: the value to mult the matrix with
    :type val: int
    """
    for (coords, value) in matrix.items():
        matrix[coords] = value * val


def get_row(matrix, row):
    """
    :param matrix: the matrix
    :type matrix: matrix
    :param row: row number
    :type row: int
    :return: the row of matrix corresponding to row
    :rtype: list
    """
    _check_bounds(matrix, row, 0)
    _, nb_cols = size(matrix)
    row_values = []
    for col in range(nb_cols):
        row_values.append(get_cell(matrix, row, col))
    return row_values


def set_row(matrix, row, val):
    """
    set all cells of row row with val

    :param matrix: the matrix to modify
    :type matrix: matrix
    :param row: the row number
    :type row: int
    :param val: the value to put in cells
    :type val: any
    """
    _check_bounds(matrix, row, 0)
    _, nb_cols = size(matrix)
    for col in range(nb_cols):
        set_cell(matrix, row, col, val)


def get_col(matrix, col):
    """
    :param matrix: the matrix
    :type matrix: matrix
    :param col: the column number
    :type col: int
    :return: the column corresponding to col of matrix
            a shallow copy of the col
    :rtype: list
    """
    _check_bounds(matrix, 0, col)
    nb_rows, _ = size(matrix)
    col_values = []
    for row in range(nb_rows):
        col_values.append(get_cell(matrix, row, col))
    return col_values


def set_col(matrix, col, val):
    """
    set all cells of col col with val

    :param matrix: the matrix to modify
    :type matrix: matrix
    :param col: the column number
    :type col: int
    :param val: the value to put in cells
    :type val: any
    """
    _check_bounds(matrix, 0, col)
    nb_rows, _ = size(matrix)
    for row in range(nb_rows):
        set_cell(matrix, row, col, val)


def replace_col(matrix, col, col_values):
    """
    replace column col with col_values

    :param matrix: the matrix to modify
    :type matrix: matrix
    :param col: the column number to replace
    :type col: int
    :param col_values: the list of values to use as replacement of column
    :type col_values: list
    """
    nb_rows, nb_cols = size(matrix)
    if len(col_values) != nb_rows:
        raise RuntimeError(
            f"the size of col_values {len(col_values)} does not fit "
            f"matrix size {nb_rows} x {nb_cols}")
    _check_bounds(matrix, 0, col)
    for row in range(nb_rows):
        set_cell(matrix, col, row, col_values[row])


def replace_row(matrix, row, row_values):
    """
    replace row row with row_values

    :param matrix: the matrix to modify
    :type matrix: matrix
    :param row: the column number
    :type row: int
    :param row: the list of value to use as replacement of row
    :type row: list
    """
    nb_rows, nb_cols = size(matrix)
    if len(row_values) != nb_cols:
        raise RuntimeError(
            f"the size of row_values {len(row_values)} does not fit "
            f"matrix size {nb_rows} x {nb_cols}")
    _check_bounds(matrix, row, 0)
    for col in range(nb_cols):
        set_cell(matrix, col, row, row_values[col])


def to_str(matrix):
    """
    :param matrix: the matrix to represent as string
    :type matrix: matrix
    :return: a string representation of the matrix
    :rtype: str
    """
    lines = []
    nb_rows, nb_cols = size(matrix)
    for row in range(nb_rows):
        lines.append("\t".join([
            str(val) for val in get_row(matrix, row)]))
    return "\n".join(lines)


if __name__ == '__main__':
    m = create(5, 3)
    print(m)
    print(to_str(m))
    set_cell(m, 0, 0, 1)
    print(m)
    print(to_str(m))
    set_cell(m, 0, 2, 2)
    print(m)
    print(to_str(m))
    set_cell(m, 4, 0, 12)
    print(m)
    print(to_str(m))
    set_cell(m, 4, 2, 15)
    print(m)
    print(to_str(m))
    print("get row 0",  get_row(m, 0))
    print("get col 0", get_col(m, 0))

    m2 = create(3, 2, 4)
    mult_inplace(m2, 2)
    print(to_str(m2))

matrix3.py .