Python course

Hannes Ovrén

Computer Vision Laboratory
Linköping University

hannes.ovren@liu.se

What is Python?

Wikipedia has this to say:

Python is a widely used general-purpose, high-level programming language. Its design philosophy emphasizes code readability, and its syntax allows programmers to express concepts in fewer lines of code than would be possible in languages such as C++ or Java. The language provides constructs intended to enable clear programs on both a small and large scale.

Python History

  • Roots in the late 80's (Guido van Rossum)
  • 1994: Version 1.0
  • 2000: Version 2.0
  • 2008: Version 3.0
  • 2010: Version 2.7 (last 2.x release)
  • 2015: Version 3.5 (current stable)

We will use 3.4+

The language at a glance

  • Multiple paradigms
    • Object orientated
    • Functional
    • Procedural
    • ...
  • Dynamic typing
  • Automatic memory management
  • Large standard library

Python as a script

  • Python 3.x: $ python3 myscript.py
  • Python 2.x: $ python myscript.py
    or $ python2 myscript.py
  • python should point to 2.x (but might not)

Hashbang

Add one of the following to the top of the script

  • #!/usr/bin/env python3
  • #!/usr/bin/python3

Run as

$ ./myscript.py

Python REPL

  • Read-Eval-Print-Loop
  • Interactive work
  • Direct Interpreter ($ python3)
  • IPython
    • history
    • tab-complete
    • ...
  • Jupyter/IPython notebooks

Hello World!

In [12]:
print('Hello World!')
Hello World!

Small example

In [13]:
for i in range(5):
    if i % 2 == 0:
        print(i, 'is even')
    else:
        print(i, 'is odd')
0 is even
1 is odd
2 is even
3 is odd
4 is even

Whitespace controls scope!

for i in range(5):
    if i % 2 == 0:
        print(i, 'is even')
    else:
        print(i, 'is odd')
  • Always use 4 spaces for indentation (check your editor settings)
  • Mixing tabs and spaces can lead to problems!

Data types

Most simple data types are built in

  • integers
  • floats
  • strings (unicode)
  • booleans (True and False)
In [14]:
x = 1
y = 2.0
s = "Strings are unicode, so we can write in 日本語"
b = True

z = int(y)
z, type(z)
Out[14]:
(2, int)

Operators

  • Like usual: +, -, *, %, /
  • Power: **
In [15]:
5 ** 2
Out[15]:
25

Logical operators

  • and, or, not
In [16]:
5 > 3 and not 5 > 7
Out[16]:
True
  • Bitwise operators exist as well

Range checks

In [17]:
x = 5
3 < x < 7
Out[17]:
True

Container types

  • list
  • tuple
  • dict
  • set

list

  • Random access (0-indexed!)
  • Negative numbers count backwards
In [18]:
a = [1, 2, 3, 4]
print(a[0], a[3], a[-1])
1 4 4
  • Items can be added or removed
In [19]:
a.append(100)
a.remove(2)
print(a)
[1, 3, 4, 100]
  • Mix item types freely!
In [20]:
b = [1, 2.0, 'banana', [30, 40]]
  • Check if item is in the list using in
In [21]:
'banana' in b
Out[21]:
True

tuple

  • A "frozen" list: No append, remove or altering values
  • Hashable (unlike list)
In [22]:
a = [1, 2, 3, 4]
t = (1, 2, 3, 4) # tuple
a[2] = 88 # OK

t[2] = 88
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-22-ba2b2f1f2cb7> in <module>()
      3 a[2] = 88 # OK
      4 
----> 5 t[2] = 88

TypeError: 'tuple' object does not support item assignment

Iterating over things (naive way)

By using

  • len(seq) - number of items in seq
  • range(N) - generate sequence of integers in half-open interval [0, N)
In [23]:
fruits = ['apple', 'banana', 'kiwi']
for i in range(len(fruits)):
    print(fruits[i])
apple
banana
kiwi

Please avoid this!

Iterating over things (better way!)

