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-06.md 11 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. % Programmation avec Python (chapitre 6)
  2. % Dimitri Merejkowsky
  3. #
  4. \center \huge Orienté objet et classes
  5. # Un petit détour
  6. Un nouveau built-in: `id()`
  7. L'adresse de l'objet pointé par la variable:
  8. ```python
  9. >>> a = 42532
  10. >>> id(a)
  11. 94295009035968
  12. >>> b = a
  13. >>> id(b) # même objet
  14. 94295009035968
  15. >>> c = 42532 # objet différent
  16. >>> id(c)
  17. 140400601470800
  18. ```
  19. Notez bien les deux objets différents (le fait que
  20. l'objet *pointé* ait la même valeur n'a pas d'importance)
  21. # Paradigmes
  22. Une façon d'envisager le code.
  23. Pour l'instant on n'a vu que le paradigme *procédural* (ou impératif).
  24. Il y en a plein d'autres! (*fonctionnel* notamment, dont on parlera un jour)
  25. # Détails du procédural
  26. * des types simples (entiers, booléens)
  27. * des structures de données (dictionnaires, listes ...)
  28. * des fonctions qui manipulent des types simples ou des structures
  29. * les fonctions sont appelées les unes après les autres
  30. Aujourd'hui on va parler de *l'orienté objet*, OOP en anglais (ou juste OO)
  31. # Orienté objet - une mauvaise définition
  32. Un "objet" informatique *représente* un véritable "objet" physique
  33. dans le vrai monde véritable.
  34. Ce n'est pas une très bonne définition:
  35. 1. Ce n'est pas nécessaire
  36. 2. Ce n'est même pas forcément souhaitable!
  37. Je le mentionne juste parce que c'est une idée reçue très répandue.
  38. # Orienté objet - 1ère définition
  39. Mettre au même endroit:
  40. * des données
  41. * des fonctions qui opèrent sur ces données
  42. L'important c'est que les deux aillent ensemble
  43. # Orienté objet - 2ème définition
  44. Des "cellules" qui s'envoient des "messages".
  45. Notamment, les cellules ne "voient" que leur état interne.
  46. On peut envoyer un message d'une cellule à une autre *sans* connaître
  47. beaucoup de détails à propos du destinataire du message
  48. # Les classes
  49. On va parler *d'une* façon de faire de l'orienté objet: avec des classes.
  50. Mais notez bien qu'on peut faire de l'orienté objet *sans* classes!
  51. # Le plan de construction
  52. La seule chose dont on a besoin pour construire un objet, c'est un plan.
  53. On note le plan avec le mot-clé `class` et un nom:
  54. ```python
  55. class MyObject:
  56. pass
  57. ```
  58. La classe est le plan de construction de notre objet.
  59. # Créons des objets
  60. ```python
  61. >>> object_1 = MyObject()
  62. ```
  63. Python ne sait rien de l'objet à part son adresse on mémoire et son nom:
  64. ```python
  65. >>> id(object_1)
  66. 139993522758320
  67. >>> print(object_1)
  68. <__main__.MyObject at 0x7f52c831e2b0>
  69. ```
  70. On appelle `object_1` une *instance* de la classe `MyObject`.
  71. # Créons d'autres objets
  72. ```python
  73. >>> object_2 = MyObject()
  74. >>> id(object_2)
  75. 140409432622920
  76. >>> print(object_2)
  77. <__main__.MyObject at 0x7fb39e5ac748>
  78. ```
  79. Une autre adresse mémoire, donc un objet différent.
  80. # Méthodes
  81. Une fonction dans une classe:
  82. ```python
  83. class MyObject:
  84. def my_method(the_object):
  85. print("hello", the_object)
  86. ```
  87. \vfill
  88. C'est tout!
  89. # Méthodes - 2
  90. La méthode n'existe pas en dehors de la classe - souvenez vous des cellules !
  91. ```python
  92. >>> my_method()
  93. NameError
  94. >>> object = MyObject()
  95. >>> object.my_method()
  96. Hello, <MyObject at 0x7f52c9f6d6d8>
  97. ```
  98. # Méthodes - 2
  99. ```.python
  100. >>> object = MyObject()
  101. >>> object
  102. <MyObject at 0x7f52c9f6d6d8>
  103. >>>> object.my_method()
  104. Hello, <MyObject at 0x7f52c9f6d6d8>
  105. ```
  106. Notez que `my_method` a pris en premier argument ce qu'il y avait *à gauche* du point.
  107. D'ailleurs, ce code fonctionne aussi et retourne *la même chose*:
  108. ```
  109. >>> MyObject.my_method(object)
  110. Hello, <MyObject at 0x7f52c9f6d6d8>
  111. ```
  112. # Méthodes - 3
  113. Il *faut* passer l'objet en cours en paramètre:
  114. ```python
  115. class MyObject:
  116. def broken():
  117. print("You cannot call me!")
  118. ```
  119. ```python
  120. >>> o = MyObject()
  121. >>> o.broken()
  122. TypeError: broken() takes 0 positional arguments
  123. but 1 was given
  124. ```
  125. # Conventions
  126. * Les classes sont en CamelCase
  127. * Tout le reste (méthodes, etc...) en snake_case
  128. * L'objet en cours s'appelle **toujours** `self`
  129. ```python
  130. class MyObject:
  131. def my_method(self):
  132. print("hello", self)
  133. ```
  134. # Attributs
  135. * Des variables dans un objet.
  136. * On peut ajouter un attribut quand on veut à qui on veut, et toujours avec le
  137. point au milieu:
  138. ```python
  139. >>> object = MyObject()
  140. >>> object.attribute # l'attribut n'existe pas
  141. AttributError
  142. >>> object.attribute = 42 # maintenant oui
  143. >>> object.attribute
  144. 42
  145. ```
  146. # Attributs dans les méthodes
  147. Avec `self`, bien sûr:
  148. ```python
  149. class MyObject:
  150. def print_attribute(self):
  151. print(self.attribute)
  152. def change_attribute(self, new_value)
  153. self.attribute = new_value
  154. ```
  155. # Accéder aux attributs
  156. ```python
  157. >>> object = MyObject()
  158. >>> object.print_attribute() # l'attribut n'existe pas
  159. AttributError
  160. >>> object.attribute = 42
  161. >>> object.print_attribute() # maintenant oui
  162. 42
  163. >>> object.change_attribute(43)
  164. >>> object.attribute
  165. 43
  166. ```
  167. # Initialisation des attributs
  168. Avec `__init__`:
  169. * méthode "spéciale"
  170. * appelée automatiquement
  171. * notez les deux underscores avant et après ('dunder' en anglais)
  172. ```python
  173. class MyObject:
  174. def __init__(self):
  175. self.attribute = 42
  176. ```
  177. ```python
  178. >>> object = MyObject()
  179. >>> object.attribute
  180. 42
  181. ```
  182. # Construire des objets différents
  183. `__init__()` et `MyObject()` sont des appels de fonctions comme les autres
  184. ```python
  185. class Car:
  186. def __init__(self, color_to_use="black"):
  187. self.color = color_to_use
  188. >>> ford = Car()
  189. >>> ford.color
  190. "black"
  191. >>> ferrari = Car(color_to_use="red")
  192. >>> ferrari.color
  193. "red"
  194. ```
  195. # Notes
  196. En vrai, on nomme souvent les paramètres du constructeur et les attributes de la même façon.
  197. ```python
  198. class Car:
  199. def __init__(self, color="black"):
  200. self.color = color
  201. ```
  202. # Récapitulatif
  203. * Classe: plan de construction
  204. * Object: ce qu'on crée avec le plan
  205. * Instance: objet issue d'une classe
  206. * Méthode: fonction dans une classe (qui prend `self` en premier argument)
  207. * Attribut: variable dans un objet
  208. #
  209. \center \huge Modules
  210. # Un fichier = un module
  211. Et oui, vous faites des modules sans le savoir depuis le début :)
  212. Un fichier `foo.py` correspond au module `foo`
  213. # Attention
  214. C'est pas tout à fait réciproque. Le module `foo` peut venir d'autre chose
  215. qu'un fichier.
  216. # Importer un module
  217. Ou: accéder à du code provenant d'un *autre* fichier source.
  218. ```python
  219. # Dans foo.py
  220. a = 42
  221. ```
  222. \vfill
  223. ```python
  224. # Dans bar.py
  225. import foo
  226. print(foo.a)
  227. ```
  228. \vfill
  229. * Affiche '42'
  230. # Espaces de noms
  231. On dit aussi *namespace*.
  232. Du point de vue de `bar.py`, `a` est dans l'*espace de nom* foo.
  233. * On retrouve la syntaxe pour accèder à un attribut: `<variable>.<membre>`.
  234. (ce n'est pas un hasard)
  235. * Les namespaces sont automatiques en Python
  236. # Importer dans le REPL
  237. ```python
  238. # dans foo.py
  239. def ma_fonction():
  240. return 42
  241. ```
  242. ```python
  243. # dans le REPL
  244. >>> import foo
  245. >>> foo.ma_fonction()
  246. 42
  247. ```
  248. * Plus sympa que de rajouter des `print()` à la fin de `foo.py` ;)
  249. # Les imports ne sont faits qu'une seule fois
  250. ```python
  251. # Dans foo.py
  252. print("Je suis le module foo et tu viens de m’importer")
  253. ```
  254. ```python
  255. >>> import foo
  256. Je suis le module foo et tu viens de m’importer
  257. >>> import foo
  258. <rien>
  259. ```
  260. On retrouve le concept de *cache*.
  261. # Attention
  262. Il faudra donc redémarrer le REPL à chaque fois que le code change.
  263. Parfois, les gens conseillent d'utiliser `reload()` mais cette fonction n'est pas toujours fiable :/
  264. # Importer juste une seule fonction ou variable
  265. ```python
  266. # Dans foo.py
  267. a = 42
  268. def ma_fonction():
  269. return 43
  270. ```
  271. \vfill
  272. ```python
  273. # Dans bar.py
  274. from foo import ma_fonction()
  275. ma_fonction()
  276. ```
  277. # Un raccourci
  278. ```
  279. >>> from foo import *
  280. >>> ma_fonction()
  281. 42
  282. ```
  283. * Utile dans le REPL
  284. * Pas une très bonne idée dans du vrai code
  285. * On perd la trace de où vient la variable
  286. * Possibilité de collisions de noms
  287. # Retour sur les scripts
  288. Un script, par opposition à un module n'est pas censé être importé.
  289. Une solution est de mettre des tirets dans le nom:
  290. ```python
  291. # Dans foo.py
  292. import my-script
  293. ```
  294. * Essaye de soustraire `script` à `my`
  295. * ça ne fonctionne pas
  296. # Retour sur main()
  297. La méthode `main()` ne *doit* pas être exécutée quand on importe le code!
  298. Solution:
  299. ```python
  300. # Dans foo.py
  301. def my_function():
  302. # Fonction utile qui peut être ré-utilisée
  303. def main():
  304. # Fonction d'entrée principale
  305. # Utilise my_function()
  306. # magie!
  307. if __name__ == "__main__":
  308. main()
  309. ```
  310. # Explication (partielle) de la magie
  311. Le `if` n'est vrai *que* quand on lance `python3 foo.py`, mais pas quand on appelle `import foo` depuis
  312. un autre module.
  313. La variable magique `__name__` est égale au nom du module quand il est importé, et à la string `"__main__"` sinon.
  314. # La librarie standard (1)
  315. * Une collection de modules directement utilisables fournis à l'installation de Python.
  316. * on a déjà vu `sys`, pour `sys.exit()` ou `sys.argv`
  317. # La librarie standard (2)
  318. Toute la librarie standard est documentée - même en Français.
  319. https://docs.python.org/fr/3/library/index.html
  320. * Gardez-là sous votre oreiller :)
  321. # La librarie standard (3)
  322. Beacoup de choses dedans. (*batteries included*)
  323. De quoi faire de la manipulation de texte, des statistiques, du réseau, de la concurrence, etc ...
  324. #
  325. \center \huge Atelier
  326. # Jouons avec les API Web
  327. API web: un serveur avec qui on peut parler depuis un programme.
  328. Il en existe des quantités sur internet.
  329. Aujourd'hui on va utiliser `numbersapi.com`
  330. # Numbersapi
  331. Example: On fait une requête sur `http://numbersapi.com/42`, on récupère du texte
  332. contenant un fait intéressant (*trivia* en anglais) à propos du nombre 42 .
  333. # Code de départ
  334. Voir sur GitHub: https://github.com/E2L/cours-python/blob/master/sources/numbers/numbers_proc.py
  335. ```python
  336. import sys
  337. import urllib.request
  338. def main():
  339. number = sys.argv[1]
  340. with urllib.request.urlopen("http://numbersapi.com/" + number) as request:
  341. response = request.read().decode("utf-8")
  342. print(response)
  343. if __name__ == "__main__":
  344. main()
  345. ```
  346. # Idée
  347. * Transformer ceci en une classe réutilisable
  348. * Gérer les faits mathématiques (avec une URL en http://numbersapi/42/math) en plus
  349. des trivias (avec une URL en http://numbersapi/42)
  350. # Refactoring
  351. Démo faite en cours.
  352. # Extraction de variable
  353. *avant*:
  354. ```python
  355. with urllib.request.urlopen(
  356. "http://numbersapi.com/" + number) as request:
  357. ...
  358. ```
  359. *après*:
  360. ```
  361. url = "http://numbersapi.com/" + number
  362. with urllib.request.urlopen(url) as request:
  363. ```
  364. # Extraction de méthode - avant
  365. ```python
  366. def get_trivia(self, number):
  367. url = "http://numbersapi.com/" + number
  368. with urllib.request.urlopen(url) as request:
  369. response = request.read().decode("utf-8")
  370. return response
  371. ```
  372. # Extraction de méthode - après
  373. ```python
  374. def build_url(self, number):
  375. return "http://numbersapi.com/" + number
  376. def do_request(self, url):
  377. with urllib.request.urlopen(url) as request:
  378. response = request.read().decode("utf-8")
  379. return response
  380. def get_trivia(self, number):
  381. url = self.build_url(number)
  382. return self.do_request(url)
  383. ```
  384. # Code final
  385. Voir sur GitHub:
  386. https://github.com/E2L/cours-python/blob/master/sources/numbers/numbers_object.py
  387. # Pour la prochaine fois - Exercice 1
  388. Partir des sources dans le répertoire `hangman`:
  389. https://github.com/E2L/cours-python/tree/master/sources/hangman
  390. * Rajouter la gestion des scores (dans `scores.py`) au code exsistant
  391. du jeu du pendu (dans `hangman.py`)
  392. * Refactorer en essayant d'introduire des classes
  393. # Pour la prochaine fois - Exercice 2
  394. Partir du code dans `numbers_object.py`, rajouter la getsion
  395. des autres URL de http://numbersapi.com
  396. # Des questions? Du code?
  397. Envoyez-moi un e-mail à `d.merej@gmail.com` :)