Introduction to Python

We introduce here the python language. Only the bare minimum necessary for getting started with the data-science stack (a bunch of libraries for data science).

To learn more about the language, consider going through the excellent tutorial

Dedicated books are also available, such as

Among many other references (since Python is one of the most popular languages right now)

Python is a programming language, as are C++, java, fortran, javascript, etc.

Specific features of Python

  • an interpreted (as opposed to compiled) language. Contrary to e.g. C++ or fortran, one does not compile Python code before executing it.

  • Used as a scripting language, by python python script.py in a terminal

  • But can be used also interactively: the jupyter notebook, iPython, etc.

  • A free software released under an open-source license: Python can be used and distributed free of charge, even for building commercial software.

  • multi-platform: Python is available for all major operating systems, Windows, Linux/Unix, MacOS X, most likely your mobile phone OS, etc.

  • A very readable language with clear non-verbose syntax

  • A language for which a large variety of high-quality packages are available for various applications, including web-frameworks and scientific computing

  • It is now the language of choice of data-science and machine learning since several years, because of his high expressivity and tools of deployment

  • An object-oriented language

See https://www.python.org/about/ for more information about distinguishing features of Python.

Important: Python 2 or Python 3

  • Simple answer: don't use Python 2, use Python 3

  • Python 2 is mostly deprecated and won't be maintained for long now

  • You'll end up hanged if you use Python 2

  • If Python 2 is mandatory at your workplace, find another work

