Explorar el Código

session 08

master
Dimitri Merejkowsky hace 5 años
padre
commit
9d885de32e
Se han modificado 4 ficheros con 439 adiciones y 11 borrados
  1. +5
    -9
      notes.md
  2. +1
    -1
      sessions/Makefile
  3. +432
    -0
      sessions/python-08.md
  4. +1
    -1
      sources/marvel/marvel_03.py

+ 5
- 9
notes.md Ver fichero

@@ -5,12 +5,12 @@ Ce fichier contient diverses notes utiles à la préparation des futurs cours.
## bits

* attributes on functions (you never know)
* __call__, functors
* `__call__`, functors
* scopes, closures, global, nonlocal
* no overlaod in python
* several __init__ ? Nope, alternative constructors
* class attributes vs instances attributes, @classmethod, properties
https://code-maven.com/slides/python-programming/class-methods-alternative-constructor
* several `__init__` ? Nope, alternative constructors
* https://code-maven.com/slides/python-programming/class-methods-alternative-constructor
* properties on classes
* stable sorts
* dict: setdefault
* listes: pop prend un argument
@@ -25,15 +25,11 @@ Ce fichier contient diverses notes utiles à la préparation des futurs cours.
* style: trailing white space, editor configuration,
* [formatage de strings](fragments/format.md)
* liste par compréhension et filtres
* `help()`, doc en ligne (également en français)

* `help()`
* packages, libraries tierces
* requests, HTTP protocol (headers, methodes, urls, anchors, links ...)

* décorateurs
* classes
* héritage
* super()

* Données binaires, encodage (binaire, ASCII, hexadécimal, unicode ...)



+ 1
- 1
sessions/Makefile Ver fichero

@@ -1,4 +1,4 @@
all: python-07.pdf
all: python-08.pdf

%.pdf: %.md
pandoc -t beamer $< -o $@

+ 432
- 0
sessions/python-08.md Ver fichero

@@ -0,0 +1,432 @@
% Programmation avec Python (chapitre 8)
% Dimitri Merejkowsky

\center \huge Rappels

# Définition et utilisation d'une classe

```python
class Counter:
def __init__(self):
self.count = 0

def increment(self, times=1):
self.count += times

>>> counter = Counter()
>>> counter.increment(times=2)
>>> counter.count
2
>>> counter.increment()
>>> counter.count
3
```

# Vocabulaire

* `counter` est une *instance* de la *classe* `Counter`
* `increment` est une *méthode d'instance*
* `count` est un *attribut d'instance*
* `__init__` est un *constructeur*

# Méthodes et attributs de classe

```python
class Car:
total_number_of_cars = 0

def __init__(self, color="black"):
self.color = color
Car.total_number_of_cars += 1

@classmethod
def print_number_of_cars(cls):
print(cls.total_number_of_cars,
"cars have been made")

>>> ford = Car()
>>> ferrari = Car(color="red")
>>> Car.print_number_of_cars()
2 cars have been made
```

#

\center \huge Composition

# Composition

* Quand on met une classe dans une autre.

* Par exemple, le constructeur de la classe A va prendre en paramètre une
instance de la classe B.

* Introduit un *couplage* entre les classes A et B.

# Example

```python
class Authorization:
def __init__(self, credentials_file):
...
self.password = ...

class Client:
url = "https://exmple.com"
def __init__(self, auth)
self.auth = auth

def make_request(self):
password = self.auth.password
requests.get(url, password=password)
```

# Couplage 1

Il faut construire une instance d'`Authorization` pour pouvoir
construire une instance de `Client`


```python
>>> auth = Authorization("credentials.txt")
>>> client = Client(auth)
>>> client.make_request()
```

# Couplage 2

Si jamais l'atttribut `password` dans la classe `Authorization` change,
le code dans `Client.make_request()` devra changer aussi.

```python
class Authorization:
...
self.password = ...


class Client:
...

def make_request(self):
password = self.auth.password
...
```

# Conclusion

Prenez le temps d'étudier les relations entre les différentes classes!

Souvent un simple schéma suffira.

#

\center \huge Héritage

# Petit détour

Qu'est-ce qui ne va pas dans ce code?

```python
def faire_le_cafe():
mettre_cafe_dans_tasse()
allumer_bouilloire()
attendre_que_ca_bouille()
verser_dans_tasse()
melanger()

def faire_le_the():
mettre_the_dans_tasse()
allumer_bouilloire()
attendre_que_ca_bouille()
verser_dans_tasse()
laisser_infuser()
```

# Duplication

* Les lignes de `allumer_bouilloire()` à `verser_dans_tasse()` sont les mêmes
* Le code est plus long
* Si jamais la procédure pour faire chauffer l'eau change, il faudra changer
le code a deux endroits différents

