Coverage for src/teachpyx/faq/faq_python.py: 100%

67 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-07-19 05:40 +0200

1# -*- coding: utf-8 -*- 

2# pylint: disable=C0115,C0116 

3""" 

4@file 

5@brief Quelques questions d'ordre général autour du langage Python. 

6 

7""" 

8 

9import os 

10import io 

11import re 

12 

13 

14def entier_grande_taille(): 

15 """ 

16 

17 .. faqref:: 

18 :tag: python 

19 :title: Quel est l'entier le plus grand ? 

20 

21 La version 3 du langage Python a supprimé la constante ``sys.maxint`` 

22 qui définissait l'entier le plus grand (voir 

23 `What's New In Python 3.0 <https://docs.python.org/3.1/whatsnew/3.0.html#integers>`_). 

24 De ce fait la fonction `getrandbit <https://docs.python.org/3/library/random.html#random.getrandbits>`_ 

25 retourne un entier aussi grand que l'on veut. 

26 

27 :: 

28 

29 import random,sys 

30 x = random.getrandbits(2048) 

31 print(type(x),x) 

32 

33 Qui affiche :: 

34 

35 <class 'int'> 2882159224557107513165483098383814837021447484558010147211921304219017212673656549681269862792029... 

36 

37 Les calculs en nombre réels se font toujours avec huit octets de précision. 

38 Au delà, il faut utiliser la librairie `gmpy2 <http://gmpy2.readthedocs.org/en/latest/>`_. 

39 Il est également recommandé d'utiliser cette librairie pour les grands nombres entiers 

40 (entre 20 et 40 chiffres). La librairie est plus rapide que l'implémentation 

41 du langage Python (voir `Overview of gmpy2 <https://gmpy2.readthedocs.org/en/latest/overview.html>`_). 

42 

43 .. faqref:: 

44 :tag: python 

45 :title: Tabulations ou espace ? 

46 

47 Il est préférable de ne pas utiliser les tabulations et de les remplacer par des espaces. 

48 Lorsqu'on passe d'un Editeur à un autre, les espaces ne bougent pas. Les tabulations sont plus ou moins grandes visuellement. 

49 L'essentiel est de ne pas mélanger. 

50 Dans `SciTE <http://www.scintilla.org/SciTE.html>`_, il faut aller dans le menu Options / Change Indentation Settings... 

51 Tous les éditeurs ont une option similaire. 

52 """ 

53 pass 

54 

55 

56def difference_div(): 

57 """ 

58 .. faqref:: 

59 :tag: python 

60 :title: Quelle est la différence entre / et // - division ? 

61 

62 Le résultat de la division avec l'opérateur ``/`` est toujours réel : 

63 la division de deux entiers ``1/2`` donne ``0.5``. 

64 Le résultat de la division avec l'opérateur ``//`` est toujours entier. 

65 Il correspond au quotient de la division. 

66 

67 .. runpython:: 

68 :showcode: 

69 

70 div1 = 1/2 

71 div2 = 4/2 

72 div3 = 1//2 

73 div4 = 1.0//2.0 

74 print(div1, div2, div3, div4) # affiche (0.5, 2.0, 0, 0) 

75 

76 Le reste d'une division entière est obtenue avec l'opérateur ``%``. 

77 

78 .. runpython:: 

79 :showcode: 

80 

81 print( 5 % 2 ) # affiche 1 

82 

83 C'est uniquement vrai pour les version Python 3.x. 

84 Pour les versions 2.x, les opérateurs ``/`` et ``//`` avaient des comportements différents 

85 (voir `What’s New In Python 3.0 <https://docs.python.org/3/whatsnew/3.0.html#integers>`_). 

86 """ 

87 div1 = 1 / 2 

88 div2 = 4 / 2 

89 div3 = 1 // 2 

90 div4 = 1.0 // 2.0 

91 return div1, div2, div3, div4 

92 

93 

94def python_path(): 