0. Hello world

  • In a jupyter notebook, you have an interactive interpreter.

  • You type in the cells, execute commands with Shift + Enter (on a Mac, I won't help people on Windows and Linux).

In [1]:
print("Salut tout le monde !")
Salut tout le monde !

1. Basic types

1.1. Integers

In [2]:
1+1
Out[2]:
2
In [3]:
type(1+1)
Out[3]:
int

We can assign values to variables with =

In [4]:
a = (3 + 5 ** 2) % 4
a
Out[4]:
0

Remark

We don't declare the type of a variable before assigning its value. In C, conversely, one should write

int a = 4;

Something cool

  • Arbitrary large integer arithmetics
In [5]:
17 ** 542
Out[5]:
8004153099680695240677662228684856314409365427758266999205063931175132640587226837141154215226851187899067565063096026317140186260836873939218139105634817684999348008544433671366043519135008200013865245747791955240844192282274023825424476387832943666754140847806277355805648624376507618604963106833797989037967001806494232055319953368448928268857747779203073913941756270620192860844700087001827697624308861431399538404552468712313829522630577767817531374612262253499813723569981496051353450351968993644643291035336065584116155321928452618573467361004489993801594806505273806498684433633838323916674207622468268867047187858269410016150838175127772100983052010703525089

1.2 Floats

There exists a floating point type that is created when the variable has decimal values

In [6]:
c = 2.
In [7]:
type(c)
Out[7]:
float
In [8]:
c = 2
type(c)
Out[8]:
int
In [9]:
truc = 1 / 2
truc
Out[9]:
0.5
In [10]:
type(truc)
Out[10]:
float

1.3. Boolean

Similarly, boolean types are created from a comparison

In [11]:
test = 3 > 4
test
Out[11]:
False
In [12]:
type(test)
Out[12]:
bool
In [13]:
False == (not True)
Out[13]:
True
In [14]:
1.41 < 2.71 and 2.71 < 3.14
Out[14]:
True
In [15]:
# It's equivalent to
1.41 < 2.71 < 3.14
Out[15]:
True

1.4. Type conversion (casting)

In [16]:
a = 1
type(a)
Out[16]:
int
In [17]:
b = float(a)
type(b)
Out[17]:
float
In [18]:
str(b)
Out[18]:
'1.0'
In [19]:
bool(b)
# All non-zero, non empty objects are casted to boolean as True (more later)
Out[19]:
True
In [20]:
bool(1-1)
Out[20]:
False

2. Containers

Python provides many efficient types of containers, in which collections of objects can be stored.

The main ones are list, tuple, set and dict (but there are many others...)

2.1. Tuples

In [21]:
tt = ('truc', 3.14, "truc")
tt
Out[21]:
('truc', 3.14, 'truc')
In [22]:
tt[1]
Out[22]:
3.14

You can't change a tuple, we say that it's immutable

In [23]:
tt[0] = 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-bf6066b4c18c> in <module>
----> 1 tt[0] = 1

TypeError: 'tuple' object does not support item assignment

Three ways of doing the same thing

In [24]:
# Method 1
tuple([1, 2, 3])
Out[24]:
(1, 2, 3)
In [25]:
# Method 2
1, 2, 3
Out[25]:
(1, 2, 3)
In [26]:
# Method 3
(1, 2, 3)
Out[26]:
(1, 2, 3)

Simpler is better in Python, so usually you want to use Method 2.

In [27]:
toto = 1, 2, 3
toto
Out[27]:
(1, 2, 3)
  • This is serious !

The Zen of Python easter's egg

In [28]:
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

2.2. Lists

A list is an ordered collection of objects. These objects may have different types. For example:

In [29]:
colors = ['red', 'blue', 'green', 'black', 'white']
In [30]:
type(colors)
Out[30]:
list

Indexing: accessing individual objects contained in the list

In [31]:
colors[2]
Out[31]:
'green'
In [32]:
colors[2] = 3.14
colors
Out[32]:
['red', 'blue', 3.14, 'black', 'white']

Warning. Indexing starts at 0 (as in C), not at 1 (as in Fortran or Matlab) for any iterable object in Python.

Counting from the end with negative indices:

In [33]:
colors[-2]
Out[33]:
'black'

Index must remain in the range of the list

In [34]:
colors[10]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-34-906df9c59652> in <module>
----> 1 colors[10]

IndexError: list index out of range
In [35]:
colors
Out[35]:
['red', 'blue', 3.14, 'black', 'white']
In [36]:
tt
Out[36]:
('truc', 3.14, 'truc')
In [37]:
colors.append(tt)
colors
Out[37]:
['red', 'blue', 3.14, 'black', 'white', ('truc', 3.14, 'truc')]
In [38]:
len(colors)
Out[38]:
6

2.3. Slicing: obtaining sublists of regularly-spaced elements

This work with anything iterable whenever it makes sense (list, str, tuple, etc.)

In [39]:
colors
Out[39]:
['red', 'blue', 3.14, 'black', 'white', ('truc', 3.14, 'truc')]
In [40]:
list(reversed(colors))
Out[40]:
[('truc', 3.14, 'truc'), 'white', 'black', 3.14, 'blue', 'red']
In [41]:
colors[::-1]
Out[41]:
[('truc', 3.14, 'truc'), 'white', 'black', 3.14, 'blue', 'red']

Slicing syntax: colors[start:stop:stride]

NB: All slicing parameters are optional

In [42]:
colors
Out[42]:
['red', 'blue', 3.14, 'black', 'white', ('truc', 3.14, 'truc')]
In [43]:
colors[3:]
Out[43]:
['black', 'white', ('truc', 3.14, 'truc')]
In [44]:
colors[:3]
Out[44]:
['red', 'blue', 3.14]
In [45]:
colors[1::2]
Out[45]:
['blue', 'black', ('truc', 3.14, 'truc')]
In [46]:
colors[::-1]
Out[46]:
[('truc', 3.14, 'truc'), 'white', 'black', 3.14, 'blue', 'red']

2.4. Strings

Different string syntaxes (simple, double or triple quotes):

In [47]:
s = 'tintin'
type(s)
Out[47]:
str
In [48]:
s
Out[48]:
'tintin'
In [49]:
s = """         Bonjour,
Je m'appelle Stephane.
Je vous souhaite une bonne journée.
Salut.       
"""
s
Out[49]:
"         Bonjour,\nJe m'appelle Stephane.\nJe vous souhaite une bonne journée.\nSalut.       \n"
In [50]:
s.strip()
Out[50]:
"Bonjour,\nJe m'appelle Stephane.\nJe vous souhaite une bonne journée.\nSalut."
In [51]:
print(s.strip())
Bonjour,
Je m'appelle Stephane.
Je vous souhaite une bonne journée.
Salut.
In [52]:
len(s)
Out[52]:
91
In [53]:
# Casting to a list
list(s.strip()[:15])
Out[53]:
['B', 'o', 'n', 'j', 'o', 'u', 'r', ',', '\n', 'J', 'e', ' ', 'm', "'", 'a']
In [54]:
# Arithmetics
print('Bonjour' * 2)
print('Hello' + ' all')
BonjourBonjour
Hello all
In [55]:
sss = 'A'
sss += 'bc'
sss += 'dE'
sss.lower()
Out[55]:
'abcde'
In [56]:
ss = s.strip()
print(ss[:10] + ss[24:28])
Bonjour,
Jepha
In [57]:
s.strip().split('\n')
Out[57]:
['Bonjour,',
 "Je m'appelle Stephane.",
 'Je vous souhaite une bonne journée.',
 'Salut.']
In [58]:
s[::3]
Out[58]:
'   BjrJmpl ea.eo ui eoeon.at  \n'
In [59]:
s[3:10]
Out[59]:
'      B'
In [60]:
' '.join(['Il', 'fait', 'super', 'beau', "aujourd'hui"])
Out[60]:
"Il fait super beau aujourd'hui"

Important

A string is immutable !!

In [61]:
s = 'I am an immutable guy'
In [62]:
s[2] = 's'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-62-45aecec22f77> in <module>
----> 1 s[2] = 's'

TypeError: 'str' object does not support item assignment
In [63]:
id(s)
Out[63]:
140546632267576
In [64]:
print(s + ' for sure')
id(s), id(s + ' for sure')
I am an immutable guy for sure
Out[64]:
(140546632267576, 140546365604528)

Extra stuff with strings

In [65]:
'square of 2 is ' + str(2 ** 2)
Out[65]:
'square of 2 is 4'
In [66]:
'square of 2 is %d' % 2 ** 2
Out[66]:
'square of 2 is 4'
In [67]:
'square of 2 is {}'.format(2 ** 2)
Out[67]:
'square of 2 is 4'
In [68]:
'square of 2 is {square}'.format(square=2 ** 2)
Out[68]:
'square of 2 is 4'
In [69]:
# And since Python 3.6 you can use an `f-string`
number = 2
square = number ** 2

f'square of {number} is {square}'
Out[69]:
'square of 2 is 4'

The in keyword

You can use the in keyword with any container, whenever it makes sense

In [70]:
print(s)
print('Salut' in s)
I am an immutable guy
False
In [71]:
print(tt)
print('truc' in tt)
('truc', 3.14, 'truc')
True
In [72]:
print(colors)
print('truc' in colors)
['red', 'blue', 3.14, 'black', 'white', ('truc', 3.14, 'truc')]
False
In [73]:
('truc', 3.14, 'truc') in colors
Out[73]:
True

Brain-f**king

Explain this weird behaviour:

In [74]:
5 in [1, 2, 3, 4] == False
Out[74]:
False
In [75]:
5 not in [1, 2, 3, 4]
Out[75]:
True
In [76]:
(5 in [1, 2, 3, 4]) == False
Out[76]:
True
In [77]:
# ANSWER.
# This is a chained comparison. We have seen that 
1 < 2 < 3
# is equivalent to
(1 < 2) and (2 < 3)
# so that
5 in [1, 2, 3, 4] == False
# is equivalent to
(5 in [1, 2, 3, 4]) and ([1, 2, 3, 4] == False)
Out[77]:
False

2.5. Dictionaries

A dictionary is basically an efficient table that maps keys to values.

The MOST important container in Python.

Many things are actually a dict under the hood in Python

In [78]:
tel = {'emmanuelle': 5752, 'sebastian': 5578}
print(tel)
print(type(tel))
{'emmanuelle': 5752, 'sebastian': 5578}
<class 'dict'>
In [79]:
tel['emmanuelle'], tel['sebastian']
Out[79]:
(5752, 5578)
In [80]:
tel['francis'] = '5919'
tel
Out[80]:
{'emmanuelle': 5752, 'sebastian': 5578, 'francis': '5919'}
In [81]:
len(tel)
Out[81]:
3

Important remarks

  • Keys can be of different types
  • A key must be of immutable type
In [82]:
tel[7162453] = [1, 3, 2]
tel[3.14] = 'bidule'
tel[('jaouad', 2)] = 1234
tel
Out[82]:
{'emmanuelle': 5752,
 'sebastian': 5578,
 'francis': '5919',
 7162453: [1, 3, 2],
 3.14: 'bidule',
 ('jaouad', 2): 1234}
In [83]:
# A list is immutable
tel[['jaouad']] = '5678'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-83-9ea80f1b5f3d> in <module>
      1 # A list is immutable
----> 2 tel[['jaouad']] = '5678'

TypeError: unhashable type: 'list'
In [84]:
tel = {'emmanuelle': 5752, 'sebastian' : 5578, 'jaouad' : 1234}
print(tel.keys())
print(tel.values())
print(tel.items())
dict_keys(['emmanuelle', 'sebastian', 'jaouad'])
dict_values([5752, 5578, 1234])
dict_items([('emmanuelle', 5752), ('sebastian', 5578), ('jaouad', 1234)])
In [85]:
'emmanuelle' in tel
Out[85]:
True
In [86]:
5919 in tel.values()
Out[86]:
False

You can swap values like this

In [87]:
print(tel)
tel['emmanuelle'], tel['sebastian'] = tel['sebastian'], tel['emmanuelle']
print(tel)
{'emmanuelle': 5752, 'sebastian': 5578, 'jaouad': 1234}
{'emmanuelle': 5578, 'sebastian': 5752, 'jaouad': 1234}
In [88]:
# It works, since
a, b = 2.71, 3.14
a, b = b, a
a, b
Out[88]:
(3.14, 2.71)

Exercice 1

Get keys of tel sorted by decreasing order

In [89]:
tel = {'emmanuelle': 5752, 'sebastian' : 5578, 'jaouad' : 1234}

Answer

In [90]:
sorted(tel, reverse=True)
Out[90]:
['sebastian', 'jaouad', 'emmanuelle']

Exercice 2

Get keys of tel sorted by increasing values

In [91]:
tel = {'emmanuelle': 5752, 'sebastian' : 5578, 'jaouad' : 1234}

Answer

In [92]:
sorted(tel, key=tel.get)
Out[92]:
['jaouad', 'sebastian', 'emmanuelle']

Exercice 3

Obtain a sorted-by-key version of tel

In [93]:
tel = {'emmanuelle': 5752, 'sebastian' : 5578, 'jaouad' : 1234}

Answer

  • A dict is inherently orderless
  • Only a representation of a dict can be ordered
In [94]:
# Simplest is through a list
sorted(tel.items())
Out[94]:
[('emmanuelle', 5752), ('jaouad', 1234), ('sebastian', 5578)]

If you really want an ordered dict OrderDict memorizes order of insertion in it

In [95]:
from collections import OrderedDict

OrderedDict(sorted(tel.items()))
Out[95]:
OrderedDict([('emmanuelle', 5752), ('jaouad', 1234), ('sebastian', 5578)])

2.5 Sets

A set is an unordered container, containing unique elements

In [96]:
ss = {1, 2, 2, 2, 3, 3, 'tintin', 'tintin', 'toto'}
ss
Out[96]:
{1, 2, 3, 'tintin', 'toto'}
In [97]:
s = 'truc truc bidule truc'
set(s)
Out[97]:
{' ', 'b', 'c', 'd', 'e', 'i', 'l', 'r', 't', 'u'}
In [98]:
{1, 5, 2, 1, 1}.union({1, 2, 3})
Out[98]:
{1, 2, 3, 5}
In [99]:
set([1, 5, 2, 1, 1]).intersection(set([1, 2, 3]))
Out[99]:
{1, 2}
In [100]:
ss.add('tintin')
ss
Out[100]:
{1, 2, 3, 'tintin', 'toto'}

You can combine all containers together

In [101]:
dd = {
    'truc': [1, 2, 3], 
    5: (1, 4, 2),
    (1, 3): {'hello', 'world'}
}
dd
Out[101]:
{'truc': [1, 2, 3], 5: (1, 4, 2), (1, 3): {'hello', 'world'}}

3. Assigments in Python is name binding

(and everything is either mutable or immutable)

In [102]:
ss = {1, 2, 3}
sss = ss
sss, ss
Out[102]:
({1, 2, 3}, {1, 2, 3})
In [103]:
sss.add(4)

Question. What is in ss ?

In [104]:
ss, sss
Out[104]:
({1, 2, 3, 4}, {1, 2, 3, 4})

ss and sss are names for the same object

In [105]:
id(ss), id(sss)
Out[105]:
(140546632072104, 140546632072104)
In [106]:
ss is sss
Out[106]:
True

Something very important about assigments in Python

  • Python never copies an object
  • Unless you ask him to

When you code

x = [1, 2, 3]
y = x

you just

  • bind the variable name x to a list [1, 2, 3]
  • give another name y to the same object

Important remarks

  • Everything is an object in Python
  • Either immutable or mutable
In [107]:
id(1), id(1+1), id(2)
Out[107]:
(4357940640, 4357940672, 4357940672)

A list is mutable

In [108]:
x = [1, 2, 3]
print(id(x), x)
x[0] += 42; x.append(3.14)
print(id(x), x)
140547174811336 [1, 2, 3]
140547174811336 [43, 2, 3, 3.14]

A str is immutable

In order to "change" an immutable object, Python creates a new one

In [109]:
s = 'to'
print(id(s), s)
s += 'to'
print(id(s), s)
140546899465360 to
140546632299944 toto

Once again, a list is mutable

In [110]:
super_list = [3.14, (1, 2, 3), 'tintin']
other_list = super_list
id(other_list), id(super_list)
Out[110]:
(140546365584456, 140546365584456)
  • other_list and super_list are the same list
  • If you change one, you change the other.
  • id returns the identity of an object. Two objects with the same idendity are the same (not only the same type, but the same instance)
In [111]:
other_list[1] = 'youps'
other_list, super_list
Out[111]:
([3.14, 'youps', 'tintin'], [3.14, 'youps', 'tintin'])

If you want a copy, to need to ask for one

In [112]:
other_list = super_list.copy()
id(other_list), id(super_list)
Out[112]:
(140547174911368, 140546365584456)
In [113]:
other_list[1] = 'copy'
other_list, super_list
Out[113]:
([3.14, 'copy', 'tintin'], [3.14, 'youps', 'tintin'])

Only other_list is modified.

But... what if you have a list of list ? (or a mutable object containing mutable objects)

In [114]:
l1, l2 = [1, 2, 3], [4, 5, 6]
list_list = [l1, l2]
list_list
Out[114]:
[[1, 2, 3], [4, 5, 6]]

Let's make a copy of list_list

In [115]:
copy_list = list_list.copy()
copy_list.append('super')
list_list, copy_list
Out[115]:
([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6], 'super'])

OK, only copy_list is modified, as expected

But now...