We use for item in sequence to grab items from sequences like list or tuple

In [24]:
fruits = ['apple', 'banana', 'kiwi']
for fruit in fruits:
    print(fruit)
apple
banana
kiwi

Slicing

seq[a:b] gives the part of the sequence in the half-open range [a, b)

In [25]:
fruits = ['oranges', 'apple', 'banana', 'kiwi', 'raspberry']
In [26]:
fruits[:2]
Out[26]:
['oranges', 'apple']
In [27]:
fruits[2:]
Out[27]:
['banana', 'kiwi', 'raspberry']
In [28]:
fruits[-3:]
Out[28]:
['banana', 'kiwi', 'raspberry']

We can also specify the step length (which can be negative!)

In [29]:
fruits[1::2]
Out[29]:
['apple', 'kiwi']
In [30]:
fruits[-1::-2]
Out[30]:
['raspberry', 'banana', 'oranges']

Sequence unpacking

Any sequence can be unpacked into separate variables

In [31]:
person = ('Hannes', 31, 'CVL')
name, age, workplace = person
print(name, 'is', age, 'years old and works at', workplace)
Hannes is 31 years old and works at CVL

We can also just get some of the values

In [32]:
name, _, workplace = person
print(name, workplace)
Hannes CVL
In [33]:
name, *rest = person
print(name, rest)
Hannes [31, 'CVL']

Unpacking in loops

In [34]:
eating_habits = [('monkey', 'bananas'), 
                 ('horse', 'grass'), 
                 ('human', 'hamburgers')]

for animal, food in eating_habits:
    print('The', animal, 'eats', food)
The monkey eats bananas
The horse eats grass
The human eats hamburgers

This is equivalent to the less pretty

In [35]:
for item in eating_habits:
    print('The', item[0], 'eats', item[1])
The monkey eats bananas
The horse eats grass
The human eats hamburgers

set

  • Accepts mixtures of any kind of hashable object
  • Is unordered
In [36]:
things = {5, 2, 1, 1, 1, 1, 'monkey', (1, 3)}
print(things)
{1, 2, (1, 3), 5, 'monkey'}
  • Check for set membership using in
In [37]:
5 in things
Out[37]:
True

We can add and remove things from sets

In [38]:
things.add(7)
things.remove('monkey')
things
Out[38]:
{1, 2, (1, 3), 5, 7}
  • Set operations like intersection, union, etc. all exist
In [39]:
A = {1, 2, 3, 4, 5}
B = {1, 4, 10, 20}
print('Intersection:', A.intersection(B))
print('Union:', A.union(B))
print('Difference:', A - B)
Intersection: {1, 4}
Union: {1, 2, 3, 4, 5, 10, 20}
Difference: {2, 3, 5}

Only hashable types work. E.g. not list objects

In [40]:
A.add([1, 2])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-4cd20301fb85> in <module>()
----> 1 A.add([1, 2])

TypeError: unhashable type: 'list'

Key-value map: dict

  • Access items by key
  • Key can be any hashable object (i.e. tuple is ok, but not list!)
  • Keys in the same dict can have different types
In [41]:
country_area = {
    'Sweden' : 449964,
    'Italy' :  301338, }
print('The area of Sweden is', country_area['Sweden'], 'km²')
The area of Sweden is 449964 km²

New values/keys can be inserted after creation

In [42]:
country_area['Germany'] = 357168
print(country_area)
{'Italy': 301338, 'Germany': 357168, 'Sweden': 449964}
  • Extract keys and values using methods dict.keys() and dict.values()
  • Note that dict is an unordered container
In [43]:
print(country_area.keys())
print(country_area.values())
dict_keys(['Italy', 'Germany', 'Sweden'])
dict_values([301338, 357168, 449964])

Check for key existance with in

In [44]:
'Sweden' in country_area
Out[44]:
True

Get key-value pairs using dict.items()

In [45]:
for country, area in country_area.items():
    print('The area of', country, 'is', area, 'km²')