95 """ 

96 .. faqref:: 

97 :tag: module 

98 :title: Comment éviter sys.path.append... quand on développe un module ? 

99 

100 Lorsqu'on développe un module, 

101 on ne veut pas l'installer. On ne veut pas qu'il soit présent dans le répertoire ``site-packages`` de la distribution 

102 de Python car cela introduit deux versions : celle qu'on développe et celle qu'on a installer. 

103 Avant, je faisais cela pour créer un petit programme utilisant mon propre module 

104 (et on en trouve quelque trace dans mon code) : 

105 

106 :: 

107 

108 import sys 

109 sys.path.append("c:/moncode/monmodule/src") 

110 import monmodule 

111 

112 Quand je récupère un programme utilisant ce module, il me faudrait ajouter 

113 ces petites lignes à chaque fois et c'est barbant. 

114 Pour éviter cela, il est possible de dire à l'interpréteur Python d'aller chercher 

115 ailleurs pour trouver des modules en ajoutant le chemin à la 

116 `variable d'environnement <http://fr.wikipedia.org/wiki/Variable_d'environnement>`_ 

117 `PYTHONPATH <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH>`_. 

118 Sous Windows : 

119 

120 :: 

121 

122 set PYTHON_PATH=%PYTHON_PATH%;c:\\moncode\\monmodule\\src 

123 """ 

124 return os.environ.get("PYTHON_PATH", "") 

125 

126 

127def same_variable(a, b): 