In [116]:
copy_list[0][1] = 'oups'
copy_list, list_list
Out[116]:
([[1, 'oups', 3], [4, 5, 6], 'super'], [[1, 'oups', 3], [4, 5, 6]])

Question. What happened ?!?

  • The list_list object is copied
  • But NOT what it's containing !
  • By default copy does a shallow copy, not a deep copy
  • It does not build copies of what is contained
  • If you want to copy an object and all that is contained in it, you need to use deepcopy.
In [117]:
from copy import deepcopy

copy_list = deepcopy(list_list)
copy_list[0][1] = 'incredible !'
list_list, copy_list
Out[117]:
([[1, 'oups', 3], [4, 5, 6]], [[1, 'incredible !', 3], [4, 5, 6]])

Final remarks

In [118]:
tt = ([1, 2, 3], [4, 5, 6])
print(id(tt), tt)
print(list(map(id, tt)))
140546900850504 ([1, 2, 3], [4, 5, 6])
[140546632175304, 140546632251912]
In [119]:
tt[0][1] = '42'
print(id(tt), tt)
print(list(map(id, tt)))
140546900850504 ([1, '42', 3], [4, 5, 6])
[140546632175304, 140546632251912]

4. Control flow and other stuff...

Namely tests, loops, again booleans, etc.

In [120]:
if 2 ** 2 == 5:
    print('Obvious')
else:
    print('YES')
print('toujours')
YES
toujours

4.1. Blocks are delimited by indentation!

In [121]:
a = 3
if a > 0:
    if a == 1:
        print(1)
    elif a == 2:
        print(2)
elif a == 2:
    print(2)
elif a == 3:
    print(3)
else:
    print(a)

4.2. Anything can be understood as a boolean

For example, don't do this to test if a list is empty

In [122]:
l2 = ['hello', 'everybody']

if len(l2) > 0:
    print(l2[0])
hello

but this

In [123]:
if l2:
    print(l2[0])
hello

Poetry

  • An empty dict is False
  • An empty string is False
  • An empty list is False
  • An empty tuple is False
  • An empty set is False
  • 0 is False
  • .0 is False
  • etc...
  • everything else is True

4.2. While loops

In [124]:
a = 10
b = 1
while b < a:
    b = b + 1
    print(b)
2
3
4
5
6
7
8
9
10

Compute the decimals of Pi using the Wallis formula

$$ \pi = 2 \prod_{i=1}^{100} \frac{4i^2}{4i^2 - 1} $$
In [125]:
pi = 2
eps = 1e-10
dif = 2 * eps
i = 1
while dif > eps:
    pi, i, old_pi = pi * 4 * i ** 2 / (4 * i ** 2 - 1), i + 1, pi
    dif = pi - old_pi
In [126]:
pi
Out[126]:
3.1415837914138556
In [127]:
from math import pi

pi
Out[127]:
3.141592653589793

4.3. for loop with range

  • Iteration with an index, with a list, with many things !
  • range has the same parameters as with slicing start:end:stride, all parameters being optional
In [128]:
for i in range(7, 1, -1):
    print(i)
7
6
5
4
3
2
In [129]:
for i in range(4):
    print(i + 1)
print('-')

for i in range(1, 5):
    print(i)
print('-')

for i in range(1, 10, 3):
    print(i)
1
2
3
4
-
1
2
3
4
-
1
4
7

Something for nerds. You can use else in a for loop

In [130]:
names = ['stephane', 'mokhtar', 'jaouad', 'simon', 'yiyang']

for name in names:
    if name.startswith('u'):
        print(name)
        break
else:
    print('Not found.')
Not found.
In [131]:
names = ['stephane', 'mokhtar', 'jaouad', 'ulysse', 'simon', 'yiyang']

for name in names:
    if name.startswith('u'):
        print(name)
        break
else:
    print('Not found.')
ulysse

4.4. For loops over iterable objects

You can iterate using for over any container: list, tuple, dict, str, set among others...

In [132]:
colors = ['red', 'blue', 'black', 'white']
peoples = ['stephane', 'jaouad', 'mokhtar', 'yiyang']
In [133]:
# This is stupid
for i in range(len(colors)):
    print(colors[i])
    
# This is better
for color in colors:
    print(color)
red
blue
black
white
red
blue
black
white

To iterate over several sequences at the same time, use zip

In [134]:
for color, people in zip(colors, peoples):
    print(color, people)
red stephane
blue jaouad
black mokhtar
white yiyang
In [135]:
l = ["Bonjour", {'francis': 5214, 'stephane': 5123}, ('truc', 3)]
for e in l:
    print(e, len(e))
Bonjour 7
{'francis': 5214, 'stephane': 5123} 2
('truc', 3) 2

Loop over a str

In [136]:
s = 'Bonjour'
for c in s:
    print(c)
B
o
n
j
o
u
r

Loop over a dict

In [137]:
dd = {(1, 3): {'hello', 'world'}, 'truc': [1, 2, 3], 5: (1, 4, 2)}

# Default is to loop over keys
for key in dd:
    print(key)
(1, 3)
truc
5
In [138]:
# Loop over values
for e in dd.values():
    print(e)
{'hello', 'world'}
[1, 2, 3]
(1, 4, 2)
In [139]:
# Loop over items (key, value) pairs
for key, val in dd.items():
    print(key, val)
(1, 3) {'hello', 'world'}
truc [1, 2, 3]
5 (1, 4, 2)

4.5. Comprehensions

You can construct a list, dict, set and others using the comprehension syntax

list comprehension

In [140]:
print(colors)
print(peoples)

# The list of people with favorite color that has no more than 4 characters
[people for color, people in zip(colors, peoples) if len(color) <= 4]
['red', 'blue', 'black', 'white']
['stephane', 'jaouad', 'mokhtar', 'yiyang']
Out[140]:
['stephane', 'jaouad']

dict comprehension

In [141]:
{people: color for color, people in zip(colors, peoples) if len(color) <= 4}
Out[141]:
{'stephane': 'red', 'jaouad': 'blue'}
In [142]:
# Allows to build a dict from two lists (for keys and values)
{key: value for (key, value) in zip(peoples, colors)}
Out[142]:
{'stephane': 'red', 'jaouad': 'blue', 'mokhtar': 'black', 'yiyang': 'white'}
In [143]:
# But it's simpler (so better) to use
dict(zip(peoples, colors))
Out[143]:
{'stephane': 'red', 'jaouad': 'blue', 'mokhtar': 'black', 'yiyang': 'white'}

Something very convenient is enumerate

In [144]:
for i, color in enumerate(colors):
    print(i, color)
0 red
1 blue
2 black
3 white
In [145]:
list(enumerate(colors))
Out[145]:
[(0, 'red'), (1, 'blue'), (2, 'black'), (3, 'white')]
In [146]:
print(dict(enumerate(s)))
{0: 'B', 1: 'o', 2: 'n', 3: 'j', 4: 'o', 5: 'u', 6: 'r'}
In [147]:
s = 'Hey everyone'
{c: i for i, c in enumerate(s)}
Out[147]:
{'H': 0, 'e': 11, 'y': 8, ' ': 3, 'v': 5, 'r': 7, 'o': 9, 'n': 10}

5. About functional programming

We can use lambda to define anonymous functions, and use them in the map and reduce functions

In [148]:
square = lambda x: x ** 2
square(2)
Out[148]:
4
In [149]:
sum2 = lambda a, b: a + b
print(sum2('Hello', ' world'))
print(sum2(1, 2))
Hello world
3

Intended for short and one-line function.

More complex functions use def (see below)

Exercice

Print the squares of even numbers between 0 et 15

  1. Using a list comprehension as before
  2. Using map
In [150]:
# Answer to 1.
[i ** 2 for i in range(15) if i % 2 == 0]
Out[150]:
[0, 4, 16, 36, 64, 100, 144, 196]
In [151]:
# Answer to 2. 
list(map(lambda x: x ** 2, range(0, 15, 2)))
Out[151]:
[0, 4, 16, 36, 64, 100, 144, 196]

Remark. We will see later why we need to use list above

In [152]:
map(lambda x: x ** 2, range(0, 15, 2))
Out[152]:
<map at 0x7fd3802f0b70>

Now, to get the sum of these squares, we can use sum

In [153]:
sum(map(lambda x: x ** 2, range(0, 15, 2)))
Out[153]:
560