The area of Italy is 301338 km²
The area of Germany is 357168 km²
The area of Sweden is 449964 km²

dict as ad-hoc "classes" for structured data

In [46]:
movies = [
    {'name' : 'Star Wars',
     'year' : 1977,
     'actors' : ['Mark Hamill', 'Harrison Ford']},
    
    {'name' : 'Alien',
     'year' : 1979,
     'actors' : ['Sigourney Weaver',]}
]

for movie in movies:
    print(movie['name'], 'was released in', movie['year'])
Star Wars was released in 1977
Alien was released in 1979

Functions

In [47]:
def square(x):
    return x ** 2

square(4)
Out[47]:
16

Functions are like any object, and can be used as input to other functions

In [48]:
def apply_func(func, x):
    return func(x)

f = square
apply_func(f, 3)
Out[48]:
9

A function can return multiple values

In [49]:
def square_and_cube(x):
    return x ** 2, x ** 3

square, cube = square_and_cube(4)
print(square, cube)
16 64

This is just creating and unpacking a tuple!

In [50]:
y = square_and_cube(5)
print(y, type(y))
(25, 125) <class 'tuple'>

Function arguments

  • keyword arguments are optional arguments with a default value
In [51]:
def greet(name, greeting='Hello'):
    print(greeting, name)

greet('Hannes')
greet('Hannes', greeting='Hi')
greet('Hannes', 'Hi')
Hello Hannes
Hi Hannes
Hi Hannes

Variable number of arguments

  • For variable number of positional arguments
  • args will be a tuple of values
In [52]:
def add(*args):
    result = 0
    for x in args: # Not *args!
        result += x
    return result

add(1, 2, 5, 9)
Out[52]:
17
  • Variable number of keyword arguments is also supported
  • kwargs will be a dictionary
In [53]:
def print_thing_length(**kwargs):
    for name, length in kwargs.items(): # Not **kwargs
        print(name, 'is', length, 'tall')

print_thing_length(hannes='182 cm', smurf='two apples')
smurf is two apples tall
hannes is 182 cm tall

When is this useful?

Variable number of arguments (cont.)

  • Combining works as expected
  • Ordering of positional, keyword args, and their variable versions is important
In [54]:
def sum_all_the_things(a, b,  *args, foo=5, bar=10, **kwargs):
    result = a + b + foo + bar
    for x in args:
        result += x
    for x in kwargs.values():
        result += x
    return result

sum_all_the_things(1, 0, 0, 0, 0, 0, monkey=100)
Out[54]:
116

String formatting using %

  • printf-like format specifiers:
    • %s
    • %d
    • %f
    • ...
In [55]:
'%s (%d) has an IMDB score of %.1f' % ('Alien', 1979, 8.5)
Out[55]:
'Alien (1979) has an IMDB score of 8.5'
  • Think of this as deprecated. Better ways exist!

String formatting using str.format()

  • Very rich formatting language
  • Recommended way to format strings
In [56]:
'{movie} ({year}) has an IMDB score of {imdb:.2f}'.format(movie='Alien',
                                                        year=1979, 
                                                        imdb=8.5)
Out[56]:
'Alien (1979) has an IMDB score of 8.50'
In [57]:
'{} ({}) has an IMDB score of {:.1f}'.format('Alien', 1979, 8.5)
Out[57]:
'Alien (1979) has an IMDB score of 8.5'
  • Supports things like:
    • 0-padding numbers
    • float precision
    • left/right justification

Mini-assignment

Write a function that takes a list of movies and returns the name and score of the movie with the highest IMDB score.

Data

http://users.isy.liu.se/cvl/hanov56/pycourse/

In [58]:
movies = [
    {
        'name' : 'Star Wars',
        'year' : 1977,
        'imdb' : 8.7
     },
    
    {
        'name' : 'Alien',
        'year' : 1979,
        'imdb' : 8.5
    },
    
    {
        'name' : 'The Terminator',
        'year' : 1984,
        'imdb' : 8.1
    },
    
    {
        'name' : 'House of the Dead',
        'year' : 2002,
        'imdb' : 2.0
    },
]  