128 """ 

129 Cette fonction dit si les deux objets sont en fait le même objet (True) 

130 ou non (False) s'ils sont différents (même s'ils contiennent la même information). 

131 

132 :param a: n'importe quel objet 

133 :param b: n'importe quel objet 

134 :return: ``True`` ou ``False`` 

135 

136 .. faqref:: 

137 :tag: python 

138 :title: Qu'est-ce qu'un type immuable ou immutable ? 

139 :lid: faq-py-immutable 

140 

141 Une variable de type *immuable* ne peut être modifiée. Cela concerne principalement : 

142 

143 - ``int``, ``float``, ``str``, ``tuple`` 

144 

145 Si une variable est de type *immuable*, lorsqu'on effectue une opération, 

146 on créé implicitement une copie de l'objet. 

147 

148 Les dictionnaires et les listes sont *modifiables* (ou *mutable*). Pour une variable 

149 de ce type, lorsqu'on écrit ``a = b``, ``a`` et ``b`` désigne le même objet même 

150 si ce sont deux noms différentes. C'est le même emplacement mémoire 

151 accessible paur deux moyens (deux identifiants). 

152 

153 Par exemple :: 

154 

155 a = (2,3) 

156 b = a 

157 a += (4,5) 

158 print( a == b ) # --> False 

159 print(a,b) # --> (2, 3, 4, 5) (2, 3) 

160 

161 a = [2,3] 

162 b = a 

163 a += [4,5] 

164 print( a == b ) # --> True 

165 print(a,b) # --> [2, 3, 4, 5] [2, 3, 4, 5] 

166 

167 Dans le premier cas, le type (``tuple``) est _immutable_, l'opérateur ``+=`` cache implicitement une copie. 

168 Dans le second cas, le type (``list``) est _mutable_, l'opérateur ``+=`` évite la copie 

169 car la variable peut être modifiée. Même si ``b=a`` est exécutée avant l'instruction suivante, 

170 elle n'a **pas** pour effet de conserver l'état de ``a`` avant l'ajout d'élément. 

171 Un autre exemple :: 

172 

173 a = [1, 2] 

174 b = a 

175 a [0] = -1 

176 print(a) # --> [-1, 2] 

177 print(b) # --> [-1, 2] 

178 

179 Pour copier une liste, il faut expliciter la demander :: 

180 

181 a = [1, 2] 

182 b = list(a) 

183 a [0] = -1 

184 print(a) # --> [-1, 2] 

185 print(b) # --> [1, 2] 

186 

187 La page `Immutable Sequence Types <https://docs.python.org/3/library/stdtypes.html?highlight=immutable#immutable-sequence-types>`_ 

188 détaille un peu plus le type qui sont *mutable* et ceux qui sont *immutable*. Parmi les types standards : 

189 

190 * **mutable** 

191 * `bool <https://docs.python.org/3/library/functions.html#bool>`_ 

192 * `int <https://docs.python.org/3/library/functions.html#int>`_, 

193 `float <https://docs.python.org/3/library/functions.html#float>`_, 

194 `complex <https://docs.python.org/3/library/functions.html#complex>`_ 

195 * `str <https://docs.python.org/3/library/functions.html#func-str>`_, 

196 `bytes <https://docs.python.org/3/library/functions.html#bytes>`_ 

197 * `None <https://docs.python.org/3/library/constants.html?highlight=none#None>`_ 

198 * `tuple <https://docs.python.org/3/library/functions.html#func-tuple>`_, 

199 `frozenset <https://docs.python.org/3/library/functions.html#func-frozenset>`_ 

200 * **immutable**, par défaut tous les autres types dont : 

201 * `list <https://docs.python.org/3/library/functions.html#func-list>`_ 

202 * `dict <https://docs.python.org/3/library/functions.html#func-dict>`_ 

203 * `set <https://docs.python.org/3/library/functions.html#func-set>`_ 

204 * `bytearray <https://docs.python.org/3/library/functions.html#bytearray>`_ 

205 

206 Une instance de classe est mutable. Il est possible de la rendre 

207 immutable par quelques astuces : 

208 

209 * `__slots__ <https://docs.python.org/3/reference/datamodel.html?highlight=_slots__#object.__slots__>`_ 

210 * `How to Create Immutable Classes in Python 

211 <http://www.blog.pythonlibrary.org/2014/01/17/how-to-create-immutable-classes-in-python/>`_ 

212 * `Ways to make a class immutable in Python <http://stackoverflow.com/questions/4996815/ways-to-make-a-class-immutable-in-python>`_ 

213 * `freeze <https://freeze.readthedocs.org/en/latest/>`_ 

214 

215 Enfin, pour les objects qui s'imbriquent les uns dans les autres, une liste de listes, une classe 

216 qui incluent des dictionnaires et des listes, on distingue une copie simple d'une copie intégrale (**deepcopy**). 

217 Dans le cas d'une liste de listes, la copie simple recopie uniquement la première liste :: 

218 

219 import copy 

220 l1 = [ [0,1], [2,3] ] 

221 l2 = copy.copy(l1) 

222 l1 [0][0] = '##' 

223 print(l1,l2) # --> [['##', 1], [2, 3]] [['##', 1], [2, 3]] 

224 

225 l1 [0] = [10,10] 

226 print(l1,l2) # --> [[10, 10], [2, 3]] [['##', 1], [2, 3]] 

227 

228 La copie intégrale recopie également les objets inclus :: 

229 

230 import copy 

231 l1 = [ [0,1], [2,3] ] 

232 l2 = copy.deepcopy(l1) 

233 l1 [0][0] = '##' 

234 print(l1,l2) # --> [['##', 1], [2, 3]] [[0, 1], [2, 3]] 

235 

236 Les deux fonctions s'appliquent à tout object Python : `module copy <https://docs.python.org/3/library/copy.html>`_. 

237 """ 

238 return id(a) == id(b) 

239 

240 

241def stringio(text): 