We can also use reduce (not a good idea here, but it's good to know that it exists)

In [154]:
from functools import reduce

reduce(lambda a, b: a + b, map(lambda x: x ** 2, range(0, 15, 2)))
Out[154]:
560

Brain-f**king

What is the output of

reduce(lambda a, b: a + b[0] * b[1], enumerate('abcde'), 'A')
In [155]:
reduce(lambda a, b: a + b[0] * b[1], enumerate('abcde'), 'A')
Out[155]:
'Abccdddeeee'

This does the following

In [156]:
((((('A' + 0 * 'a') + 1 * 'b') + 2 * 'c') + 3 * 'd') + 4 * 'e')
Out[156]:
'Abccdddeeee'

6. A glimpse at the collections module

(This is where the good stuff hides)

In [157]:
texte = """
Bonjour,
Python c'est super.
Python ca a l'air quand même un peu compliqué.
Mais bon, ca a l'air pratique.
Peut-être que je pourrais m'en servir pour faire des trucs super.
"""
texte
Out[157]:
"\nBonjour,\nPython c'est super.\nPython ca a l'air quand même un peu compliqué.\nMais bon, ca a l'air pratique.\nPeut-être que je pourrais m'en servir pour faire des trucs super.\n"
In [158]:
print(texte)
Bonjour,
Python c'est super.
Python ca a l'air quand même un peu compliqué.
Mais bon, ca a l'air pratique.
Peut-être que je pourrais m'en servir pour faire des trucs super.

In [159]:
# Some basic text preprocessing 
new_text = texte.strip()\
    .replace('\n', ' ')\
    .replace(',', ' ')\
    .replace('.', ' ')

print(new_text)
print('-' * 8)

words = new_text.split()
print(words)
Bonjour  Python c'est super  Python ca a l'air quand même un peu compliqué  Mais bon  ca a l'air pratique  Peut-être que je pourrais m'en servir pour faire des trucs super 
--------
['Bonjour', 'Python', "c'est", 'super', 'Python', 'ca', 'a', "l'air", 'quand', 'même', 'un', 'peu', 'compliqué', 'Mais', 'bon', 'ca', 'a', "l'air", 'pratique', 'Peut-être', 'que', 'je', 'pourrais', "m'en", 'servir', 'pour', 'faire', 'des', 'trucs', 'super']

Exercice

Count the number of occurences of all the words in words.

Output must be a dictionary containg word: count

In [160]:
print(words)
['Bonjour', 'Python', "c'est", 'super', 'Python', 'ca', 'a', "l'air", 'quand', 'même', 'un', 'peu', 'compliqué', 'Mais', 'bon', 'ca', 'a', "l'air", 'pratique', 'Peut-être', 'que', 'je', 'pourrais', "m'en", 'servir', 'pour', 'faire', 'des', 'trucs', 'super']

Solution 1

In [161]:
words_counts = {}
for word in words:
    if word in words_counts:
        words_counts[word] += 1
    else:
        words_counts[word] = 1

print(words_counts)
{'Bonjour': 1, 'Python': 2, "c'est": 1, 'super': 2, 'ca': 2, 'a': 2, "l'air": 2, 'quand': 1, 'même': 1, 'un': 1, 'peu': 1, 'compliqué': 1, 'Mais': 1, 'bon': 1, 'pratique': 1, 'Peut-être': 1, 'que': 1, 'je': 1, 'pourrais': 1, "m'en": 1, 'servir': 1, 'pour': 1, 'faire': 1, 'des': 1, 'trucs': 1}

Solution 2

In [162]:
from collections import defaultdict

words_counts = defaultdict(int)
for word in words:
    words_counts[word] += 1

print(words_counts)
defaultdict(<class 'int'>, {'Bonjour': 1, 'Python': 2, "c'est": 1, 'super': 2, 'ca': 2, 'a': 2, "l'air": 2, 'quand': 1, 'même': 1, 'un': 1, 'peu': 1, 'compliqué': 1, 'Mais': 1, 'bon': 1, 'pratique': 1, 'Peut-être': 1, 'que': 1, 'je': 1, 'pourrais': 1, "m'en": 1, 'servir': 1, 'pour': 1, 'faire': 1, 'des': 1, 'trucs': 1})
  • defaultdict can be extremely useful
  • A dict with a default value: here an int is created (defaults to 0) if key is not found
  • Allows to avoid a test
In [163]:
int()
Out[163]:
0

About defaultdict

  • the argument must be a "callable" (something that can be called)
  • Beware: as soon as a key is searched, a default value is added to the defaultdict
In [164]:
addresses = defaultdict(lambda: 'unknown')
addresses['huyen']
addresses['stephane'] = '8 place Aurelie Nemours'
print(addresses)
defaultdict(<function <lambda> at 0x7fd3b06ebb70>, {'huyen': 'unknown', 'stephane': '8 place Aurelie Nemours'})
In [165]:
# Somewhat nasty...
print('jean-francois' in addresses)
print(addresses['jean-francois'])
print('jean-francois' in addresses)
False
unknown
True

Solution 3

In [166]:
from collections import Counter

print(dict(Counter(words)))
{'Bonjour': 1, 'Python': 2, "c'est": 1, 'super': 2, 'ca': 2, 'a': 2, "l'air": 2, 'quand': 1, 'même': 1, 'un': 1, 'peu': 1, 'compliqué': 1, 'Mais': 1, 'bon': 1, 'pratique': 1, 'Peut-être': 1, 'que': 1, 'je': 1, 'pourrais': 1, "m'en": 1, 'servir': 1, 'pour': 1, 'faire': 1, 'des': 1, 'trucs': 1}

Counter counts the number of occurences of all objects in an iterable

Question. Which one do you prefer ?

  • The Counter one right ?

Morality

  • When you need to do something, assume that there is a tool to do it directly

  • If you can't find it, ask google or stackoverflow

  • Otherwise, try to do it as simply as possible

Exercice

Compute the number of occurences AND the length of each word in words.

Output must be a dictionary containing word: (count, length)

Solution

In [167]:
from collections import Counter

{word: (count, len(word)) for word, count in Counter(words).items()}
Out[167]:
{'Bonjour': (1, 7),
 'Python': (2, 6),
 "c'est": (1, 5),
 'super': (2, 5),
 'ca': (2, 2),
 'a': (2, 1),
 "l'air": (2, 5),
 'quand': (1, 5),
 'même': (1, 4),
 'un': (1, 2),
 'peu': (1, 3),
 'compliqué': (1, 9),
 'Mais': (1, 4),
 'bon': (1, 3),
 'pratique': (1, 8),
 'Peut-être': (1, 9),
 'que': (1, 3),
 'je': (1, 2),
 'pourrais': (1, 8),
 "m'en": (1, 4),
 'servir': (1, 6),
 'pour': (1, 4),
 'faire': (1, 5),
 'des': (1, 3),
 'trucs': (1, 5)}

More from collections

There is also the namedtuple. It's a tuple but with named attributes

In [168]:
from collections import namedtuple

Jedi = namedtuple('Jedi', ['firstname', 'lastname', 'age', 'color'])
yoda = Jedi('Minch', 'Yoda', 900, 'green')
yoda
Out[168]:
Jedi(firstname='Minch', lastname='Yoda', age=900, color='green')
In [169]:
yoda.firstname
Out[169]:
'Minch'
In [170]:
yoda[1]
Out[170]:
'Yoda'

Remark. A better alternative since Python 3.7 is dataclasses. We will talk about it later (or never)

7. I/O, reading and writing files

Next, we assume that you have a text file miserables.txt in the folder containing this notebook.

If you don't, simply run the next cell that downloads it from my webpage and saves it in a file.

In [171]:
import requests

url = 'https://stephanegaiffas.github.io/files/miserables.txt'
r = requests.get(url)

with open('miserables.txt', 'wb') as f:
    f.write(r.content)

In jupyter and ipython you can run terminal command lines using !

Let's count number of lines and number of words with the wc command-line tool (linux or mac only, don't ask me how on windows)

In [172]:
# Lines count
!wc -l miserables.txt
   66633 miserables.txt
In [173]:
# Word count
!wc -w miserables.txt
  515774 miserables.txt

Exercice

Count the number of occurences of each word in the text file miserables.txt. We use a open context and the Counter from before.

In [174]:
counter = Counter()

with open('miserables.txt', encoding='utf8') as f:
    for line in f:
        line = line.strip().replace('\n', ' ')\
            .replace(',', ' ')\
            .replace('.', ' ')\
            .replace('»', ' ')\
            .replace('-', ' ')\
            .replace('!', ' ')\
            .replace('(', ' ')\
            .replace(')', ' ')\
            .replace('?', ' ').split()
        
        counter.update(line)

Contexts

  • A context in Python is something that we use with the with keyword.

  • It allows to deal automatically with the opening and the closing of the file.

Note the for loop:

for line in f:
    ...

You loop directly over the lines of the open file from within the open context

