Slides15 Python2
Slides15 Python2
1 More on python
Python has many high-level builtin features, time to learn some more!
Functions can be defined using a lambda expression or via def. Python provides for functions both
positional and keyword-based arguments.
[1]: square = lambda x: x * x
[2]: square(10)
[2]: 100
coeff = .5 / a
Functions can have positional arguments and keyword based arguments. Positional arguments have
to be declared before keyword args
1
[7]: # name is a positional argument, message a keyword argument
def greet(name, message='Hello {}, how are you today?'):
print(message.format(name))
[8]: greet('Tux')
Hi Tux!
What's up Tux?
[13]: log(math.e)
[13]: 1.0
[14]: log(10)
[14]: 2.302585092994046
[15]: 2.9999999999999996
2
1.2 3.03 builtin functions, attributes
Python provides a rich standard library with many builtin functions. Also, bools/ints/floats/strings
have many builtin methods allowing for concise code.
One of the most useful builtin function is help. Call it on any object to get more information,
what methods it supports.
[16]: s = 'This is a test string!'
print(s.lower())
print(s.upper())
print(s.startswith('This'))
print('string' in s)
print(s.isalnum())
[17]: (1, 2, 3, 4)
Dictionaries (or associate arrays) provide a structure to lookup values based on keys. I.e. they’re
a collection of k->v pairs.
[20]: list(zip(['brand', 'model', 'year'], ['Ford', 'Mustang', 1964])) # creates a␣
,→list of tuples by "zipping" two list
3
[21]: # convert a list of tuples to a dictionary
D = dict(zip(['brand', 'model', 'year'], ['Ford', 'Mustang', 1964]))
D
[22]: D['brand']
[22]: 'Ford'
[23]: 'Mustang'
Dictionaries can be also directly defined using { ... : ..., ...} syntax
[24]: D = {'brand' : 'Ford', 'model' : 'Mustang', 'year' : 1964}
[25]: D
[28]: D
[30]: D
[31]: True
4
[32]: dict_keys(['brand', 'model', 'price'])
[34]: D
brand
model
price
Ford
Mustang
48k
brand: Ford
model: Mustang
price: 48k
Python provides two special operators * and ** to call functions with arguments specified through
a tuple or dictionary. I.e. * unpacks a tuple into positional args, whereas ** unpacks a dictionary
into keyword arguments.
[38]: quadratic_root(1, 5.5, -10.5)
5
[40]: args=('Tux',) # to create a tuple with one element, need to append , !
kwargs={'message' : 'Hi {}!'}
greet(*args, **kwargs)
Hi Tux!
python has builtin support for sets (i.e. an unordered list without duplicates). Sets can be defined
using {...}.
[41]: set
[42]: S = {1, 2, 3, 1, 4}
S
[42]: {1, 2, 3, 4}
[43]: 2 in S
[43]: True
list(set(L))
6
[47]: {1, 5, 3, 4} & {2, 3}
[47]: {3}
[48]: {3}
Instead of creating list, dictionaries or sets via explicit extensional declaration, you can use a
comprehension expression. This is especially useful for conversions.
[49]: # list comprehension
L = ['apple', 'pear', 'banana', 'cherry']
[(1, x) for x in L]
The same works also for sets AND dictionaries. The collection to iterate over doesn’t need to be
of the same type.
[52]: L = ['apple', 'pear', 'banana', 'cherry']
7
[54]: {random.randint(0, 10) for _ in range(20)}
[58]: (3, 4, 5)
A more complicated function can be created to create functions to evaluate a polynomial defined
through a vectpr p = (p1 , ..., pn )T
∑
n
f (x) = p i xi
i=1
8
return f
[61]: poly(2)
[61]: 1
[62]: 4
Basic idea is that when declaring nested functions, the inner ones have access to the enclosing
functions scope. When returning them, a closure is created.
We can use this to change the behavior of functions by wrapping them with another!
==> we basically decorate the function with another, thus the name decorator
[63]: def greet(name):
return 'Hello {}!'.format(name)
[64]: greet('Tux')
[67]: state_an_important_fact().upper()
9
[69]: GREET = make_upper(greet)
STATE_AN_IMPORTANT_FACT = make_upper(state_an_important_fact)
[70]: GREET('tux')
[71]: STATE_AN_IMPORTANT_FACT()
Instead of explicitly having to create the decoration via make_upper, we can also use python’s
builtin support for this via the @ statement. I.e.
[72]: @make_upper
def say_hi(name):
return 'Hi ' + name + '.'
[73]: say_hi('sealion')
[75]: @split
@make_upper
def state_an_important_fact():
return 'The one and only answer to ... is 42!'
[76]: state_an_important_fact()
[76]: ['THE', 'ONE', 'AND', 'ONLY', 'ANSWER', 'TO', '…', 'IS', '42!']
10
1.8 6.01 Generators
Assume we want to generate all square numbers. We could do so using a list comprehension:
[77]: [x * x for x in range(10)]
However, this will create a list of all numbers. Sometimes, we just want to consume the number.
I.e. we could do this via a function
[78]: square = lambda x: x * x
[79]: print(square(1))
print(square(2))
print(square(3))
1
4
9
However, what about a more complicated sequence? I.e. fibonnaci numbers?
[80]: def fib(n):
if n <= 0:
return 0
if n <= 1:
return 1
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
[81]: n = 10
for i in range(n):
print(fib(i))
0
1
1
2
3
5
8
13
21
34
Complexity is n^2! However, with generators we can stop execution.
11
The pattern is to call basically generator.next()
[83]: fib()
[84]: g = fib()
for i in range(5):
print(next(g))
0
1
1
2
3
enumerate and zip are both generator objects!
[85]: L = ['a', 'b', 'c', 'd']
[86]: g = enumerate(L)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
# stop iteration exception will be done
print(next(g))
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
␣
---------------------------------------------------------------------------
,→
<ipython-input-86-a98f42ccb1c3> in <module>
5 print(next(g))
12
6 # stop iteration exception will be done
----> 7 print(next(g))
StopIteration:
[ ]: g = range(3)
g
g = zip(L, i)
for el in g:
print(el)
('a', 3)
('b', 2)
('c', 1)
('d', 0)
Note: There is no hasNext in python. Use a loop with in to iterate over the full generator.
[88]: for i, n in enumerate(fib()):
if i > 10:
break
print(n)
0
1
1
2
3
5
8
13
21
34
55
python provides two builitn higher order functions: map and filter. A higher order function is a
function which takes another function as argument or returns a function (=> decorators).
In python3, map and filter yield a generator object.
13
[89]: map(lambda x: x * x, range(7))
0
1
4
9
16
25
36
f.write('Hello world')
f.close()
Because a file needs to be closed (i.e. the file object destructed), python has a handy statement to
deal with auto-closing/destruction: The with statement.
['Hello world']
Again, help is useful to understand what methods a file object has
[94]: # uncomment here to get the full help
# help(f)
14
2 7.01 classes
def print(self):
print('{} ({} kg)'.format(self.name, self.weight))
def __str__(self):
return '{} ({} kg)'.format(self.name, self.weight)
[97]: dog
[98]: print(dog)
[99]: dog.print()
def __init__(self):
Animal.__init__(self, 'elephant', 1500)
#alternative:
# super().__init__(...)
[101]: e = Elephant()
[102]: print(e)
15
3 8.01 Modules and packages
[104]: !ls
01_Intro_to_Python.ipynb LICENSE
01_Python_Introduction.ipynb README.md
02_More_Python.ipynb __pycache__
02_More_Python_empty.ipynb file.txt
coeff = .5 / a
Writing solver.py
coeff = .5 / a
16
Alternative is to import the name quadratic_root directly into the current scope
[109]: from solver import quadratic_root
To import everything, you can use from ... import *. To import multiple specific functions, use
from ... import a, b.
E.g. from flask import render_template, request, abort, jsonify, make_response.
To organize modules in submodules, subsubmodules, … you can use folders. I.e. to import a
function from a submodule, use from solver.algebraic import quadratic_root.
There’s a special file __init__.py that is added at each level, which gets executed when import
folder is run.
[111]: !rm *.py
Writing solver/__init__.py
Writing solver/algebraic/__init__.py
print('solver.algebraic.quadratic executed!')
coeff = .5 / a
Writing solver/algebraic/quadratic.py
17
[116]: %%file test.py
import solver
Writing test.py
import solver.algebraic
Overwriting test.py
import solver.algebraic.quadratic
Overwriting test.py
import solver.algebraic.quadratic
Overwriting test.py
print(quadratic_root(1, 1, -2))
Overwriting test.py
18
import solver executed!
import solver.algebraic executed!
solver.algebraic.quadratic executed!
(-2.0, 1.0)
One can also use relative imports to import from other files via . or ..!
[125]: %%file solver/version.py
__version__ = "1.0"
Writing solver/version.py
solver
��� __init__.py
��� __pycache__
� ��� __init__.cpython-37.pyc
��� algebraic
� ��� __init__.py
� ��� __pycache__
� � ��� __init__.cpython-37.pyc
� � ��� quadratic.cpython-37.pyc
� ��� quadratic.py
��� version.py
3 directories, 7 files
coeff = .5 / a
Overwriting solver/algebraic/quadratic.py
19
package version is 1.0
(-2.0, 1.0)
This can be also used to bring certain functions into scope!
[129]: %%file solver/algebraic/__init__.py
Overwriting solver/algebraic/__init__.py
print(quadratic_root(1, 1, -2))
Overwriting test.py
20