You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

python-08.md 6.9 KiB

5 年之前
5 年之前
5 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. % Programmation avec Python (chapitre 8)
  2. % Dimitri Merejkowsky
  3. \center \huge Rappels
  4. # Définition et utilisation d'une classe
  5. ```python
  6. class Counter:
  7. def __init__(self):
  8. self.count = 0
  9. def increment(self, times=1):
  10. self.count += times
  11. >>> counter = Counter()
  12. >>> counter.increment(times=2)
  13. >>> counter.count
  14. 2
  15. >>> counter.increment()
  16. >>> counter.count
  17. 3
  18. ```
  19. # Vocabulaire
  20. * `counter` est une *instance* de la *classe* `Counter`
  21. * `increment` est une *méthode d'instance*
  22. * `count` est un *attribut d'instance*
  23. * `__init__` est un *constructeur*
  24. # Méthodes et attributs de classe
  25. ```python
  26. class Car:
  27. total_number_of_cars = 0
  28. def __init__(self, color="black"):
  29. self.color = color
  30. Car.total_number_of_cars += 1
  31. @classmethod
  32. def print_number_of_cars(cls):
  33. print(cls.total_number_of_cars,
  34. "cars have been made")
  35. >>> ford = Car()
  36. >>> ferrari = Car(color="red")
  37. >>> Car.print_number_of_cars()
  38. 2 cars have been made
  39. ```
  40. #
  41. \center \huge Composition
  42. # Composition
  43. * Quand on met une classe dans une autre.
  44. * Par exemple, le constructeur de la classe A va prendre en paramètre une
  45. instance de la classe B.
  46. * Introduit un *couplage* entre les classes A et B.
  47. # Example
  48. ```python
  49. class Authorization:
  50. def __init__(self, credentials_file):
  51. ...
  52. self.password = ...
  53. class Client:
  54. url = "https://exmple.com"
  55. def __init__(self, auth)
  56. self.auth = auth
  57. def make_request(self):
  58. password = self.auth.password
  59. requests.get(url, password=password)
  60. ```
  61. # Couplage 1
  62. Il faut construire une instance d'`Authorization` pour pouvoir
  63. construire une instance de `Client`
  64. ```python
  65. >>> auth = Authorization("credentials.txt")
  66. >>> client = Client(auth)
  67. >>> client.make_request()
  68. ```
  69. # Couplage 2
  70. Si jamais l'atttribut `password` dans la classe `Authorization` change,
  71. le code dans `Client.make_request()` devra changer aussi.
  72. ```python
  73. class Authorization:
  74. ...
  75. self.password = ...
  76. class Client:
  77. ...
  78. def make_request(self):
  79. password = self.auth.password
  80. ...
  81. ```
  82. # Conclusion
  83. Prenez le temps d'étudier les relations entre les différentes classes!
  84. Souvent un simple schéma suffira.
  85. #
  86. \center \huge Héritage
  87. # Petit détour
  88. Qu'est-ce qui ne va pas dans ce code?
  89. ```python
  90. def faire_le_cafe():
  91. mettre_cafe_dans_tasse()
  92. allumer_bouilloire()
  93. attendre_que_ca_bouille()
  94. verser_dans_tasse()
  95. melanger()
  96. def faire_le_the():
  97. mettre_the_dans_tasse()
  98. allumer_bouilloire()
  99. attendre_que_ca_bouille()
  100. verser_dans_tasse()
  101. laisser_infuser()
  102. ```
  103. # Duplication
  104. * Les lignes de `allumer_bouilloire()` à `verser_dans_tasse()` sont les mêmes
  105. * Le code est plus long
  106. * Si jamais la procédure pour faire chauffer l'eau change, il faudra changer
  107. le code a deux endroits différents
  108. # Solution: extraire une fonction
  109. ```python
  110. def faire_chauffer_l_eau():
  111. allumer_bouilloire()
  112. attendre_que_ca_bouille()
  113. def faire_le_cafe():
  114. mettre_cafe_dans_tasse()
  115. faire_chauffer_l_eau()
  116. verser_dans_tasse()
  117. melanger()
  118. def faire_le_the():
  119. mettre_the_dans_tasse()
  120. faire_chauffer_l_eau()
  121. verser_dans_tasse()
  122. laisser_infuser()
  123. ```
  124. # Facile à changer
  125. Si maintenant il faut débrancher le grille-pain avant de pouvoir faire chauffer l'eau,
  126. on a juste à changer une fonction:
  127. ```python
  128. def faire_chauffer_l_eau():
  129. debrancher_grille_pain()
  130. brancher_bouilloire()
  131. allumer_bouilloire()
  132. attendre_que_ca_bouille()
  133. ```
  134. # Note
  135. Notez qu'on a *laissé* la ligne `verser_dans_tasse()` dupliquée.
  136. C'est une duplication par *coïncidence*.
  137. Et ça nous permet de faire ça:
  138. ```python
  139. def faire_le_the():
  140. faire_chauffer_l_eau()
  141. mettre_the_dans_tasse()
  142. verser_dans_tasse()
  143. laisser_infuser()
  144. ```
  145. # Conclusion
  146. Encore une fois, réfléchissez avant d'agir!
  147. #
  148. \center \huge Héritage
  149. # Un autre type de relation entre classes
  150. Si la composition est une relation "has-a", l'héritage décrit
  151. une relation "is-a".
  152. # Composition
  153. ```python
  154. class Dog:
  155. pass
  156. class Person:
  157. def __init__(self, pet=None):
  158. self.pet = pet
  159. >>> nestor = Dog()
  160. >>> john = Person(pet=nestor)
  161. >>> john.pet
  162. nestor
  163. ```
  164. `John` *a* un animal.
  165. # Héritage
  166. ```python
  167. class Animal:
  168. pass
  169. class Dog(Animal):
  170. pass
  171. class Cat(Animal):
  172. pass
  173. ```
  174. `Dog` et `Cat` *sont* des animaux.
  175. # Vocabulaire
  176. ```python
  177. class A:
  178. ...
  179. class B(A):
  180. ...
  181. ```
  182. * A est la classe *parente* de B.
  183. * B *hérite* de A.
  184. * B est une classe *fille* de A.
  185. # Utilisation
  186. * Si une méthode n'est pas trouvée dans la classe courante,
  187. Python ira la chercher dans la classe parente
  188. ```python
  189. class A:
  190. def method_in_a(self):
  191. print("in a")
  192. class B(A):
  193. def method_in_b(self):
  194. print("in b")
  195. >>> b = B()
  196. >>> b.method_in_b()
  197. 'in b' # comme d'habitude
  198. >>> b.method_in_a()
  199. 'in a'
  200. ```
  201. # Ordre de résolution
  202. S'il y a plusieurs classes parentes, Python les remonte toutes:
  203. ```python
  204. class A:
  205. def method_in_a(self):
  206. print("in a")
  207. class B(A):
  208. def method_in_b(self):
  209. ...
  210. class C(B):
  211. def method_in_c(self):
  212. ...
  213. >>> c = C()
  214. >>> c.method_in_a()
  215. 'in a'
  216. ```
  217. # Avec `__init__`
  218. La résolution fonctionne pour toutes les méthodes, y compris `__init__`
  219. ```python
  220. class A:
  221. def __init__(self):
  222. print("Building parent")
  223. class B(A):
  224. ...
  225. >>> b = B()
  226. Building parent
  227. ```
  228. # Attributs
  229. Même méchanisme pour les attributs:
  230. ```python
  231. class A:
  232. def __init__(self):
  233. self.a_attribute = 42
  234. class B(A):
  235. ...
  236. >>> b = B()
  237. >>> b.a_attribute
  238. 42
  239. ```
  240. # Overriding
  241. On peut aussi *écraser* la méthode du parent dans l'enfant:
  242. ```python
  243. class A:
  244. def my_method(self):
  245. print("method in A")
  246. class B(A):
  247. def my_method(self):
  248. print("method in B")
  249. >>> b = B()
  250. >>> b.my_method()
  251. "method in B"
  252. ```
  253. # super()
  254. Demande à chercher une méthode dans la classe parente
  255. ```python
  256. class A:
  257. def a_method(self):
  258. print("method in A")
  259. class B(A):
  260. def b_method(self):
  261. super().a_method()
  262. print("method in B")
  263. >>> b = B()
  264. >>> b.b_method()
  265. method in A
  266. method in B
  267. ```
  268. # `super` et `__init__`
  269. Erreur très courante:
  270. ```python
  271. class A:
  272. def __init__(self):
  273. self.a_attribute = "foo"
  274. class B(A):
  275. def __init__(self):
  276. self.b_attribute = 42
  277. >>> b = B()
  278. >>> b.b_attribute
  279. 42
  280. >>> b.a_attribute
  281. AttributeError
  282. ```
  283. On a écrasé `A.__init__`!
  284. # `super` et `__init__`
  285. ```python
  286. class A:
  287. def __init__(self):
  288. self.a_attribute = "foo"
  289. class B(A):
  290. def __init__(self):
  291. super().__init__()
  292. self.b_attribute = 42
  293. >>> b = B()
  294. >>> b.b_attribute
  295. 42
  296. >>> b.a_attribute
  297. "foo" # OK
  298. ```
  299. #
  300. \center \huge Atelier
  301. # Objectif
  302. *Une autre vision de l'héritage*: on va rajouter une fonctionnalité dans
  303. notre script marvel, puis on va réduire le code dupliqué.
  304. # Résultats
  305. * Départ: https://github.com/E2L/cours-python/blob/master/sources/marvel/marvel_04.py
  306. \vfill
  307. * Arrivée: prochainement sur `git.e2li.org` :)