242 """ 

243 returns a StringIO object on a text 

244 

245 :param text: any text 

246 :return: StringIO object 

247 

248 .. faqref:: 

249 :tag: python 

250 :title: A quoi sert un ``StringIO`` ? 

251 

252 La plupart du temps, lorsqu'on récupère des données, elles sont sur le disque dur 

253 de votre ordinateur dans un fichier texte. Lorsqu'on souhaite automatiser un processur 

254 qu'on répète souvent avec ce fichier, on écrit une fonction qui prend le nom du fichier en entrée. 

255 

256 :: 

257 

258 def processus_quotidien(nom_fichier) : 

259 # on compte les lignes 

260 nb = 0 

261 with open(nom_fichier,"r") as f : 

262 for line in f : 

263 nb += 1 

264 return nb 

265 

266 Et puis un jour, les données ne sont plus dans un fichier mais sur Internet. 

267 Le plus simple dans ce cas est de recopier ces données sur disque dur et d'appeler la même fonction. 

268 Simple. Un autre les données qu'on doit télécharger font plusieurs gigaoctets. Tout télécharger prend 

269 du temps pour finir pour s'apercevoir qu'elles sont corrompues. On a perdu plusieurs heures pour rien. 

270 On aurait bien voulu que la fonction ``processus_quotidien`` commence à traiter les données 

271 dès le début du téléchargement. 

272 

273 Pour cela, on a inventé la notion de **stream** ou **flux** qui sert d'interface entre la fonction 

274 qui traite les données et la source des données. Le flux lire les données depuis n'importe quel source 

275 (fichier, internet, mémoire), la fonction qui les traite n'a pas besoin d'en connaître la provenance. 

276 

277 `StringIO <https://docs.python.org/3/library/io.html#io.StringIO>`_ est un flux qui considère 

278 la mémoire comme source de données. 

279 

280 :: 

281 

282 def processus_quotidien(data_stream): 

283 # on compte toujours les lignes 

284 nb = 0 

285 for line in data_stream : 

286 nb += 1 

287 return nb 

288 

289 La fonction ``processus_quotidien`` fonctionne pour des données en mémoire 

290 et sur un fichier. 

291 

292 :: 

293 

294 fichier = __file__ 

295 f = open(fichier,"r") 

296 nb = processus_quotidien(f) 

297 print(nb) 

298 

299 text = "ligne1\nligne2" 

300 st = io.StringIO(text) 

301 nb = processus_quotidien(st) 

302 print(nb) 

303 """ 

304 return io.StringIO(text) 

305 

306 

307def property_example(): 

308 """ 

309 

310 .. faqref:: 

311 :tag: class 

312 :title: property 

313 

314 Une `property <https://docs.python.org/3/library/functions.html#property>`_ est 

315 une écriture qui sert à transformer l'appel d'une méthode de classe 

316 en un attribut. 

317 

318 :: 

319 

320 class ClasseAvecProperty: 

321 

322 def __init__(self,x,y): 

323 self._x, self._y = x,y 

324 

325 @property 

326 def x(self): 

327 return self._x 

328 

329 @property 

330 def y(self): 

331 return self._y 

332 

333 @property 

334 def norm2(self): 

335 return self._y**2 + self._x**2 

336 

337 c = ClasseAvecProperty(1,2) 

338 print(c.x) 

339 print(c.y) 

340 

341 ``x`` est définit comme une méthode mais elle retourne simplement l'attribut 

342 ``_x``. De cette façon, il est impossible de changer ``x`` en écrivant:: 

343 

344 c.x = 5 

345 

346 Cela déclenche l'erreur:: 

347 

348 Traceback (most recent call last): 

349 File "faq_python.py", line 455, in <module> 

350 c.x = 5 

351 AttributeError: can't set attribute 

352 

353 On fait cela parce que l'écriture est plus courte et que cela 

354 évite certaines erreurs. 

355 """ 

356 pass 

357 

358 

359def enumerate_regex_search(exp, text): 

360 """ 

361 Cette fonction itère sur les différentes occurences d'une expression régulière. 

362 

363 :param exp: expression régulière 

364 :param text: text à parser 

365 :return: itérateur 

366 

367 .. faqref:: 

368 :tag: regex 

369 :title: Comment itérer sur les résultats d'une expression régulière ? 

370 

371 On utilise la méthode `finditer <https://docs.python.org/3/library/re.html#re.regex.finditer>`_. 

372 

373 :: 

374 

375 found = exp.search(text) 

376 for m in exp.finditer(text): 

377 # ... 

378 

379 Voir également `Petites subtilités avec les expressions régulières en Python 

380 <http://www.xavierdupre.fr/blog/2014-12-02_nojs.html>`_. 

381 """ 

382 # found = exp.search(text) 

383 if isinstance(exp, str): 

384 exp = re.compile(exp) 

385 for m in exp.finditer(text): 

386 yield m 

387 

388 

389def sortable_class(cl): 

