Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
4 views

python-control_flow-iterations-functions

The document provides an overview of Python programming concepts including control flow, iterations, functions, and classes. It covers conditional statements, loops, function definitions, and the use of classes and objects, along with advanced topics like list comprehensions, iterators, and error handling. Additionally, it introduces generators and specialized iterators from the itertools module, highlighting their applications in Python programming.

Uploaded by

muffmufferson
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

python-control_flow-iterations-functions

The document provides an overview of Python programming concepts including control flow, iterations, functions, and classes. It covers conditional statements, loops, function definitions, and the use of classes and objects, along with advanced topics like list comprehensions, iterators, and error handling. Additionally, it introduces generators and specialized iterators from the itertools module, highlighting their applications in Python programming.

Uploaded by

muffmufferson
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

python-control_flow-iterations-functions

October 18, 2019

1 Python for Data Science

Control Flow, Iterations, Functions, Classes, Errors, …

2 Control Flow

• Without control flow, programs are sequences of statements


• With control flow you execute code
• conditionally (if, else)
• repeatedly (for, while)

2.1 Conditional Statements: if-elif-else:

[26]: x = inf

if x == 0:
print(x, "is zero")
elif x > 0:
print(x, "is positive")
elif x < 0:
print(x, "is negative")
else:
print(x, "is unlike anything I've ever seen...")


,→---------------------------------------------------------------------------

NameError Traceback (most recent call␣


,→ last)

<ipython-input-26-87def6aed850> in <module>
----> 1 x = inf
2
3 if x == 0:

1
4 print(x, "is zero")
5 elif x > 0:

NameError: name 'inf' is not defined

2.2 for loops

• Iterate over each element of a collection


• Python makes this look like almost natural language:

for [each] value in [the] list


[ ]: for N in [2, 3, 5, 7]:
print(N, end=' ') # print all on same line

[ ]: for N in range(5):
print(N, end=' ') # print all on same line

2.3 while loops

Iterate until a condition is met


[ ]: i = 0
while i < 10:
print(i, end=' ')
i += 1

3 Functions

Remember the print statement


print('abc')
print is a function and 'abc' is an argument.
[27]: # multiple input arguments
print('abc','d','e','f','g')

abc d e f g

[28]: # keyword arguments


print('abc','d','e','f','g', sep='--')

2
abc--d--e--f--g

3.1 Defining Functions

[29]: def add(a, b):


"""
This function adds two numbers

Input
a: a number
b: another number

Returns sum of a and b


"""
result = a + b
return result

[30]: add(1,1)

[30]: 2

[31]: def add_and_print(a, b, print_result):


"""
This function adds two numbers

Input
a: a number
b: another number
print_result: boolean, set to true if you'd like the result printed

Returns sum of a and b


"""
result = a + b
if print_result:
print("Your result is {}".format(result))
return result

[32]: add_and_print(1, 1, True)

Your result is 2

[32]: 2

3
3.2 Default Arguments

[33]: def add_and_print(a, b, print_result=True):


"""
This function adds two numbers

Input
a: a number
b: another number
print_result: boolean, set to true if you'd like the result printed

Returns sum of a and b


"""
result = a + b
if print_result:
print("Your result is {}".format(result))
return result

[34]: add_and_print(1, 1)

Your result is 2

[34]: 2

3.3 *args and **kwargs: Flexible Arguments

[35]: def add_and_print(*args, **kwargs):


"""
This function adds two numbers

Input
a: a number
b: another number
print_result: boolean, set to true if you'd like the result printed

Returns sum of a and b


"""
result = 0
for number in args:
result += number
if 'print_result' in kwargs.keys() and kwargs['print_result']:
print("Your result is {}".format(result))
return result

[36]: add_and_print(1, 1, 1, print_result=True, unknown_argument='ignored')

4
Your result is 3

[36]: 3

[37]: list_of_numbers = [1,2,3,42-6]


add_and_print(*list_of_numbers)

[37]: 42

3.4 Anonymous (lambda) Functions

[38]: add = lambda x, y: x + y


add(1, 2)

[38]: 3

4 Classes

• Python is an object oriented language


• Classes provide a means of bundling data and functionality together
• Classes allow for inheriting functionality
[39]: class Person:

def __init__(self, name, age):


self.name = name
self.age = age

def is_adult(self):
return self.age > 18

[40]: p1 = Person("John", 36)

print(p1.name)
print(p1.age)
print(p1.is_adult())

John
36
True

[41]: class Student(Person):


"""A class inheriting fields and methods from class Person"""

p2 = Student("Peter", 20)

5
p2.is_adult()

[41]: True

4.1 Some Convenient Special Functions