In [175]:
counter
Out[175]:
Counter({'Victor': 7,
         'Hugo': 8,
         'LES': 6,
         'MISÉRABLES': 4,
         'Tome': 5,
         'I': 98,
         'FANTINE': 1,
         '1862': 6,
         'TABLE': 3,
         'DES': 3,
         'MATIÈRES': 3,
         'Livre': 98,
         'premier': 242,
         'Un': 581,
         'juste': 90,
         'Chapitre': 732,
         'Monsieur': 238,
         'Myriel': 34,
         'II': 103,
         'devient': 58,
         'monseigneur': 63,
         'Bienvenu': 38,
         'III': 91,
         'À': 540,
         'bon': 397,
         'évêque': 61,
         'dur': 18,
         'évêché': 3,
         'IV': 92,
         'Les': 937,
         'oeuvres': 10,
         'semblables': 7,
         'aux': 934,
         'paroles': 94,
         'V': 70,
         'Que': 167,
         'faisait': 453,
         'durer': 13,
         'trop': 243,
         'longtemps': 111,
         'ses': 1282,
         'soutanes': 3,
         'VI': 63,
         'Par': 66,
         'qui': 5499,
         'il': 6172,
         'garder': 30,
         'sa': 2213,
         'maison': 327,
         'VII': 50,
         'Cravatte': 4,
         'VIII': 44,
         'Philosophie': 2,
         'après': 415,
         'boire': 43,
         'IX': 33,
         'Le': 1688,
         'frère': 101,
         'raconté': 12,
         'par': 2058,
         'la': 14553,
         'soeur': 152,
         'X': 34,
         "L'évêque": 58,
         'en': 4359,
         'présence': 67,
         "d'une": 819,
         'lumière': 217,
         'inconnue': 18,
         'XI': 26,
         'Une': 420,
         'restriction': 3,
         'XII': 17,
         'Solitude': 2,
         'de': 21683,
         'XIII': 17,
         'Ce': 693,
         "qu'il": 1904,
         'croyait': 84,
         'XIV': 20,
         'pensait': 47,
         'deuxième': 54,
         'La': 1392,
         'chute': 41,
         'soir': 218,
         "d'un": 1042,
         'jour': 525,
         'marche': 84,
         'prudence': 14,
         'conseillée': 3,
         'à': 9608,
         'sagesse': 22,
         'Héroïsme': 2,
         "l'obéissance": 6,
         'passive': 5,
         'Détails': 4,
         'sur': 2589,
         'les': 7103,
         'fromageries': 4,
         'Pontarlier': 7,
         'Tranquillité': 2,
         'Jean': 1240,
         'Valjean': 1091,
         'dedans': 83,
         'du': 4006,
         'désespoir': 51,
         "L'onde": 2,
         'et': 13148,
         "l'ombre": 146,
         'Nouveaux': 2,
         'griefs': 7,
         "L'homme": 145,
         'réveillé': 12,
         'fait': 1132,
         'travaille': 25,
         'Petit': 83,
         'Gervais': 29,
         'troisième': 87,
         'En': 394,
         "l'année": 23,
         '1817': 15,
         "L'année": 5,
         'Double': 3,
         'quatuor': 4,
         'Quatre': 25,
         'quatre': 421,
         'Tholomyès': 62,
         'est': 2459,
         'si': 936,
         'joyeux': 29,
         'chante': 16,
         'une': 4452,
         'chanson': 19,
         'espagnole': 6,
         'Chez': 17,
         'Bombarda': 14,
         'où': 1197,
         "l'on": 284,
         "s'adore": 3,
         'Sagesse': 3,
         'Mort': 9,
         'cheval': 101,
         'Fin': 12,
         'joyeuse': 15,
         'joie': 146,
         'quatrième': 33,
         'Confier': 2,
         "c'est": 1312,
         'quelquefois': 120,
         'livrer': 9,
         'mère': 339,
         'rencontre': 51,
         'autre': 305,
         'Première': 2,
         'esquisse': 3,
         'deux': 1416,
         'figures': 24,
         'louches': 3,
         "L'Alouette": 4,
         'cinquième': 27,
         'descente': 9,
         'Histoire': 4,
         'progrès': 80,
         'dans': 5272,
         'verroteries': 7,
         'noires': 30,
         'M': 685,
         'Madeleine': 278,
         'Sommes': 3,
         'déposées': 5,
         'chez': 320,
         'Laffitte': 16,
         'deuil': 25,
         'Vagues': 2,
         'éclairs': 12,
         "l'horizon": 44,
         'père': 636,
         'Fauchelevent': 294,
         'jardinier': 31,
         'Paris': 390,
         'Madame': 73,
         'Victurnien': 8,
         'dépense': 17,
         'trente': 89,
         'cinq': 270,
         'francs': 306,
         'pour': 2441,
         'morale': 26,
         'Succès': 3,
         'Suite': 4,
         'succès': 25,
         '_Christus': 2,
         'nos': 98,
         'liberavit_': 2,
         'désoeuvrement': 2,
         'Bamatabois': 6,
         'Solution': 2,
         'quelques': 367,
         'questions': 54,
         'police': 150,
         'municipale': 10,
         'sixième': 18,
         'Javert': 452,
         'Commencement': 9,
         'repos': 25,
         'Comment': 101,
         'peut': 548,
         'devenir': 53,
         'Champ': 20,
         'septième': 23,
         "L'affaire": 7,
         'Champmathieu': 46,
         'Simplice': 30,
         'Perspicacité': 2,
         'maître': 90,
         'Scaufflaire': 18,
         'tempête': 12,
         'sous': 800,
         'un': 5973,
         'crâne': 12,
         'Formes': 2,
         'que': 5535,
         'prend': 54,
         'souffrance': 32,
         'pendant': 135,
         'le': 11056,
         'sommeil': 46,
         'Bâtons': 2,
         'roues': 39,
         'mise': 45,
         "l'épreuve": 9,
         'voyageur': 48,
         'arrivé': 66,
         'précautions': 12,
         'repartir': 10,
         'Entrée': 4,
         'faveur': 7,
         'lieu': 147,
         'des': 5042,
         'convictions': 4,
         'sont': 712,
         'train': 37,
         'se': 4049,
         'former': 8,
         'système': 14,
         'dénégations': 4,
         'plus': 2413,
         'étonné': 23,
         'huitième': 33,
         'Contre': 2,
         'coup': 434,
         'Dans': 262,
         'quel': 120,
         'miroir': 37,
         'regarde': 65,
         'cheveux': 140,
         'Fantine': 202,
         'heureuse': 44,
         'content': 39,
         "L'autorité": 3,
         'reprend': 7,
         'droits': 17,
         'Tombeau': 2,
         'convenable': 8,
         '1815': 42,
         'Charles': 30,
         'François': 15,
         'était': 3509,
         'Digne': 56,
         "C'était": 461,
         'vieillard': 127,
         "d'environ": 13,
         'soixante': 54,
         'quinze': 93,
         'ans;': 11,
         'occupait': 15,
         'siège': 9,
         'depuis': 246,
         '1806': 7,
         'Quoique': 16,
         'ce': 4007,
         'détail': 26,
         'ne': 3682,
         'touche': 14,
         'aucune': 80,
         'manière': 63,
         'au': 2628,
         'fond': 258,
         'même': 1247,
         'nous': 1117,
         'avons': 140,
         'raconter': 12,
         "n'est": 509,
         'être': 894,
         'pas': 3797,
         'inutile': 40,
         'fût': 222,
         'exact': 11,
         'tout': 1728,
         "d'indiquer": 9,
         'ici': 237,
         'bruits': 12,
         'propos': 65,
         'avaient': 347,
         'couru': 12,
         'son': 2473,
         'compte': 64,
         'moment': 482,
         'diocèse': 12,
         'Vrai': 11,
         'ou': 703,
         'faux': 39,
         "qu'on": 797,
         'dit': 1455,
         'hommes': 405,
         'tient': 36,
         'souvent': 104,
         'autant': 54,
         'place': 181,
         'leur': 593,
         'vie': 392,
         'surtout': 55,
         'destinée': 78,
         "qu'ils": 183,
         'font': 137,
         'fils': 99,
         'conseiller': 7,
         'parlement': 7,
         "d'Aix;": 1,
         'noblesse': 7,
         'robe': 64,
         'On': 975,
         'contait': 7,
         'lui': 2824,
         'réservant': 2,
         'hériter': 1,
         'charge': 22,
         "l'avait": 237,
         'marié': 10,
         'fort': 192,
         'bonne': 196,
         'heure': 197,
         'dix': 214,
         'huit': 124,
         'vingt': 214,
         'ans': 361,
         'suivant': 10,
         'usage': 2,
         'assez': 233,
         'répandu': 3,
         'familles': 27,
         'parlementaires': 3,
         'nonobstant': 3,
         'mariage': 37,
         'avait': 3512,
         'disait': 251,
         'on': 1608,
         'beaucoup': 160,
         'parler': 153,
         'Il': 4180,
         'bien': 1070,
         'personne': 251,
         'quoique': 54,
         "d'assez": 8,
         'petite': 304,
         'taille': 42,
         'élégant': 9,
         'gracieux': 9,
         'spirituel;': 1,
         'toute': 560,
         'première': 195,
         'partie': 88,
         'été': 695,
         'donnée': 27,
         'monde': 270,
         'galanteries': 1,
         'révolution': 124,
         'survint': 4,
         'événements': 44,
         'précipitèrent': 5,
         'décimées': 1,
         'chassées': 4,
         'traquées': 1,
         'dispersèrent': 3,
         'dès': 61,
         'premiers': 30,
         'jours': 212,
         'émigra': 2,
         'Italie': 7,
         'Sa': 139,
         'femme': 374,
         'y': 1951,
         'mourut': 8,
         'maladie': 29,
         'poitrine': 66,
         'dont': 441,
         'elle': 1672,
         'atteinte': 4,
         'Ils': 320,
         "n'avaient": 52,
         'point': 613,
         "d'enfants": 17,
         'passa': 78,
         't': 292,
         'ensuite': 30,
         "L'écroulement": 1,
         "l'ancienne": 16,
         'société': 109,
         'française': 43,
         'propre': 96,
         'famille': 94,
         'tragiques': 14,
         'spectacles': 7,
         '93': 21,
         'effrayants': 10,
         'encore': 615,
         'émigrés': 2,
         'voyaient': 19,
         'loin': 144,
         'avec': 1823,
         'grossissement': 7,
         "l'épouvante": 10,
         'firent': 36,
         'ils': 456,
         'germer': 4,
         'idées': 79,
         'renoncement': 6,
         'solitude': 23,
         'Fut': 2,
         'milieu': 151,
         'ces': 1289,
         'distractions': 4,
         'affections': 1,
         'occupaient': 2,
         'subitement': 32,
         'atteint': 20,
         'coups': 114,
         'mystérieux': 74,
         'terribles': 23,
         'viennent': 30,
         'renverser': 6,
         'frappant': 8,
         'coeur': 226,
         "l'homme": 428,
         'catastrophes': 11,
         'publiques': 9,
         "n'ébranleraient": 1,
         'existence': 18,
         'fortune': 50,
         'Nul': 6,
         "n'aurait": 28,
         'pu': 201,
         'dire;': 5,
         'savait': 146,
         "lorsqu'il": 30,
         'revint': 72,
         "d'Italie": 7,
         'prêtre': 62,
         '1804': 1,
         'curé': 56,
         'Brignolles': 1,
         'déjà': 186,
         'vieux': 383,
         'vivait': 41,
         'retraite': 13,
         'profonde': 67,
         'Vers': 45,
         "l'époque": 25,
         'couronnement': 2,
         'affaire': 49,
         'cure': 2,
         'sait': 221,
         'quoi': 227,
         "l'amena": 1,
         'Entre': 21,
         'autres': 245,
         'personnes': 36,
         'puissantes': 5,
         'alla': 123,
         'solliciter': 2,
         'paroissiens': 2,
         'cardinal': 20,
         'Fesch': 3,
         "l'empereur": 43,
         'venu': 122,
         'faire': 713,
         'visite': 35,
         'oncle': 9,
         'digne': 62,
         'attendait': 34,
         "l'antichambre": 15,
         'trouva': 82,
         'passage': 50,
         'majesté': 31,
         'Napoléon': 127,
         'voyant': 50,
         'regardé': 24,
         'certaine': 74,
         'curiosité': 26,
         'retourna': 71,
         'brusquement:': 5,
         'Quel': 76,
         'bonhomme': 95,
         'me': 688,
         'Sire': 5,
         'vous': 2413,
         'regardez': 14,
         'moi': 569,
         'je': 1712,
         'grand': 418,
         'homme': 806,
         'Chacun': 22,
         'profiter': 7,
         "L'empereur": 15,
         'demanda': 128,
         'nom': 292,
         'quelque': 598,
         'temps': 602,
         'fut': 536,
         'surpris': 14,
         "d'apprendre": 1,
         'nommé': 56,
         "Qu'y": 10,
         'vrai': 185,
         'reste': 299,
         'récits': 5,
         'Personne': 51,
         'Peu': 22,
         'connu': 42,
         'avant': 215,
         'devait': 84,
         'subir': 8,
         'sort': 72,
         'nouveau': 101,
         'ville': 181,
         'a': 2002,
         'bouches': 23,
         'parlent': 18,
         'peu': 710,
         'têtes': 54,
         'pensent': 8,
         "quoiqu'il": 14,
         'parce': 138,
         'Mais': 403,
         'auxquels': 17,
         'mêlait': 20,
         "n'étaient": 47,
         'propos;': 1,
         'bruit': 173,
         'mots': 105,
         'paroles;': 2,
         'moins': 313,
         '_palabres_': 1,
         'comme': 1994,
         "l'énergique": 3,
         'langue': 69,
         'midi': 67,
         'Quoi': 82,
         'neuf': 97,
         "d'épiscopat": 1,
         'résidence': 2,
         'tous': 663,
         'racontages': 1,
         'sujets': 6,
         'conversation': 23,
         'occupent': 3,
         'petites': 108,
         'villes': 23,
         'gens': 191,
         'étaient': 552,
         'tombés': 10,
         'oubli': 5,
         'profond': 62,
         "n'eût": 66,
         'osé': 15,
         "s'en": 332,
         'souvenir': 54,
         'accompagné': 14,
         'vieille': 218,
         'fille': 340,
         'mademoiselle': 65,
         'Baptistine': 26,
         'domestique': 10,
         'servante': 36,
         'âge': 53,
         'appelée': 17,
         'madame': 159,
         'Magloire': 75,
         'laquelle': 115,
         'avoir': 302,
         '_la': 35,
         'Curé_': 1,
         'prenait': 74,
         'maintenant': 172,
         'double': 49,
         'titre': 15,
         'chambre': 262,
         'Mademoiselle': 22,
         'longue': 65,
         'pâle': 53,
         'mince': 11,
         'douce;': 2,
         'réalisait': 3,
         "l'idéal": 36,
         "qu'exprime": 2,
         'mot': 268,
         '«respectable': 2,
         ';': 28,
         'car': 141,
         'semble': 79,
         'soit': 194,
         'nécessaire': 46,
         "qu'une": 212,
         'vénérable': 25,
         'Elle': 910,
         "n'avait": 476,
         'jamais': 338,
         'jolie;': 4,
         'suite': 114,
         'saintes': 16,
         'fini': 102,
         'mettre': 87,
         'sorte': 310,
         'blancheur': 22,
         'clarté;': 2,
         'vieillissant': 2,
         'gagné': 26,
         'pourrait': 103,
         'appeler': 43,
         'beauté': 42,
         'bonté': 40,
         'maigreur': 5,
         'jeunesse': 59,
         'devenu': 70,
         'maturité': 1,
         'transparence;': 2,
         'cette': 2050,
         'diaphanéité': 2,
         'laissait': 62,
         'voir': 402,
         "l'ange": 22,
         'âme': 134,
         "n'était": 434,
         'vierge': 23,
         'semblait': 268,
         'faite': 123,
         "d'ombre;": 1,
         'peine': 228,
         'corps': 119,
         'eût': 485,
         'là': 1412,
         'sexe;': 1,
         'matière': 24,
         'contenant': 4,
         'lueur;': 2,
         'grands': 130,
         'yeux': 431,
         'toujours': 485,
         'baissés;': 1,
         'prétexte': 11,
         'terre': 298,
         'blanche': 53,
         'grasse': 6,
         'replète': 1,
         'affairée': 1,
         'haletante': 3,
         'cause': 99,
         'activité': 3,
         "d'abord": 75,
         'asthme': 2,
         'arrivée': 18,
         'installa': 3,
         'palais': 30,
         'épiscopal': 5,
         'honneurs': 3,
         'voulus': 1,
         'décrets': 2,
         'impériaux': 1,
         'classent': 1,
         "l'évêque": 188,
         'immédiatement': 18,
         'maréchal': 16,
         'camp': 15,
         'maire': 170,
         'président': 53,
         'côté': 398,
         'fit': 512,
         'général': 106,
         'préfet': 24,
         "L'installation": 1,
         'terminée': 6,
         'attendit': 15,
         "l'oeuvre": 9,
         'attenant': 1,
         "l'hôpital": 14,
         'vaste': 49,
         'bel': 10,
         'hôtel': 3,
         'bâti': 17,
         'pierre': 123,
         'commencement': 61,
         'siècle': 127,
         'dernier': 94,
         'Henri': 20,
         'Puget': 3,
         'docteur': 7,
         'théologie': 2,
         'faculté': 9,
         'abbé': 9,
         'Simore': 1,
         'lequel': 77,
         '1712': 1,
         'logis': 47,
         'seigneurial': 3,
         'Tout': 376,
         'air': 128,
         'appartements': 3,
         'salons': 12,
         'chambres': 35,
         'cour': 108,
         "d'honneur": 25,
         'large': 64,
         'promenoirs': 1,
         'arcades': 6,
         'selon': 83,
         'mode': 28,
         'florentine': 1,
         'jardins': 23,
         'plantés': 5,
         'magnifiques': 15,
         'arbres': 75,
         'salle': 151,
         'manger': 59,
         'superbe': 11,
         'galerie': 14,
         'rez': 25,
         'chaussée': 51,
         "s'ouvrait": 24,
         'donné': 112,
         'cérémonie': 5,
         '29': 4,
         'juillet': 26,
         '1714': 2,
         'messeigneurs': 2,
         'Brûlart': 1,
         'Genlis': 5,
         'archevêque': 12,
         'prince': 37,
         "d'Embrun": 6,
         'Antoine': 38,
         'Mesgrigny': 1,
         'capucin': 2,
         'Grasse': 4,
         'Philippe': 43,
         'Vendôme': 2,
         'prieur': 2,
         'France': 157,
         'Saint': 356,
         'Honoré': 7,
         'Lérins': 1,
         'Berton': 1,
         'Grillon': 1,
         'baron': 68,
         'Vence': 1,
         'César': 31,
         'Sabran': 2,
         'Forcalquier': 1,
         'seigneur': 8,
         'Glandève': 1,
         'Soanen': 1,
         "l'oratoire": 12,
         'prédicateur': 6,
         'ordinaire': 22,
         'roi': 128,
         'Senez': 2,
         'portraits': 8,
         'sept': 129,
         'révérends': 1,
         'personnages': 6,
         'décoraient': 1,
         'date': 24,
         'mémorable': 5,
         'gravée': 4,
         'lettres': 64,
         "d'or": 50,
         'table': 190,
         'marbre': 29,
         'blanc': 102,
         "L'hôpital": 2,
         'étroite': 32,
         'basse': 88,
         'seul': 188,
         'étage': 38,
         'petit': 383,
         'jardin': 220,
         'Trois': 25,
         'visita': 2,
         'prier': 21,
         'directeur': 7,
         'vouloir': 24,
         'venir': 116,
         'jusque': 47,
         'combien': 16,
         'avez': 185,
         'malades': 21,
         'Vingt': 17,
         'six': 226,
         "C'est": 803,
         "j'avais": 36,
         'compté': 8,
         'lits': 19,
         'reprit': 235,
         'serrés': 2,
         'uns': 88,
         'contre': 270,
         'remarqué': 31,
         'salles': 7,
         "l'air": 210,
         "s'y": 203,
         'renouvelle': 1,
         'difficilement': 7,
         'Et': 724,
         'puis': 471,
         'quand': 398,
         'rayon': 34,
         'soleil': 146,
         'convalescents': 1,
         'disais': 12,
         'épidémies': 1,
         'eu': 291,
         'année': 35,
         'typhus': 3,
         'suette': 1,
         'militaire': 28,
         'cent': 175,
         'quelquefois;': 3,
         'savons': 9,
         'pensée': 164,
         "m'était": 2,
         'venue': 42,
         'voulez': 75,
         'faut': 347,
         'résigner': 2,
         'Cette': 360,
         'garda': 11,
         'silence': 159,
         'tourna': 95,
         'brusquement': 88,
         'vers': 417,
         "l'hôpital:": 2,
         'pensez': 7,
         'tiendrait': 2,
         'rien': 648,
         "s'écria": 108,
         'stupéfait': 19,
         'parcourait': 3,
         'regard': 183,
         'mesures': 6,
         'calculs': 7,
         'parlant': 44,
         'Puis': 190,
         'élevant': 7,
         'voix:': 31,
         'Tenez': 31,
         'monsieur': 483,
         'vais': 116,
         'dire': 620,
         'évidemment': 68,
         'erreur': 16,
         'Vous': 436,
         'êtes': 208,
         'Nous': 260,
         'sommes': 112,
         'trois': 445,
         'dis': 92,
         'mon': 424,
         "j'ai": 283,
         'vôtre': 4,
         'Rendez': 4,
         'ma': 304,
         'lendemain': 85,
         'pauvres': 87,
         'installés': 2,
         'ayant': 149,
         'ruinée': 5,
         'touchait': 23,
         'rente': 15,
         'viagère': 1,
         'cents': 133,
         'presbytère': 7,
         'suffisait': 29,
         'personnelle': 8,
         'recevait': 12,
         "l'état": 46,
         'traitement': 5,
         'mille': 199,
         'vint': 83,
         'loger': 15,
         'détermina': 5,
         "l'emploi": 5,
         'somme': 62,
         'fois': 490,
         'toutes': 514,
         'suivante': 1,
         'transcrivons': 2,
         'note': 18,
         'écrite': 20,
         'main': 434,
         '_Note': 2,
         'régler': 4,
         'dépenses': 9,
         '_': 325,
         '_Pour': 19,
         'séminaire:': 1,
         'livres_': 23,
         '_Congrégation': 3,
         'mission:': 1,
         'lazaristes': 3,
         'Montdidier:': 1,
         '_Séminaire': 1,
         'missions': 1,
         'étrangères': 4,
         'Paris:': 1,
         'Esprit:': 1,
         'cinquante': 65,
         '_Établissements': 1,
         'religieux': 14,
         'Terre': 1,
         'Sainte:': 1,
         '_Sociétés': 1,
         'charité': 35,
         'maternelle:': 1,
         '_En': 8,
         'sus': 2,
         'celle': 119,
         "d'Arles:": 1,
         '_OEuvre': 2,
         "l'amélioration": 4,
         'prisons:': 1,
         'soulagement': 4,
         'délivrance': 9,
         'prisonniers:': 1,
         'libérer': 1,
         'pères': 20,
         'prisonniers': 13,
         'dettes:': 1,
         '_Supplément': 1,
         'maîtres': 20,
         "d'école": 12,
         'diocèse:': 1,
         '_Grenier': 1,
         "d'abondance": 2,
         'Hautes': 2,
         'Alpes:': 1,
         'dames': 18,
         'Manosque': 1,
         'Sisteron': 1,
         "l'enseignement": 7,
         'gratuit': 3,
         'filles': 150,
         'indigentes:': 1,
         'pauvres:': 1,
         '_Ma': 5,
         'personnelle:': 1,
         'Total:': 3,
         '_quinze': 2,
         'Pendant': 58,
         'occupa': 2,
         'changea': 6,
         'presque': 368,
         'cet': 555,
         'arrangement': 4,
         'appelait': 63,
         'cela': 898,
         'voit': 118,
         '_avoir': 1,
         'réglé': 6,
         'maison_': 2,
         'Cet': 122,
         'accepté': 13,
         'soumission': 4,
         'absolue': 12,
         'Pour': 148,
         'sainte': 35,
         'ami': 28,
         'nature': 108,
         'supérieur': 15,
         "l'église": 52,
         "l'aimait": 11,
         'vénérait': 5,
         'simplement': 41,
         'Quand': 273,
         'parlait': 122,
         "s'inclinait;": 1,
         'agissait': 3,
         'adhérait': 1,
         'seule': 104,
         'murmura': 36,
         "l'a": 72,
         'remarquer': 28,
         ...})