My solution

Batteries included!

In [59]:
def best_movie(movielist):
    best = max(movielist, key=lambda movie: movie['imdb'])
    return best['name'], best['imdb']

movie, score = best_movie(movies)
print("The best movie is '{}' with a score of {:.1f}".format(movie, score))
The best movie is 'Star Wars' with a score of 8.7

Classes

In [60]:
from math import pi

class Circle:
    name = 'Circle'
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return pi * self.radius ** 2

c = Circle(2.0)
print('A circle with radius {} has area {:.2f}'.format(c.radius,
                                                       c.area()))
A circle with radius 2.0 has area 12.57
  • __init__ is called when the object is initialized (a "constructor")
  • self $\approx$ this but is explicit
  • class members can be declared outside of __init__, but don't!

Class inheritance

  • Simply list parent classes
  • object is the top level object (can be omitted like previous example)
In [61]:
class Shape(object):
    def print_info(self):
        print('I am a {} with area {:.2f}'.format(self.shape_type,
                                                  self.area()))

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
        self.shape_type = 'Circle'
    
    def area(self):
        return pi * self.radius ** 2

c = Circle(2.0)
c.print_info()
I am a Circle with area 12.57

Where are my public and private??!

  • Nowhere: all attributes are "public"!
  • "getters" and "setters" are unneccessary (and unpythonic)

How about underscores?

  • '_' or '__' can be used to signal "privateness"
In [62]:
class A:
    def __init__(self):
        self._my_private = 10
a = A()
a._my_private # Hmm??
Out[62]:
10
In [63]:
class A:
    def __init__(self):
        self.__really_private = 10

a = A()
a.__really_private
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-63-9cf669e59029> in <module>()
      4 
      5 a = A()
----> 6 a.__really_private

AttributeError: 'A' object has no attribute '__really_private'
In [64]:
a._A__really_private
Out[64]:
10

We can never make something private. But we can give hints.

Calling parents methods: super()

In [65]:
class CircleWithSquareHole(Circle):
    def __init__(self, radius, hole_side):
        super().__init__(radius) # calls Circle.__init__(self, radius)
        self.side = hole_side
    
    def area(self):
        circle_area = super().area() # calls Circle.area(self)
        return circle_area - self.side ** 2
    
cwsh = CircleWithSquareHole(2, 1)
cwsh.area()
Out[65]:
11.566370614359172

Exceptions: handling errors

  • Python coding involves a lot of exception handling
  • try - except - finally blocks
In [66]:
try:
    y = 5 / 0
except ZeroDivisionError:
    print('Oh no! Divide by zero!')
    y = 0
finally:
    print('This always executes')
print('y =', y)
Oh no! Divide by zero!
This always executes
y = 0

Easier to ask forgiveness than to ask for permission

Python prefers exception handling over condition checking

Example: set value from dict, or to default value

In [67]:
DEFAULT_VALUE = 1
d = {'a' : 10, 'b' : 20}

if 'c' in d:
    x = 5
else:
    x = DEFAULT_VALUE
print(x)
1

is equivalent to

In [68]:
try:
    x = d['c']
except KeyError:
    x = DEFAULT_VALUE
print(x)
1

File open example

In [69]:
try:
    f = open('/tmp/thisfilereallydoesnotexist', 'r')
    data = f.read()
except IOError:
    print('Failed to open and read data from file')
Failed to open and read data from file

Compare to these pre-checks

  • Does the file exist?
  • Do I have the right permissions?
  • Is there some other error?

Exceptions (cont.)

Catch multiple exceptions

try:
    x = some_calculation()
except (ZeroDivisionError, ValueError):
    x = 0

Catch everything (avoid if possible). Why?

try:
    x = some_calculation()
except:
    x = 0

lambda functions

Short single statement anonymous functions

