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
« 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.
7"""
9import os
10import io
11import re
14def entier_grande_taille():
15 """
17 .. faqref::
18 :tag: python
19 :title: Quel est l'entier le plus grand ?
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.
27 ::
29 import random,sys
30 x = random.getrandbits(2048)
31 print(type(x),x)
33 Qui affiche ::
35 <class 'int'> 2882159224557107513165483098383814837021447484558010147211921304219017212673656549681269862792029...
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>`_).
43 .. faqref::
44 :tag: python
45 :title: Tabulations ou espace ?
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
56def difference_div():
57 """
58 .. faqref::
59 :tag: python
60 :title: Quelle est la différence entre / et // - division ?
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.
67 .. runpython::
68 :showcode:
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)
76 Le reste d'une division entière est obtenue avec l'opérateur ``%``.
78 .. runpython::
79 :showcode:
81 print( 5 % 2 ) # affiche 1
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
94def python_path():
95 """
96 .. faqref::
97 :tag: module
98 :title: Comment éviter sys.path.append... quand on développe un module ?
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) :
106 ::
108 import sys
109 sys.path.append("c:/moncode/monmodule/src")
110 import monmodule
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 :
120 ::
122 set PYTHON_PATH=%PYTHON_PATH%;c:\\moncode\\monmodule\\src
123 """
124 return os.environ.get("PYTHON_PATH", "")
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).
132 :param a: n'importe quel objet
133 :param b: n'importe quel objet
134 :return: ``True`` ou ``False``
136 .. faqref::
137 :tag: python
138 :title: Qu'est-ce qu'un type immuable ou immutable ?
139 :lid: faq-py-immutable
141 Une variable de type *immuable* ne peut être modifiée. Cela concerne principalement :
143 - ``int``, ``float``, ``str``, ``tuple``
145 Si une variable est de type *immuable*, lorsqu'on effectue une opération,
146 on créé implicitement une copie de l'objet.
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).
153 Par exemple ::
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)
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]
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 ::
173 a = [1, 2]
174 b = a
175 a [0] = -1
176 print(a) # --> [-1, 2]
177 print(b) # --> [-1, 2]
179 Pour copier une liste, il faut expliciter la demander ::
181 a = [1, 2]
182 b = list(a)
183 a [0] = -1
184 print(a) # --> [-1, 2]
185 print(b) # --> [1, 2]
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 :
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>`_
206 Une instance de classe est mutable. Il est possible de la rendre
207 immutable par quelques astuces :
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/>`_
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 ::
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]]
225 l1 [0] = [10,10]
226 print(l1,l2) # --> [[10, 10], [2, 3]] [['##', 1], [2, 3]]
228 La copie intégrale recopie également les objets inclus ::
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]]
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)
241def stringio(text):
242 """
243 returns a StringIO object on a text
245 :param text: any text
246 :return: StringIO object
248 .. faqref::
249 :tag: python
250 :title: A quoi sert un ``StringIO`` ?
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.
256 ::
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
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.
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.
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.
280 ::
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
289 La fonction ``processus_quotidien`` fonctionne pour des données en mémoire
290 et sur un fichier.
292 ::
294 fichier = __file__
295 f = open(fichier,"r")
296 nb = processus_quotidien(f)
297 print(nb)
299 text = "ligne1\nligne2"
300 st = io.StringIO(text)
301 nb = processus_quotidien(st)
302 print(nb)
303 """
304 return io.StringIO(text)
307def property_example():
308 """
310 .. faqref::
311 :tag: class
312 :title: property
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.
318 ::
320 class ClasseAvecProperty:
322 def __init__(self,x,y):
323 self._x, self._y = x,y
325 @property
326 def x(self):
327 return self._x
329 @property
330 def y(self):
331 return self._y
333 @property
334 def norm2(self):
335 return self._y**2 + self._x**2
337 c = ClasseAvecProperty(1,2)
338 print(c.x)
339 print(c.y)
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::
344 c.x = 5
346 Cela déclenche l'erreur::
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
353 On fait cela parce que l'écriture est plus courte et que cela
354 évite certaines erreurs.
355 """
356 pass
359def enumerate_regex_search(exp, text):
360 """
361 Cette fonction itère sur les différentes occurences d'une expression régulière.
363 :param exp: expression régulière
364 :param text: text à parser
365 :return: itérateur
367 .. faqref::
368 :tag: regex
369 :title: Comment itérer sur les résultats d'une expression régulière ?
371 On utilise la méthode `finditer <https://docs.python.org/3/library/re.html#re.regex.finditer>`_.
373 ::
375 found = exp.search(text)
376 for m in exp.finditer(text):
377 # ...
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
389def sortable_class(cl):
390 """
391 .. faqref::
392 :tag: class
393 :title: Classe sortable
395 Il faut prononcer *sortable* à l'anglaise. Comment rendre une classe
396 *sortable* ? Pour faire simple, on veut écrire ::
398 l = [ o1, o2 ]
399 l.sort()
401 Où ``o1`` et ``o2`` sont des objets d'une classe
402 que vous avez définie ::
404 class MaClasse:
406 ...
408 Pour que cela fonctionne, il suffit juste
409 de surcharger l'opérateur ``<`` ou plus exactement
410 ``__lt__``. Par exemple ::
412 class MaClasse:
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
428def list_of_installed_packages():
429 """
430 calls ``pip list`` to retrieve the list of packages
432 .. faqref::
433 :tag: module
434 :title: Obtenir des informations sur les packages installés
436 Le module `pip <https://pip.pypa.io/en/stable/>`_ retourne des informations
437 sur n'importe quel module installé, sa version, sa license ::
439 pip show pandas
441 On peut également l'obtenir depuis l'interpréteur python ::
443 import pip
444 pip.main(["show", "pandas"])
446 Exemple ::
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
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 ::
462 docutils==0.11
463 Jinja2==2.7.2
464 MarkupSafe==0.19
465 Pygments==1.6
466 Sphinx==1.2.2
468 Ce qu'on utilise pour répliquer l'environnement de la manière suivante ::
470 pip freeze > requirements.txt
471 pip install -r requirements.txt
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()
481def information_about_package(name):
482 """
483 calls ``pip show`` to retrieve information about packages
485 .. faqref::
486 :tag: module
487 :title: Récupérer la liste des modules installés
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 ::
492 pip list
494 On peut également l'obtenir depuis l'interpréteur python ::
496 import pip
497 pip.main(["list"])
499 .. faqref::
500 :tag: python
501 :title: Pourquoi l'installation de pandas (ou numpy) ne marche pas sous Windows avec pip ?
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 ...
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>`_).
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 ::
525 conda install <module_compile>
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)
538def get_month_name(date):
539 """
540 returns the month name for a give date
542 :param date: datatime
543 :return: month name
545 .. faqref::
546 :tag: python
547 :title: Récupérer le nom du mois à partir d'une date
549 .. runpython::
550 :showcode:
552 import datetime
553 dt = datetime.datetime(2016, 1, 1)
554 print(dt.strftime("%B"))
555 """
556 return date.strftime("%B")
559def get_day_name(date):
560 """
561 returns the day name for a give date
563 :param date: datatime
564 :return: month name
566 .. faqref::
567 :tag: python
568 :title: Récupérer le nom du jour à partir d'une date
570 .. runpython::
571 :showcode:
573 import datetime
574 dt = datetime.datetime(2016, 1, 1)
575 print(dt.strftime("%A"))
576 """
577 return date.strftime("%A")
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 """
588 class A:
589 def __init__(self):
590 pass
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
600 @classmethod
601 def __class_getitem__(cls, index):
602 return cls.get(index)
604 def __getitem__(self, index):
605 return "i[%d]" % index
607 class A1(A):
608 def __init__(self):
609 A.__init__(self)
611 class A2(A):
612 def __init__(self):
613 A.__init__(self)
615 a = A()
616 assert a[5] == "i[5]"
617 assert a.__class__.__name__ == "A"
619 a = A.get(1)()
620 assert a.__class__.__name__ == "A1"
622 a = A[1]()
623 assert a.__class__.__name__ == "A1"
625 a = A[2]()
626 assert a.__class__.__name__ == "A2"