In [176]:
counter.most_common(50)
Out[176]:
[('de', 21683),
 ('la', 14553),
 ('et', 13148),
 ('le', 11056),
 ('à', 9608),
 ('les', 7103),
 ('il', 6172),
 ('un', 5973),
 ('que', 5535),
 ('qui', 5499),
 ('dans', 5272),
 ('des', 5042),
 ('une', 4452),
 ('en', 4359),
 ('Il', 4180),
 ('se', 4049),
 ('ce', 4007),
 ('du', 4006),
 ('pas', 3797),
 ('ne', 3682),
 ('avait', 3512),
 ('était', 3509),
 ('lui', 2824),
 ('au', 2628),
 ('sur', 2589),
 ('son', 2473),
 ('est', 2459),
 ('pour', 2441),
 ('plus', 2413),
 ('vous', 2413),
 ('sa', 2213),
 ('par', 2058),
 ('cette', 2050),
 ('a', 2002),
 ('comme', 1994),
 ('y', 1951),
 ("qu'il", 1904),
 ('avec', 1823),
 ('tout', 1728),
 ('je', 1712),
 ('Le', 1688),
 ('elle', 1672),
 ('on', 1608),
 ('dit', 1455),
 ('deux', 1416),
 ('là', 1412),
 ('La', 1392),
 ('Marius', 1343),
 ("c'est", 1312),
 ('ces', 1289)]

