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-09.md 9.6 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. % Programmation avec Python (chapitre 9)
  2. % Dimitri Merejkowsky
  3. \center \huge Formats et chaînes de caractères
  4. # Formater des chaînes de caractères
  5. Problème:
  6. \vfill
  7. ```python
  8. >>> nom = "Ford"
  9. >>> résultat = 42
  10. >>> message = "Bonjour, " + nom + ". "
  11. >>> message += "La réponse est: " + str(résultat) + "."
  12. >>> message
  13. 'Bonjour, Ford. La réponse est: 42.'
  14. ```
  15. \vfill
  16. Ce n'est pas très lisible ...
  17. 3 solutions différentes en Python
  18. # Solution 1: À l'ancienne
  19. * Avec `%`
  20. * Compatible avec les très vieux Pythons
  21. * Compatibles avec d'autres langage (`printf` en C par example)
  22. # Example 1
  23. ```python
  24. name = "John"
  25. print("Bonjour %s!" % name) # 's' comme string
  26. age = 42
  27. print("Vous avez %i ans" % age) # 'i' comme integer
  28. poids = 68.5
  29. print("Vous pesez %f kilos" % poids) # 'f' comme float
  30. ```
  31. # Attention aux types:
  32. ```python
  33. >>> print("Bonjour %i!" % name)
  34. TypeError: %i format: a number is required, not str
  35. ```
  36. # On peut en mettre plusieurs
  37. Avec un tuple:
  38. ```python
  39. print("Bonjour %s, vous avez %i" % (name, age))
  40. ```
  41. \vfill
  42. Avec un dictionnaire:
  43. ```python
  44. data = {"name": "John", "age": 42}
  45. print("Bonjour %(name)s, vous avez %(age)i ans" % data)
  46. ```
  47. # Solution 2: format()
  48. Des `{}` comme "placeholders" et une *méthode* sur les strings:
  49. ```python
  50. >>> nom = "Ford"
  51. >>> résultat = 42
  52. >>> template = "Bonjour, {}. La réponse est: {}"
  53. >>> message = template.format(nom, résultat)
  54. >>> message
  55. 'Bonjour, Ford. La réponse est: 42.'
  56. ```
  57. * Pas de types!
  58. # format() - à plusieurs
  59. On peut aussi nommer les remplacements:
  60. ```python
  61. template = "Bonjour, {nom}. La réponse est: {résultat}"
  62. template.format(nom="Ford", résultat=42)
  63. ```
  64. # format() - à plusieurs
  65. On peut les ordonner:
  66. ```python
  67. template = "Bonjour {1}. La réponse est {0}"
  68. template.format(reponse, name)
  69. ```
  70. * Pas possible avec `%`!
  71. # Solution 3: f-strings
  72. * La meilleure de toutes :)
  73. * Plus succint que `format()`
  74. * Plus performant que `%` et `.format()`
  75. * Mais pas avant Python 3.6 (2016)
  76. # Principe
  77. On peut mettre du *code* dans `{}` avec la lettre `f` devant la chaîne:
  78. ```python
  79. name = "Patrick"
  80. score = 42
  81. text = f"Bonjour {name}. Votre score est: {score}"
  82. ```
  83. \vfill
  84. Mais aussi:
  85. ```python
  86. a = 2
  87. b = 3
  88. text = f"résultat: {a + b}"
  89. ```
  90. # Conclusion
  91. Je vous ai présenté `%` et `format()` parce que vous risquez d'en voir.
  92. Mais si vous avez le choix, utilisez des `f-strings`!
  93. # Spécifications de formats
  94. * Permet des opérations pendant la conversion en texte
  95. * Fonctionne avec les 3 solutions
  96. # Tronquer
  97. ```python
  98. >>> pi = 3.14159265359
  99. >>> f"pi vaut à peu près {pi:.2f}"
  100. pi vaut à peu près 3.14
  101. ```
  102. Le texte dans les accolades après le `:` est un mini-langage de spécification de format.
  103. `.2f` veut dire: 2 chiffres après la virgule maximum.
  104. Fonctionne aussi avec `.format()` `et %`:
  105. ```python
  106. "pi vaut à peu près {:.2f}".format(pi)
  107. "pi vaut à peu près %.2f" % pi
  108. ```
  109. # Alignements et paddings
  110. On peut aussi faire des alignements et du "padding":
  111. \vfill
  112. ```python
  113. template = "{name:>10}: {score:03}"
  114. print(template.format(name="Alice", score=42))
  115. print(template.format(name="Bob", score=5))
  116. ```
  117. ```
  118. Alice: 042
  119. Bob: 005
  120. ```
  121. * `>10` : aligné à gauche, taille minimum 10
  122. * `03`: rajouter jusqu'à 2 zéros à gauche pour que la taille fasse 3
  123. # Documentation
  124. Documentation ici:
  125. htps://docs.python.org/fr/3/library/string.html#format-specification-mini-language
  126. #
  127. \center \huge Rappels sur les classes
  128. # Classes
  129. ```python
  130. class Car:
  131. total_number_of_cars = 0
  132. def __init__(self, color="black"):
  133. self.color = color
  134. Car.total_number_of_cars += 1
  135. def drive(self):
  136. print("vroom")
  137. @classmethod
  138. def print_number_of_cars(cls):
  139. print(cls.total_number_of_cars,
  140. "cars have been made")
  141. ```
  142. # Composition
  143. ```python
  144. class Authorization:
  145. def __init__(self, credentials_file):
  146. ...
  147. self.password = ...
  148. class Client:
  149. url = "https://exmple.com"
  150. def __init__(self, auth)
  151. self.auth = auth
  152. def make_request(self):
  153. password = self.auth.get_password()
  154. requests.get(url, password=password)
  155. ```
  156. # Héritage - partage des attributs et méthodes
  157. ```python
  158. class A:
  159. def method_in_a(self):
  160. self.attribute_in_a = 42
  161. class B(A):
  162. def method_in_b(self):
  163. self.method_in_a() # ok
  164. self.attribute_in_a # ok
  165. ```
  166. On dit aussi que A est la classe *de base* et B la classe *dérivée*.
  167. # Héritage - ordre de résolution des méthodes
  168. ```python
  169. class A:
  170. def method_in_a(self):
  171. pass
  172. class B(A):
  173. def method_in_b(self):
  174. pass
  175. >>> a = A()
  176. >>> a.method_in_a() # ok
  177. >>> a.method_in_b() # error
  178. >>> b = B()
  179. >>> b.method_in_b() # ok
  180. >>> b.method_in_a() # ok
  181. ```
  182. # Héritage - ordre de résolution des méthodes
  183. ```python
  184. class A:
  185. def method_in_a(self):
  186. pass
  187. class B(A):
  188. def method_in_b(self):
  189. pass
  190. >>> a = A()
  191. >>> a.method_in_a() # ok
  192. >>> a.method_in_b() # error
  193. >>> b = B()
  194. >>> b.method_in_b() # ok
  195. >>> b.method_in_a() # ok
  196. ```
  197. # Héritage: surcharge
  198. ```python
  199. class A:
  200. def do_stuff(self):
  201. print("A!")
  202. class B(A):
  203. def do_stuff(self):
  204. print("B!")
  205. >>> a = A()
  206. >>> a.do_stuff() # ok
  207. 'A!'
  208. >>> b = B()
  209. >>> b.do_stuff()
  210. 'B!'
  211. ```
  212. # Héritage - super()
  213. ```python
  214. class A:
  215. def do_stuff(self):
  216. print("A!")
  217. class B(A):
  218. def do_stuff(self):
  219. super().do_stuff()
  220. print("B!")
  221. >>> a = A()
  222. >>> a.do_stuff() # ok
  223. 'A!'
  224. >>> b = B()
  225. >>> b.do_stuff()
  226. 'A!'
  227. 'B!'
  228. ```
  229. # Héritage - super() et \_\_init\_\_
  230. ```python
  231. # All animals have a species
  232. class Animal:
  233. def __init__(self, species):
  234. self.species = species
  235. # Pets are animals with a name
  236. class Pet(Animal):
  237. def __init__(self, species, name):
  238. super().__init__(species) # <- à ne pas oublier
  239. self.name = name
  240. # All dogs are pets
  241. class Dog(Pet):
  242. def __init__(self, name):
  243. super().__init__("dog", name)
  244. ```
  245. #
  246. \center \huge Interfaces et classes abstraites
  247. # Example
  248. Imaginons un jeu où il faut retrouver le nom d'un super-héros à partir
  249. de sa description.
  250. On a une classe `MarvelClient` qui permet de lister les personnages et leurs
  251. descriptions et une class `Game` pour la logique du jeu
  252. # Implémentation - MarvelClient
  253. ```python
  254. class MarvelClient:
  255. url = "https://marvel.com/api"
  256. def __init__(self, credentials_file):
  257. # réupére les clés depuis un fichier
  258. def get_all_characters(self):
  259. # appelle l'api marvel pour récupérer
  260. # tous les personnages
  261. def get_description(self, character_name)
  262. # appelle l'api marvel pour récupérer
  263. # une description
  264. ```
  265. # Implémentation - Game
  266. ```python
  267. import random
  268. class Game:
  269. def __init__(self, marvel_client)
  270. self.marvel_client = marvel_client
  271. def play(self):
  272. characters = self.marvel_client.get_all_characters()
  273. name_to_guess = random.choice(characters)
  274. description = self.marvel_client.get_description(
  275. name_to_guess)
  276. while not self.won():
  277. ...
  278. ```
  279. # Contrats implicites - 1
  280. Il y a un *contrat implicite* entre `Game` et `MarvelClient`.
  281. Dans `play` on appelle `self.marvel_client.get_all_characters()` donc la méthode
  282. `get_all_characters()` doit:
  283. * exister
  284. * ne prendre aucun argument
  285. * retourner une liste de noms
  286. # Contrats implicites - 2
  287. Pareil avec `get_description()`. La méthode doit:
  288. * exister
  289. * prendre un nom en unique argument
  290. * retourner une description
  291. # Une force et une faiblesse
  292. On peut passer à `Game.__init__()` n'importe qu'elle classe pourvu qu'elle ait
  293. les bonnes méthodes!
  294. On appelle ça "duck typing"
  295. # duck typing
  296. Définition traditionnelle (pas exacte à mon sens):
  297. * Si ça marche comme un canard et que ça fait coin-coin comme un canard alors c'est un canard.
  298. \vfill
  299. Meilleure définition:
  300. * Tu peux passer remplacer le canard par une vache. Tant que la vache a un bec et fait coin-coin, c'est bon!
  301. # En image
  302. ![canard vache](img/canard-vache.jpg)
  303. # Exemple utile
  304. ```python
  305. class FakeClient():
  306. def get_all_characters(self):
  307. return ["Spider-Man", "Batman", "Superman"]
  308. def get_description(self, name):
  309. if name == "Spider-Man":
  310. ...
  311. ...
  312. fake_client = FakeClient()
  313. game = Game(fake_client)
  314. game.play()
  315. # Tout marche!
  316. ```
  317. # Problème
  318. Comment s'assurer que `FakeClient` et `MarvelClient` restent synchronisés?
  319. # Solution
  320. Une classe *abstraite*:
  321. ```python
  322. import abc
  323. class BaseClient(metaclass=abc.ABCMeta):
  324. @abc.abstractmethod
  325. def get_all_characters(self):
  326. pass
  327. @abc.abstractmethod
  328. def get_description(self, name):
  329. pass
  330. ```
  331. On retrouve le `@` au-dessus des méthodes.
  332. On reparlera des metaclasses plus tard :)
  333. # Utilisation
  334. On ne peut pas instancier la classe abstraite directement:
  335. ```python
  336. >>> client = BaseClient()
  337. # Cannot instantiate abstract class BaseClient
  338. # with abstract methods
  339. # get_all_characters, get_description
  340. ```
  341. En revanche on peut en hériter:
  342. ```python
  343. class MarvelClient(BaseClient):
  344. def get_all_characters(self):
  345. ...
  346. def get_description(self, name):
  347. ...
  348. ```
  349. À la construction, Python va vérifier que les méthodes abstraites
  350. sont bien surchargées.
  351. # Conclusion
  352. Plein de langages ont un concept d'interface.
  353. C'est utile de savoir que les interfaces existent en Python et ça peut rendre
  354. le code plus clair.
  355. Cela dit, dans le cas de Python c'est complètement *optionnel*.
  356. #
  357. \center \huge Atelier
  358. # Encore un refactoring
  359. Résultat sur `git.e2li.org`:
  360. `dmerejkowsky/cours-python/sources/marvel/marvel.py`
  361. # Pour la prochaine fois:
  362. * Créer un compte dévelopeur sur le site de Marvel
  363. * Implémenter le jeu!
  364. * Consignes:
  365. `dmerejkowsky/cours-python/sources/marvel/consignes.md`