390 """ 

391 .. faqref:: 

392 :tag: class 

393 :title: Classe sortable 

394 

395 Il faut prononcer *sortable* à l'anglaise. Comment rendre une classe 

396 *sortable* ? Pour faire simple, on veut écrire :: 

397 

398 l = [ o1, o2 ] 

399 l.sort() 

400 

401 Où ``o1`` et ``o2`` sont des objets d'une classe 

402 que vous avez définie :: 

403 

404 class MaClasse: 

405 

406 ... 

407 

408 Pour que cela fonctionne, il suffit juste 

409 de surcharger l'opérateur ``<`` ou plus exactement 

410 ``__lt__``. Par exemple :: 

411 

412 class MaClasse: 

413 

414 def __lt__(self, autre_instance): 

415 if self.jenesaispas < autre.jenesaispas: 

416 return True 

417 elif self.jenesaispas > autre.jenesaispas: 

418 return False: 

419 else: 

420 if self.jenesaispas2 < autre.jenesaispas2: 

421 return True 

422 else: 

423 return False 

424 """ 

425 pass 

426 

427 

428def list_of_installed_packages(): 

429 """ 

430 calls ``pip list`` to retrieve the list of packages 

431 

432 .. faqref:: 

433 :tag: module 

434 :title: Obtenir des informations sur les packages installés 

435 

436 Le module `pip <https://pip.pypa.io/en/stable/>`_ retourne des informations 

437 sur n'importe quel module installé, sa version, sa license :: 

438 

439 pip show pandas 

440 

441 On peut également l'obtenir depuis l'interpréteur python :: 

442 

443 import pip 

444 pip.main(["show", "pandas"]) 

445 

446 Exemple :: 

447 

448 Name: pandas 

449 Version: 0.16.0 

450 Summary: Powerful data structures for data analysis, time series,and statistics 

451 Home-page: http://pandas.pydata.org 

452 Author: The PyData Development Team 

453 Author-email: pydata@googlegroups.com 

454 License: BSD 

455 Location: c:\\python35_x64\\lib\\site-packages 

456 Requires: python-dateutil, pytz, numpy 

457 

458 On utilise également ``pip freeze`` pour répliquer l'environnement 

459 dans lequel on a développé un programme. `pip freeze <https://pip.pypa.io/en/latest/reference/pip_freeze.html>`_ 

460 produit la liste des modules avec la version utilisée :: 

461 

462 docutils==0.11 

463 Jinja2==2.7.2 

464 MarkupSafe==0.19 

465 Pygments==1.6 

466 Sphinx==1.2.2 

467 

468 Ce qu'on utilise pour répliquer l'environnement de la manière suivante :: 

469 

470 pip freeze > requirements.txt 

471 pip install -r requirements.txt 

472 

473 Cette façon de faire fonctionne très bien sous Linux mais n'est pas encore 

474 opérationnelle sous Windows à moins d'installer le compilateur C++ utilisée pour compiler 

475 Python. 

476 """ 

477 from pyquickhelper.pycode.pip_helper import get_packages_list # pylint: disable=C0415 

478 return get_packages_list() 

479 

480 

481def information_about_package(name): 