You can save your computation with pickle.

  • pickle is a way of saving almost anything with Python.
  • It serializes the object in a binary format, and is usually the simplest and fastest way to go.
In [177]:
import pickle as pkl

# Let's save it
with open('miserable_word_counts.pkl', 'wb') as f:
    pkl.dump(counter, f)

# And read it again
with open('miserable_word_counts.pkl', 'rb') as f:
    counter = pkl.load(f)
In [178]:
counter.most_common(10)
Out[178]:
[('de', 21683),
 ('la', 14553),
 ('et', 13148),
 ('le', 11056),
 ('à', 9608),
 ('les', 7103),
 ('il', 6172),
 ('un', 5973),
 ('que', 5535),
 ('qui', 5499)]

8. Defining functions

You must use function to order and reuse code

8.1. Function definition

Function blocks must be indented as other control-flow blocks.

In [179]:
def test():
    return 'in test function'

test()
Out[179]:
'in test function'

8.2. Return statement

Functions can optionally return values. By default, functions return None.

The syntax to define a function:

  • the def keyword;
  • is followed by the function's name, then
  • the arguments of the function are given between parentheses followed by a colon
  • the function body;
  • and return object for optionally returning values.
In [180]:
def f(x):
    return x + 10
f(20)
Out[180]:
30

A function that returns several elements returns a tuple

In [181]:
def f(x):
    return x + 1, x + 4

f(5)
Out[181]:
(6, 9)
In [182]:
type(f(5))
Out[182]:
tuple

8.3. Parameters

Mandatory parameters (positional arguments)

In [183]:
def double_it(x):
    return x * 2

double_it(2)
Out[183]:
4
In [184]:
double_it()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-184-7e40baa67e25> in <module>
----> 1 double_it()

TypeError: double_it() missing 1 required positional argument: 'x'

Optimal parameters

In [185]:
def double_it(x=2):
    return x * 2

double_it()
Out[185]:
4
In [186]:
double_it(3)
Out[186]:
6
In [187]:
def f(x, y=2, z=10):
    print(x, '+', y, '+', z, '=', x + y + z)
In [188]:
f(5)
5 + 2 + 10 = 17
In [189]:
f(5, -2)
5 + -2 + 10 = 13
In [190]:
f(5, -2, 8)
5 + -2 + 8 = 11
In [191]:
f(z=5, x=-2, y=8)
-2 + 8 + 5 = 11

Argument unpacking and keyword argument unpacking: the * and ** notation

You can do stuff like this, using unpacking * notation

In [192]:
a, *b, c = 1, 2, 3, 4, 5
a, b, c
Out[192]:
(1, [2, 3, 4], 5)

Back to function f you can unpack a tuple as positional arguments

In [193]:
tt = (1, 2, 3)
f(*tt)
1 + 2 + 3 = 6
In [194]:
dd = {'y': 10, 'z': -5}
In [195]:
f(3, **dd)
3 + 10 + -5 = 8
In [196]:
def g(x, z, y, t=1, u=2):
    print(x, '+', y, '+', z, '+', t, '+', 
          u, '=', x + y + z + t + u)
In [197]:
tt = (1, -4, 2)
dd = {'t': 10, 'u': -5}
g(*tt, **dd)
1 + 2 + -4 + 10 + -5 = 4

Important. The prototype of all functions in Python is

In [198]:
def f(*args, **kwargs):
    print('args=', args)
    print('kwargs=', kwargs)

f(1, 2, 'truc', lastname='gaiffas', firstname='stephane')
args= (1, 2, 'truc')
kwargs= {'lastname': 'gaiffas', 'firstname': 'stephane'}
  • Uses * for argument unpacking and ** for keyword argument unpacking
  • The names args and kwargs are a convention, not mandatory
  • (but you are fired if you name these arguments otherwise)
In [199]:
# How to get fired
def f(*aaa, **bbb):
    print('args=', aaa)
    print('kwargs=', bbb)
f(1, 2, 'truc', lastname='gaiffas', firstname='stephane')    
args= (1, 2, 'truc')
kwargs= {'lastname': 'gaiffas', 'firstname': 'stephane'}

Remark. A function is a regular an object... you can add attributes on it !

In [200]:
f.truc = 4
In [201]:
f(1, 3)
args= (1, 3)
kwargs= {}
In [202]:
f(3, -2, y='truc')
args= (3, -2)
kwargs= {'y': 'truc'}

9. Object-oriented programming (OOP)