• Printing a String representation of an object: __repr__


• For calling an object: __call__
• Many more for specialized objects like iterables (just create an object and type .__ + <TAB>)

4.1.1 Nice String Representations of Objects with __repr__

[42]: # the string representation of the Person class is not very informative
p1

[42]: <__main__.Person at 0x10ed44210>

[43]: # defining a __repr__ function that returns a string can help


class PrintableStudent(Student):
def __repr__(self):
return f"A student with name {self.name} and age {self.age}"

p3 = PrintableStudent("Michael Mustermann", 25)


p3

[43]: A student with name Michael Mustermann and age 25

4.1.2 Clean APIs using __call__ for obvious usages of Objects

[44]: # defining a __call__ function can help to keep APIs simple


class CallableStudent(PrintableStudent):
def __call__(self, other_student):
print(f"{self.name} calls {other_student.name}")

p4 = CallableStudent("Michael Mustermann", 25)


p4(p2)

Michael Mustermann calls Peter

5 List Comprehensions

A simple way of compressing a list building for loop into single statement

6
[45]: L = []
for n in range(12):
L.append(n ** 2)
L

[45]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

[46]: [n ** 2 for n in range(12)]

[46]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

5.1 Conditional List Comprehensions

Including an if statement in list comprehensions


[47]: [n ** 2 for n in range(12) if n % 3 == 0]

[47]: [0, 9, 36, 81]

6 Set Comprehensions

Same as for lists, but for sets


[48]: {n**2 for n in range(12)}

[48]: {0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121}

7 Dict Comprehensions

Same as for lists, but for dictionaries


[49]: {n:n**2 for n in range(12)}

[49]: {0: 0,
1: 1,
2: 4,
3: 9,
4: 16,
5: 25,
6: 36,
7: 49,
8: 64,
9: 81,

7
10: 100,
11: 121}

8 Generator Comprehensions

Generators generate values one by one. More on this later.


[50]: (n**2 for n in range(12))

[50]: <generator object <genexpr> at 0x10e25be50>

[51]: # generators can be turned into lists


list((n**2 for n in range(12)))

[51]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

9 Iterators

• An object over which Python can iterate are called Iterators


• Iterators
– have a __next__ method that returns the next element
– have an __iter__ method that returns self
• The builtin function iter turns any iterable in an iterator
[52]: my_iterator = iter([1,2])
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))

1
2


---------------------------------------------------------------------------
,→

StopIteration Traceback (most recent call␣


last)
,→

<ipython-input-52-bdcbfa3d0082> in <module>
2 print(next(my_iterator))
3 print(next(my_iterator))
----> 4 print(next(my_iterator))

8
StopIteration:

9.1 Custom Iterators

[53]: class Squares(object):

def __init__(self, start, stop):


self.start = start
self.stop = stop

def __iter__(self): return self

def __next__(self):
if self.start >= self.stop:
raise StopIteration
current = self.start * self.start
self.start += 1
return current

iterator = Squares(1, 5)
[i for i in iterator]

[53]: [1, 4, 9, 16]

9.2 Useful Builtin Iterators

9.2.1 enumerate

Often you need not only the elements of a collection but also their index
[54]: L = [2, 4, 6]
for i in range(len(L)):
print(i, L[i])

0 2
1 4
2 6
Instead you can write
[55]: L = [2, 4, 6]
for idx, element in enumerate(L):
print(idx, element)

9
0 2
1 4
2 6

9.2.2 zip

Zips together two iterators


[56]: L = [2, 4, 6, 8, 10]
R = [3, 5, 7, 9, 11]
for l, r in zip(L, R):
print(l, r)

2 3
4 5
6 7
8 9
10 11

9.2.3 Unzipping with zip

An iterable of tuples can be unzipped with zip, too:


[57]: zipped = [('a',1), ('b',2)]
letters, numbers = zip(*zipped)
print(letters)
print(numbers)

('a', 'b')
(1, 2)

9.2.4 map

Applies a function to a collection


[58]: def power_of(x, y=2):
return x**2

for n in map(power_of, range(5)):


print(n)

0
1
4
9
16

10
9.2.5 filter

Filters elements from a collection


[59]: def is_even(x):
return x % 2 == 0

for n in filter(is_even, map(power_of, range(5))):


print(n)

0
4
16

[60]: # compressing the above for loop


print(*filter(is_even, map(power_of, range(5))))

0 4 16

9.3 Specialized Iterators: itertools

9.3.1 Permutations

Iterating over all permutations of a list


[61]: from itertools import permutations
my_iterator = range(3)
p = permutations(my_iterator)
print(*p)

(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)

9.3.2 Combinations