# Solution: extraire une fonction

```python
def faire_chauffer_l_eau():
allumer_bouilloire()
attendre_que_ca_bouille()

def faire_le_cafe():
mettre_cafe_dans_tasse()
faire_chauffer_l_eau()
verser_dans_tasse()
melanger()

def faire_le_the():
mettre_the_dans_tasse()
faire_chauffer_l_eau()
verser_dans_tasse()
laisser_infuser()
```

# Facile à changer

Si maintenant il faut débrancher le grille-pain avant de pouvoir faire chauffer l'eau,
on a juste à changer une fonction:

```python
def faire_chauffer_l_eau():
debrancher_grille_pain()
brancher_bouilloire()
allumer_bouilloire()
attendre_que_ca_bouille()
```

# Note

Notez qu'on a *laissé* la ligne `verser_dans_tasse()` dupliquée.

C'est une duplication par *coïncidence*.

Et ça nous permet de faire ça:

```python
def faire_le_the():
faire_chauffer_l_eau()
mettre_the_dans_tasse()
verser_dans_tasse()
laisser_infuser()
```

# Conclusion

Encore une fois, réfléchissez avant d'agir!

#

\center \huge Héritage

# Un autre type de relation entre classes

Si la composition est une relation "has-a", l'héritage décrit
une relation "is-a".

# Composition

```python
class Dog:
pass

class Person:
def __init__(self, pet=None):
self.pet = pet

>>> nestor = Dog()
>>> john = Person(pet=nestor)
>>> jonh.pet
nestor
```

`John` *a* un animal.


# Héritage

```python
class Animal:
pass


class Dog(Animal):
pass


class Cat(Animal):
pass

```

`Dog` et `Cat` *sont* des animaux.

# Vocabulaire

```python
class A:
...

class B(A):
...
```

* A est la classe *parente* de B.
* B *hérite* de A.
* B est une classe *file* de A.


# Utilisation

* Si une méthode n'est pas trouvée dans la classe courante,
Python ira la chercher dans la classe parente

```python
class A:
def method_in_a(self):
print("in a")

class B(A):
def method_in_b(self):
print("in b")


>>> b = B()
>>> b.method_in_b()
'in b' # comme d'habitude
>>> b.method_in_a()
'in a'
```

# Ordre de résolution

S'il y a plusieurs classes parentes, Python les remonte toutes:

```python
class A:
def method_in_a(self):
print("in a")

class B(A):
def method_in_b(self):
...

class C(B):
def method_in_c(self):
...

>>> c = C()
>>> c.method_in_a()
'in a'
```

# Avec `__init__`

La résolution fonctionne pour toutes les méthodes, y compris `__init__`

```python
class A:
def __init__(self):
print("Building parent")

class B(A):
...

>>> b = B()
Building parent
```

# Attributs

Même méchanisme pour les attributs:

```python
class A:
def __init__(self):
self.a_attribute = 42

class B(A):
...

>>> b = B()
>>> b.a_attribute
42
```

# Overriding

On peut aussi *écraser* la méthode du parent dans l'enfant:

```python
class A:
def my_method(self):
print("method in A")

class B(A):
def my_method(self):
print("method in B")


>>> b = B()
>>> b.my_method()
"method in B"
```

# super()

Demande à chercher une méthode dans la classe parente

```python
class A:
def a_method(self):
print("method in A")

class B(A):
def b_method(self):
super().a_method()
print("method in B")

>>> b = B()
>>> b.b_method()
method in A
method in B
```

# `super` et `__init__`

Erreur très courante:

```python
class A:
def __init__(self):
self.a_attribute = "foo"

class B(A):
def __init__(self):
self.b_attribute = 42

>>> b = B()
>>> b.b_attribute
42
>>> b.a_attribute
AttributeError
```

On a écrasé `A.__init__`!

# `super` et `__init__`

```python
class A:
def __init__(self):
self.a_attribute = "foo"

class B(A):
def __init__(self):
super().__init__()
self.b_attribute = 42

>>> b = B()
>>> b.b_attribute
42
>>> b.a_attribute
"foo" # OK
```

#


\center \huge Atelier


# Objectif

*Une autre vision de l'héritage*: on va rajouter une fonctionnalité dans
notre script marvel, puis on va réduire le code dupliqué.

+ 1
- 1
sources/marvel/marvel_03.py Ver fichero

@@ -36,7 +36,7 @@ class Client:
def __init__(self, auth):
self.auth = auth

def get_character_description(self,name):
def get_character_description(self, name):
params = self.auth.generate_params()
params["name"] = name
url = Client.base_url + "/characters"