Python supports object-oriented programming (OOP). The goals of OOP are:

  • to organize the code, and
  • to re-use code in similar contexts.

Here is a small example: we create a Student class, which is an object gathering several custom functions (called methods) and variables (called attributes).

In [203]:
class Student(object):

    def __init__(self, name, birthyear, major='computer science'):
        self.name = name
        self.birthyear = birthyear
        self.major = major

    def __repr__(self):
        return "Student(name='{name}', birthyear={birthyear}, major='{major}')"\
                .format(name=self.name, birthyear=self.birthyear, major=self.major)

anna = Student('anna', 1987)
anna
Out[203]:
Student(name='anna', birthyear=1987, major='computer science')

The __repr__ is what we call a 'magic method' in Python, that allows to display an object as a string easily. There is a very large number of such magic method...

Exercice

Add a age method to the Student class that computes the age of the student.

  • You can (and should) use the datetime module.
  • Since we only know about the birth year, let's assume that the day of the birth is January, 1st.
In [204]:
from datetime import datetime

class Student(object):

    def __init__(self, name, birthyear, major='computer science'):
        self.name = name
        self.birthyear = birthyear
        self.major = major

    def __repr__(self):
        return "Student(name='{name}', birthyear={birthyear}, major='{major}')"\
                .format(name=self.name, birthyear=self.birthyear, major=self.major)

    def age(self):
        return datetime.now().year - self.birthyear
        
anna = Student('anna', 1987)
anna.age()
Out[204]:
32

Properties

We can make methods look like attributes using properties, as shown below

In [205]:
class Student(object):

    def __init__(self, name, birthyear, major='computer science'):
        self.name = name
        self.birthyear = birthyear
        self.major = major

    def __repr__(self):
        return "Student(name='{name}', birthyear={birthyear}, major='{major}')"\
                .format(name=self.name, birthyear=self.birthyear, major=self.major)

    @property
    def age(self):
        return datetime.now().year - self.birthyear
        
anna = Student('anna', 1987)
anna.age
Out[205]:
32

Inheritance

A MasterStudent is a Student with a new extra mandatory internship attribute

In [206]:
class MasterStudent(Student):
    
    def __init__(self, name, age, internship, major='computer science'):
        Student.__init__(self, name, age, major)
        self.internship = internship

    def __repr__(self):
        return "MasterStudent(name='{name}', internship='{internship}'" \
               ", birthyear={birthyear}, major='{major}')"\
                .format(name=self.name, internship=self.internship,
                        birthyear=self.birthyear, major=self.major)
    
MasterStudent('djalil', 22, 'pwc')
Out[206]:
MasterStudent(name='djalil', internship='pwc', birthyear=22, major='computer science')

Data classes

Since Python 3.7 you can use a dataclass for this

Does a lot of work for you (produces the __repr__ among many other things for you)

In [207]:
from dataclasses import dataclass

@dataclass
class Student(object):
    name: str
    birthyear: int
    major: str = 'computer science'

    @property
    def age(self):
        return datetime.now().year - self.birthyear
        
anna = Student('anna', 1987)
anna
Out[207]:
Student(name='anna', birthyear=1987, major='computer science')
In [208]:
print(anna.age)
32

10. Most common mistakes

  • Now, you already know a little bit about Python

  • But you still have soooooooooo much to learn...

  • In particular, tons of other modules

  • we'll learn a about the ones useful for data-science and machine learning

  • Let us wrap this up with the most common mistakes with Python

Best way to learn and practice:

10.1 Using a mutable value as a default value

In [209]:
def foo(bar=[]):
    bar.append('oops')
    return bar

print(foo())
print(foo())
print(foo())

print('-' * 8)
print(foo(['Ah ah']))
print(foo([]))
['oops']
['oops', 'oops']
['oops', 'oops', 'oops']
--------
['Ah ah', 'oops']
['oops']
  • The default value for a function argument is evaluated once, when the function is defined
  • the bar argument is initialized to its default (i.e., an empty list) only when foo() is first defined
  • successive calls to foo() (with no a bar argument specified) use the same list!

One should use instead

In [210]:
def foo(bar=None):
    if bar is None:
        bar = []
    bar.append('oops')
    return bar

print(foo())
print(foo())
print(foo())
print(foo(['OK']))
['oops']
['oops']
['oops']
['OK', 'oops']

No problem with immutable types

In [211]:
def foo(bar=()):
    bar += ('oops',)
    return bar

print(foo())
print(foo())
print(foo())
('oops',)
('oops',)
('oops',)

10.2. Class attributes VS object attributes

In [212]:
class A(object):
    x = 1

    def __init__(self):
        self.y = 2

class B(A):
    def __init__(self):
        super().__init__()

class C(A):
    def __init__(self):
        super().__init__()

a, b, c = A(), B(), C()
In [213]:
print(a.x, b.x, c.x)
print(a.y, b.y, c.y)
1 1 1
2 2 2
In [214]:
a.y = 3
print(a.y, b.y, c.y)
3 2 2
In [215]:
a.x = 3  # Adds a new attribute named x in object a
print(a.x, b.x, c.x)
3 1 1
In [216]:
A.x = 3 # Changes the class attribute x of class A
print(a.x, b.x, c.x)
3 3 3
  • Attribute x is not an attribute of b nor c
  • It is also not a class attribute of classes B and C
  • So, it is is looked up in the base class A, which contains a class attribute x

Classes and objects contain a hidden dict to store their attributes, and are accessed following a method resolution order (MRO)

In [217]:
a.__dict__, b.__dict__, c.__dict__
Out[217]:
({'y': 3, 'x': 3}, {'y': 2}, {'y': 2})
In [218]:
A.__dict__, B.__dict__, C.__dict__
Out[218]:
(mappingproxy({'__module__': '__main__',
               'x': 3,
               '__init__': <function __main__.A.__init__(self)>,
               '__dict__': <attribute '__dict__' of 'A' objects>,
               '__weakref__': <attribute '__weakref__' of 'A' objects>,
               '__doc__': None}),
 mappingproxy({'__module__': '__main__',
               '__init__': <function __main__.B.__init__(self)>,
               '__doc__': None}),
 mappingproxy({'__module__': '__main__',
               '__init__': <function __main__.C.__init__(self)>,
               '__doc__': None}))

This can lead to nasty errors when using class attributes: learn more about this

10.3. Python scope rules

In [219]:
ints = [1]

def foo1():
    ints.append(2)
    return ints

def foo2():
    ints += [2]
    return ints
In [220]:
foo1()
Out[220]:
[1, 2]
In [221]:
foo2()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-221-33ee749b93dd> in <module>
----> 1 foo2()

<ipython-input-219-ff81ed218d0a> in foo2()
      6 
      7 def foo2():
----> 8     ints += [2]
      9     return ints

UnboundLocalError: local variable 'ints' referenced before assignment

What the hell ?

  • An assignment to a variable in a scope assumes that the variable is local to that scope
  • and shadows any similarly named variable in any outer scope
ints += [2]

means

ints = ints + [2]

which is an assigment: ints must be defined in the local scope, but it is not, while

ints.append(2)

is not an assignemnt

10.4. Modify a list while iterating over it

In [222]:
odd = lambda x: bool(x % 2)
numbers = list(range(10))

for i in range(len(numbers)):
    if odd(numbers[i]):
        del numbers[i]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-222-e93d1d610975> in <module>
      3 
      4 for i in range(len(numbers)):
----> 5     if odd(numbers[i]):
      6         del numbers[i]

IndexError: list index out of range

Typically an example where one should use a list comprehension

In [223]:
[number for number in numbers if not odd(number)]
Out[223]:
[0, 2, 4, 6, 8]

10.5. No docstrings

Accept to spend time to write clean docstrings (my favourite is the numpydoc style)

In [224]:
def create_student(name, age, address, major='computer science'):
    """Add a student in the databases
    
    Parameters
    ----------
    name: `str`
        Name of the student
    
    age: `int`
        Age of the student
    
    address: `str`
        Address of the student
    
    major: `str`, default='computer science'
        The major chosen by the student
    
    Returns
    -------
    output: `Student`
        A fresh student
    """
    pass

10.6. Not using available methods and/or the simplest solution

In [225]:
dd = {'stephane': 1234, 'gael': 4567, 'gontran': 891011}

# Bad
for key in dd.keys():
    print(key, dd[key])

print('-' * 8)

# Good
for key, value in dd.items():
    print(key, value)
stephane 1234
gael 4567
gontran 891011
--------
stephane 1234
gael 4567
gontran 891011
In [226]:
colors = ['black', 'yellow', 'brown', 'red', 'pink']

# Bad
for i in range(len(colors)):
    print(i, colors[i])

print('-' * 8)

# Good
for i, color in enumerate(colors):
    print(i, color)
0 black
1 yellow
2 brown
3 red
4 pink
--------
0 black
1 yellow
2 brown
3 red
4 pink

10.7. Not using the standard library

While it's always better than a hand-made solution

In [227]:
list1 = [1, 2]
list2 = [3, 4]
list3 = [5, 6, 7]

for a in list1:
    for b in list2:
        for c in list3:
            print(a, b, c)
1 3 5
1 3 6
1 3 7
1 4 5
1 4 6
1 4 7
2 3 5
2 3 6
2 3 7
2 4 5
2 4 6
2 4 7
In [228]:
from itertools import product

for a, b, c in product(list1, list2, list3):
    print(a, b, c)
1 3 5
1 3 6
1 3 7
1 4 5
1 4 6
1 4 7
2 3 5
2 3 6
2 3 7
2 4 5
2 4 6
2 4 7

That's it for now !