Iterating over all unique combinations of N values within a list


[62]: from itertools import combinations
c = combinations(range(4), 2)
print(*c)

(0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3)

9.3.3 Product

Iterating over all combinations of elements in two or more iterables

11
[63]: from itertools import product
my_iterator = range(3)
another_iterator = iter(['a', 'b'])
yet_another_iterator = iter([True, False])
p = product(my_iterator, another_iterator, yet_another_iterator)
print(*p)

(0, 'a', True) (0, 'a', False) (0, 'b', True) (0, 'b', False) (1, 'a', True) (1,
'a', False) (1, 'b', True) (1, 'b', False) (2, 'a', True) (2, 'a', False) (2,
'b', True) (2, 'b', False)

9.3.4 Chaining

Use Case: Chaining multiple iterators allows to combine file iterators


[64]: from itertools import chain
my_iterator = range(3)
another_iterator = iter(['a', 'b'])
yet_another_iterator = iter([True, False])
p = chain(my_iterator, another_iterator, yet_another_iterator)
print(*p)

0 1 2 a b True False

9.3.5 Chaining for Flattening

Turning a nested collection like [['a','b'],'c'] into a flat one like ['a','b','c'] is called
flattening
[65]: from itertools import chain
my_nested_list = [['a','b'],'c']
p = chain(*my_nested_list)
print(*p)

a b c

10 Generators - A Special Kind of Iterator

Generators make creation of iterators simpler.


Generators are built by calling a function that has one or more yield expression
[66]: def squares(start, stop):
for i in range(start, stop):
yield i * i

12
generator = squares(1, 10)
[i for i in generator]

[66]: [1, 4, 9, 16, 25, 36, 49, 64, 81]

11 When to use Iterators vs Generators

• Every Generator is an Iterator - but not vice versa


• Generator implementations can be simpler: python generator = (i*i for i in
range(a, b))
• Iterators can have rich state

12 Errors

Bugs come in three basic flavours:


• Syntax errors:
– Code is not valid Python (easy to fix, except for some whitespace things)
• Runtime errors:
– Syntactically valid code fails, often because variables contain wrong values
• Semantic errors:
– Errors in logic: code executes without a problem, but the result is wrong (difficult to
fix)

12.1 Runtime Errors

12.1.1 Trying to access undefined variables

[67]: # Q was never defined


print(Q)


,→---------------------------------------------------------------------------

NameError Traceback (most recent call␣


last)
,→

<ipython-input-67-7ba079dc2063> in <module>
1 # Q was never defined
----> 2 print(Q)

13
NameError: name 'Q' is not defined

12.1.2 Trying to execute unsupported operations

[76]: 1 + 'abc'


,→---------------------------------------------------------------------------

TypeError Traceback (most recent call␣


last)
,→

<ipython-input-76-a51a3635a212> in <module>
----> 1 1 + 'abc'

TypeError: unsupported operand type(s) for +: 'int' and 'str'

12.1.3 Trying to access elements in collections that don’t exist

[ ]: L = [1, 2, 3]
L[1000]

12.1.4 Trying to compute a mathematically ill-defined result

[ ]: 2 / 0

12.2 Catching Exceptions: try and except

[77]: try:
print("this gets executed first")
except:
print("this gets executed only if there is an error")

this gets executed first

[78]: try:
print("let's try something:")
x = 1 / 0 # ZeroDivisionError
except:

14
print("something bad happened!")

let's try something:


something bad happened!

[79]: def safe_divide(a, b):


"""
A function that does a division and returns a half-sensible
value even for mathematically ill-defined results
"""
try:
return a / b
except:
return 1E100

[80]: print(safe_divide(1, 2))


print(safe_divide(1, 0))

0.5
1e+100

12.2.1 What about errors that we didn’t expect?

[81]: safe_divide (1, '2')

[81]: 1e+100

12.2.2 It’s good practice to always catch errors explicitly:

All other errors will be raised as if there were no try/except clause.

[82]: def safe_divide(a, b):


try:
return a / b
except ZeroDivisionError:
return 1E100

[83]: safe_divide(1, '2')


,→---------------------------------------------------------------------------

TypeError Traceback (most recent call␣


,→last)

15
<ipython-input-83-cbb3eb91a66d> in <module>
----> 1 safe_divide(1, '2')

<ipython-input-82-57f0d324952e> in safe_divide(a, b)
1 def safe_divide(a, b):
2 try:
----> 3 return a / b
4 except ZeroDivisionError:
5 return 1E100

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

12.3 Throwing Errors

• When your code is executed, make sure that it’s clear what went wrong in case of errors.
• Throw specific errors built into Python
• Write your own error classes
[84]: raise RuntimeError("my error message")