In [70]:
add = lambda a, b: a + b
add(3, 4)
Out[70]:
7

Example: sort by norm

In [71]:
alist = [(1, 2), (2,0), (0, 10)]
In [72]:
sorted(alist, key=lambda x: x[0] ** 2 + x[1] ** 2)
Out[72]:
[(2, 0), (1, 2), (0, 10)]

List-comprehensions

In [73]:
squares = [x ** 2 for x in range(10)]
print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [74]:
vehicles = [('SAAB 9-5', 'car'),
            ('Titanic', 'boat'),
            ('Tesla model S', 'car'),
            ('Atlantis', 'spaceship')]
In [75]:
cars = [name for name, vtype in vehicles if vtype == 'car']
print(cars)
['SAAB 9-5', 'Tesla model S']

List comprehensions (cont.)

result = [expr(item) for item in sequence 
                          if condition(item)]

Example: Loading images

  • Assume files is a list of filenames in a directory (not only images!)
  • The image files all start with image_ (e.g. image_000.png)
  • We want to convert the images to grayscale
images = [cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
            for image in 
                [cv2.imread(fn) for fn in files 
                    if fn.startswith('image_')
                ]
         ]
  • Nesting too many can hurt readability!

List comprehensions: dict

We can do the same for dicts

In [76]:
square_lookup = {x : x ** 2 for x in range(10)}
square_lookup[7]
Out[76]:
49

Reading files

  • Use open() to create a File object.
  • Modes: r, w, a, rb, wb, ...
In [77]:
f = open('/etc/redhat-release', 'r') # Read-only
data = f.read()
print('File contents:')
print(data)
f.close()
File contents:
Fedora release 22 (Twenty Two)

File objects

  • read(), write()
  • readlines(), writelines()
In [78]:
f = open('/etc/passwd', 'r')
lines = f.readlines()
for l in lines[:3]:
    print(l)
f.close()
root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

Why the extra linebreaks?

Reading files (cont.)

Forgot to call f.close()?

Either never create the File object f at all

In [79]:
data = open('/etc/redhat-release', 'r').read()

or use a context manager, using with

In [80]:
with open('/etc/redhat-release', 'r') as f:
    data = f.read()

The file is automatically closed after the with finishes

String manipulation

Some useful string methods

  • .split()
  • .strip()
  • .lower() / .upper()
In [81]:
s = 'The quick brown fox jumps over the lazy dog'
words = s.split()
words[3]
Out[81]:
'fox'
In [82]:
s.upper()
Out[82]:
'THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG'
In [83]:
'  extra whitespace      '.strip()
Out[83]:
'extra whitespace'

From list to string

A list of strings can be turned to a string with str.join().

In [84]:
print('My favourite fruits are', ', '.join(fruits[:3]))
My favourite fruits are oranges, apple, banana

Python modules and packages

  • package is a collection of modules
  • modules are used via import
In [85]:
import time
print(time)
print('Current POSIX time:', time.time())
<module 'time' (built-in)>
Current POSIX time: 1446481872.8159316

We can also import only specific names from a module using from ... import ...

In [86]:
from calendar import isleap, leapdays
isleap(1984)
Out[86]:
True

Assignment:

IMDB movie ratings

See assignment web page for details

Some useful things

  • help(obj) launches a help section for some object "obj", which can be an object instance, a class, a module, function, ...
  • dir(obj) lists all attributes of an object
  • repr(obj) returns the represenation string for an object

In [87]:
def do_duck_things(duck):
    print("I'm a duck that goes", duck.quack())
In [88]:
class Duck:
    def quack(self):
        return 'quack!'

donald = Duck()
do_duck_things(donald)
I'm a duck that goes quack!
In [89]:
class DisguisedCat:
    def quack(self):
        return 'meuw-ack!'

barry = DisguisedCat()
do_duck_things(barry)
I'm a duck that goes meuw-ack!

Duck typing

If it walks like a duck, and talks like a duck ... then it's a duck!