482 """ 

483 calls ``pip show`` to retrieve information about packages 

484 

485 .. faqref:: 

486 :tag: module 

487 :title: Récupérer la liste des modules installés 

488 

489 Le module `pip <https://pip.pypa.io/en/stable/>`_ permet d'installer 

490 de nouveaux modules mais aussi d'obtenir la liste des packages installés :: 

491 

492 pip list 

493 

494 On peut également l'obtenir depuis l'interpréteur python :: 

495 

496 import pip 

497 pip.main(["list"]) 

498 

499 .. faqref:: 

500 :tag: python 

501 :title: Pourquoi l'installation de pandas (ou numpy) ne marche pas sous Windows avec pip ? 

502 

503 Python est un langage très lent et c'est pourquoi la plupart des modules de calculs numériques 

504 incluent des parties implémentées en langage C++. 

505 `numpy <http://www.numpy.org/>`_, 

506 `pandas <http://pandas.pydata.org/>`_, 

507 `matplotlib <http://matplotlib.org/>`_, 

508 `scipy <http://www.scipy.org/>`_, 

509 `scikit-learn <http://scikit-learn.org/stable/>`_, 

510 ... 

511 

512 Sous Linux, le compilateur est intégré au système et l'installation de ces modules via 

513 l'instruction ``pip install <module>`` met implicitement le compilateur à contribution. 

514 Sous Windows, il n'existe pas de compilateur C++ par défaut à moins de l'installer. 

515 Il faut faire attention alors d'utiliser exactement le même que celui utilisé 

516 pour compiler Python (voir 

517 `Compiling Python on Windows <https://docs.python.org/3/using/windows.html#compiling-python-on-windows>`_). 

518 

519 C'est pour cela qu'on préfère utiliser des distributions comme 

520 `Anaconda <http://continuum.io/downloads#py34>`_ 

521 qui propose par défaut 

522 une version de Python accompagnée des modules les plus utilisés. Elle propose également une façon 

523 simple d'installer des modules précompilés avec l'instruction :: 

524 

525 conda install <module_compile> 

526 

527 L'autre option est d'utilser le site 

528 `Unofficial Windows Binaries for Python Extension Packages <http://www.lfd.uci.edu/~gohlke/pythonlibs/>`_ 

529 qui propose des versions compilées sous Windows d'un grand nombre de modules. 

530 Il faut télécharger le fichier *.whl* puis l'installer avec l'instruction ``pip install <fichier.whl>``. 

531 La différence entre les deux ooptions tient aux environnements virtuels, voir 

532 `Python virtual environments <http://astropy.readthedocs.org/en/stable/development/workflow/virtual_pythons.html>`_. 

533 """ 

534 from pyquickhelper.pycode.pip_helper import get_package_info # pylint: disable=C0415 

535 return get_package_info(name) 

536 

537 

538def get_month_name(date): 

539 """ 

540 returns the month name for a give date 

541 

542 :param date: datatime 

543 :return: month name 

544 

545 .. faqref:: 

546 :tag: python 

547 :title: Récupérer le nom du mois à partir d'une date 

548 

549 .. runpython:: 

550 :showcode: 

551 

552 import datetime 

553 dt = datetime.datetime(2016, 1, 1) 

554 print(dt.strftime("%B")) 

555 """ 

556 return date.strftime("%B") 

557 

558 

559def get_day_name(date): 

560 """ 

561 returns the day name for a give date 

562 

563 :param date: datatime 

564 :return: month name 

565 

566 .. faqref:: 

567 :tag: python 

568 :title: Récupérer le nom du jour à partir d'une date 

569 

570 .. runpython:: 

571 :showcode: 

572 

573 import datetime 

574 dt = datetime.datetime(2016, 1, 1) 

575 print(dt.strftime("%A")) 

576 """ 

577 return date.strftime("%A") 

578 

579 

580def class_getitem(): 

581 """ 

582 This function shows how to enable an expression such as 

583 `A[1]` where `A` is a class type and not an instance. 

584 This can be done through `__class_getitem__ 

585 <https://docs.python.org/3/reference/datamodel.html#object.__class_getitem__>`_. 

586 """ 

587 

588 class A: 

589 def __init__(self): 

590 pass 

591 

592 @classmethod 

593 def get(cls, index): 

594 if index == 1: 

595 return A1 

596 if index == 2: 

597 return A2 

598 assert False # pragma: no cover 

599 

600 @classmethod 

601 def __class_getitem__(cls, index): 

602 return cls.get(index) 

603 

604 def __getitem__(self, index): 

605 return "i[%d]" % index 

606 

607 class A1(A): 

608 def __init__(self): 

609 A.__init__(self) 

610 

611 class A2(A): 

612 def __init__(self): 

613 A.__init__(self) 

614 

615 a = A() 

616 assert a[5] == "i[5]" 

617 assert a.__class__.__name__ == "A" 

618 

619 a = A.get(1)() 

620 assert a.__class__.__name__ == "A1" 

621 

622 a = A[1]() 

623 assert a.__class__.__name__ == "A1" 

624 

625 a = A[2]() 

626 assert a.__class__.__name__ == "A2"