---------------------------------------------------------------------------
,→

RuntimeError Traceback (most recent call␣


last)
,→

<ipython-input-84-b1834d213d3b> in <module>
----> 1 raise RuntimeError("my error message")

RuntimeError: my error message

12.4 Specific Errors

[ ]: def safe_divide(a, b):


if (not issubclass(type(a), float)) or (not issubclass(type(b), float)):
raise ValueError("Arguments must be floats")
try:
return a / b
except ZeroDivisionError:
return 1E100

16
[ ]: safe_divide(1, '2')

12.5 Accessing Error Details

[85]: import warnings

def safe_divide(a, b):


if (not issubclass(type(a), float)) or (not issubclass(type(b), float)):
raise ValueError("Arguments must be floats")
try:
return a / b
except ZeroDivisionError as err:
warnings.warn("Caught Error {} with message {}".format(type(err),err) +
" - will just return a large number instead")
return 1E100

[86]: safe_divide(1., 0.)

/Users/felix/anaconda3/envs/pdds_1920/lib/python3.7/site-
packages/ipykernel_launcher.py:10: UserWarning: Caught Error <class
'ZeroDivisionError'> with message float division by zero - will just return a
large number instead
# Remove the CWD from sys.path while we load stuff.

[86]: 1e+100

13 Loading Modules: the import Statement

• Explicit imports (best)


• Explicit imports with alias (ok for long package names)
• Explicit import of module contents
• Implicit imports (to be avoided)

13.1 Creating Modules

• Create a file called [somefilename].py


• In a (i)python shell change dir to that containing dir
• type
import [somefilename]
Now all classes, functions and variables in the top level namespace are available.

17
Let’s assume we have a file mymodule.py in the current working directory with the content:
mystring = 'hello world'

def myfunc():
print(mystring)

[87]: import mymodule


mymodule.mystring

[87]: 'hello world'

[88]: mymodule.myfunc()

hello world

13.2 Explicit module import

Explicit import of a module preserves the module’s content in a namespace.


[89]: import math
math.cos(math.pi)

[89]: -1.0

13.3 Explicit module import with aliases

For longer module names, it’s not convenient to use the full module name.
[90]: import numpy as np
np.cos(np.pi)

[90]: -1.0

13.4 Explicit import of module contents

You can import specific elements separately.


[91]: from math import cos, pi
cos(pi)

[91]: -1.0

18
13.5 Implicit import of module contents

You can import all elements of a module into the global namespace. Use with caution.
[92]: cos = 0
from math import *
sin(pi) ** 2 + cos(pi) ** 2

[92]: 1.0

14 File IO and Encoding

• Files are opened with open


• By default in 'r' mode, reading text mode, line-by-line

14.1 Reading Text

[93]: path = 'umlauts.txt'


f = open(path)
lines = [x.strip() for x in f]
f.close()
lines

[93]: ['Eichhörnchen', 'Flußpferd', '', 'Löwe', '', 'Eichelhäher']

[94]: # for easier cleanup


with open(path) as f:
lines = [x.rstrip() for x in f]
lines

[94]: ['Eichhörnchen', 'Flußpferd', '', 'Löwe', '', 'Eichelhäher']

14.2 Detour: Context Managers

Often, like when opening files, you want to make sure that the file handle gets closed in any case.
file = open(path, 'w')
try:
# an error
1 / 0
finally:
file.close()
Context managers are a convenient shortcut:

19
with open(path, 'w') as opened_file:
# an error
1/0

14.3 Writing Text

[95]: with open('tmp.txt', 'w') as handle:


handle.writelines(x for x in open(path) if len(x) > 1)
[x.rstrip() for x in open('tmp.txt')]

[95]: ['Eichhörnchen', 'Flußpferd', 'Löwe', 'Eichelhäher']

14.4 Reading Bytes

[96]: # remember 't' was for text reading/writing


with open(path, 'rt') as f:
# just the first 6 characters
chars = f.read(6)
chars

[96]: 'Eichhö'

[97]: # now we read the file content as bytes


with open(path, 'rb') as f:
# just the first 6 bytes
data = f.read(6)

[98]: # byte representation


data.decode('utf8')


,→---------------------------------------------------------------------------

UnicodeDecodeError Traceback (most recent call␣


last)
,→

<ipython-input-98-9981ac9de387> in <module>
1 # byte representation
----> 2 data.decode('utf8')

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 5:␣


unexpected end of data
,→

20
[99]: # decoding error, utf-8 has variable length character encodings
data[:4].decode('utf8')

[99]: 'Eich'

21

You might also like