Coverage for src/ensae_teaching_cs/faq/faq_python.py: 100%
52 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-04-28 06:23 +0200
« prev ^ index » next coverage.py v7.1.0, created at 2023-04-28 06:23 +0200
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Quelques questions d'ordre général autour du langage Python.
6"""
8import os
9import io
10import re
11import urllib.request
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 .. runpython::
28 :showcode:
30 import random,sys
31 x = random.getrandbits(2048)
32 print(type(x), x)
34 Les calculs en nombre réels se font toujours avec huit octets de précision.
35 Au delà, il faut utiliser la librairie `gmpy2 <http://gmpy2.readthedocs.org/en/latest/>`_.
36 Il est également recommandé d'utiliser cette librairie pour les grands nombres entiers
37 (entre 20 et 40 chiffres). La librairie est plus rapide que l'implémentation
38 du langage Python (voir `Overview of gmpy2 <https://gmpy2.readthedocs.org/en/latest/overview.html>`_).
40 .. faqref::
41 :tag: python
42 :title: Tabulations ou espace ?
44 Il est préférable de ne pas utiliser les tabulations et de les remplacer par des espaces.
45 Lorsqu'on passe d'un Editeur à un autre, les espaces ne bougent pas. Les tabulations sont plus ou moins grandes visuellement.
46 L'essentiel est de ne pas mélanger.
47 Dans `SciTE <http://www.scintilla.org/SciTE.html>`_, il faut aller dans le menu Options / Change Indentation Settings...
48 Tous les éditeurs ont une option similaire.
49 """
50 pass
53def difference_div():
54 """
55 .. faqref::
56 :tag: python
57 :title: Quelle est la différence entre / et // - division ?
59 Le résultat de la division avec l'opérateur ``/`` est toujours réel :
60 la division de deux entiers ``1/2`` donne ``0.5``.
61 Le résultat de la division avec l'opérateur ``//`` est toujours entier.
62 Il correspond au quotient de la division.
64 ::
66 div1 = 1/2
67 div2 = 4/2
68 div3 = 1//2
69 div4 = 1.0//2.0
70 print(div1,div2,div3,div4) # affiche (0.5, 2.0, 0, 0)
72 Le reste d'une division entière est obtenue avec l'opérateur ``%``.
74 ::
76 print ( 5 % 2 ) # affiche 1
78 C'est uniquement vrai pour les version Python 3.x.
79 Pour les versions 2.x, les opérateurs ``/`` et ``//`` avaient des comportements différents
80 (voir `What’s New In Python 3.0 <https://docs.python.org/3/whatsnew/3.0.html#integers>`_).
81 """
82 div1 = 1 / 2
83 div2 = 4 / 2
84 div3 = 1 // 2
85 div4 = 1.0 // 2.0
86 return div1, div2, div3, div4
89def python_path():
90 """
91 .. faqref::
92 :tag: python
93 :title: Comment éviter sys.path.append... quand on développe un module ?
95 Lorsqu'on développe un module,
96 on ne veut pas l'installer. On ne veut pas qu'il soit présent dans le répertoire ``site-packages`` de la distribution
97 de Python car cela introduit deux versions : celle qu'on développe et celle qu'on a installer.
98 Avant, je faisais cela pour créer un petit programme utilisant mon propre module
99 (et on en trouve quelque trace dans mon code) :
101 ::
103 import sys
104 sys.path.append("c:/moncode/monmodule/src")
105 import monmodule
107 Quand je récupère un programme utilisant ce module, il me faudrait ajouter
108 ces petites lignes à chaque fois et c'est barbant.
109 Pour éviter cela, il est possible de dire à l'interpréteur Python d'aller chercher
110 ailleurs pour trouver des modules en ajoutant le chemin à la
111 `variable d'environnement <http://fr.wikipedia.org/wiki/Variable_d'environnement>`_
112 `PYTHONPATH <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH>`_.
113 Sous Windows :
115 ::
117 set PYTHON_PATH=%PYTHON_PATH%;c:\\moncode\\monmodule\\src
118 """
119 return os.environ.get("PYTHON_PATH", "")
122def test_unitaire():
123 """
124 .. faqref::
125 :tag: python
126 :title: Qu'est-ce qu'un test unitaire ?
127 :lid: faq-python-tu
129 Un `test unitaire <http://fr.wikipedia.org/wiki/Test_unitaire>`_
130 une procédure permettant de vérifier le bon fonctionnement d'une partie précise.
131 Concrètement, cela consiste à écrire une fonction qui exécute la partie de code
132 à vérifier. Cette fonction retourne ``True`` si le test est valide, c'est-à-dire
133 que la partie de code s'est comportée comme prévue : elle a retourné le résultat attendu.
134 Elle déclenche une exception si elle le code à vérifier ne se comporte pas comme prévu.
136 Par example, si on voulait écrire un test unitaire pour la fonction
137 `pow <https://docs.python.org/3/library/functions.html#pow>`_, on pourrait
138 écrire ::
140 def test_pow():
141 assert pow(2,1) == 2 # on vérifie que 2^1 == 0
142 assert pow(2,0) == 1
143 assert pow(2,-1) == 0.5
144 assert pow(2,-1) == 0.5
145 assert pow(0,0) == 1 # convention, on s'assure qu'elle ne change pas
146 assert isinstance(pow(-2,3.4), complex)
147 return True
150 **A quoi ça sert ?**
152 On écrit la fonction ``x_exp`` (:math:`=y x^{-n}`) comme suit ::
154 def x_exp(x,y,n):
155 return y / pow(x,n)
157 La fonction retourne 0 si :math:`x=y=n=0`.
158 Admettons maintenant qu'un dévelopeur veuille changer la convention :math:`0^0=1` en :math:`0^0=0`.
159 La fonction précédente produira une erreur à cause d'une division ``0/0``.
160 Un test unitaire détectera au plus tôt cette erreur.
162 Les tests unitaires garantissent la qualité d'un logiciel qui est considéré comme bonne
163 si 80% du code est couvert par un test unitaire. Lorsque plusieurs personnes
164 travaillent sur un même programme, un dévelopeur utilisera une fonction faite par un
165 autre. Il s'attend donc à ce que la fonction produise les mêmes résultats
166 avec les mêmes entrées
167 **même si on la modifie ultérieurement**.
168 Les tests unitaires servent à s'assurer qu'il n'y a pas d'erreur
169 introduite au sein des résultats intermédiaire d'une chaîne de traitement, auquel
170 cas, c'est une cascade d'erreur qui est susceptible de se produite. La source
171 d'une erreur est plus facile à trouver lorsque les tests unitaires sont nombreux.
172 """
173 assert pow(2, 1) == 2 # on vérifie que 2^1 == 0
174 assert pow(2, 0) == 1
175 assert pow(2, -1) == 0.5
176 assert pow(2, -1) == 0.5
177 assert pow(0, 0) == 1
178 assert isinstance(pow(-2, 3.4), complex)
179 return True
182def same_variable(a, b):
183 """
184 Cette fonction dit si les deux objets sont en fait le même objet (True)
185 ou non (False) s'ils sont différents (même s'ils contiennent la même information).
187 @param a n'importe quel objet
188 @param b n'importe quel objet
189 @return ``True`` ou ``False``
191 .. faqref::
192 :tag: python
193 :title: Qu'est-ce qu'un type immuable ou immutable ?
194 :lid: faq-py-immutable
196 Une variable de type *immuable* ne peut être modifiée. Cela concerne principalement :
198 - ``int``, ``float``, ``str``, ``tuple``
200 Si une variable est de type *immuable*, lorsqu'on effectue une opération,
201 on créé implicitement une copie de l'objet.
203 Les dictionnaires et les listes sont *modifiables* (ou *mutable*). Pour une variable
204 de ce type, lorsqu'on écrit ``a = b``, ``a`` et ``b`` désigne le même objet même
205 si ce sont deux noms différentes. C'est le même emplacement mémoire
206 accessible paur deux moyens (deux identifiants).
208 Par exemple ::
210 a = (2,3)
211 b = a
212 a += (4,5)
213 print( a == b ) # --> False
214 print(a,b) # --> (2, 3, 4, 5) (2, 3)
216 a = [2,3]
217 b = a
218 a += [4,5]
219 print( a == b ) # --> True
220 print(a,b) # --> [2, 3, 4, 5] [2, 3, 4, 5]
222 Dans le premier cas, le type (``tuple``) est _immutable_, l'opérateur ``+=`` cache implicitement une copie.
223 Dans le second cas, le type (``list``) est _mutable_, l'opérateur ``+=`` évite la copie
224 car la variable peut être modifiée. Même si ``b=a`` est exécutée avant l'instruction suivante,
225 elle n'a **pas** pour effet de conserver l'état de ``a`` avant l'ajout d'élément.
226 Un autre exemple ::
228 a = [1, 2]
229 b = a
230 a [0] = -1
231 print(a) # --> [-1, 2]
232 print(b) # --> [-1, 2]
234 Pour copier une liste, il faut expliciter la demander ::
236 a = [1, 2]
237 b = list(a)
238 a [0] = -1
239 print(a) # --> [-1, 2]
240 print(b) # --> [1, 2]
242 La page `Immutable Sequence Types <https://docs.python.org/3/library/stdtypes.html?highlight=immutable#immutable-sequence-types>`_
243 détaille un peu plus le type qui sont *mutable* et ceux qui sont *immutable*. Parmi les types standards :
245 * **mutable**
246 * `bool <https://docs.python.org/3/library/functions.html#bool>`_
247 * `int <https://docs.python.org/3/library/functions.html#int>`_,
248 `float <https://docs.python.org/3/library/functions.html#float>`_,
249 `complex <https://docs.python.org/3/library/functions.html#complex>`_
250 * `str <https://docs.python.org/3/library/functions.html#func-str>`_,
251 `bytes <https://docs.python.org/3/library/functions.html#bytes>`_
252 * `None <https://docs.python.org/3/library/constants.html?highlight=none#None>`_
253 * `tuple <https://docs.python.org/3/library/functions.html#func-tuple>`_,
254 `frozenset <https://docs.python.org/3/library/functions.html#func-frozenset>`_
255 * **immutable**, par défaut tous les autres types dont :
256 * `list <https://docs.python.org/3/library/functions.html#func-list>`_
257 * `dict <https://docs.python.org/3/library/functions.html#func-dict>`_
258 * `set <https://docs.python.org/3/library/functions.html#func-set>`_
259 * `bytearray <https://docs.python.org/3/library/functions.html#bytearray>`_
261 Une instance de classe est mutable. Il est possible de la rendre
262 immutable par quelques astuces :
264 * `__slots__ <https://docs.python.org/3/reference/datamodel.html?highlight=_slots__#object.__slots__>`_
265 * `How to Create Immutable Classes in Python <http://www.blog.pythonlibrary.org/2014/01/17/
266 how-to-create-immutable-classes-in-python/>`_
267 * `Ways to make a class immutable in Python <http://stackoverflow.com/questions/4996815/
268 ways-to-make-a-class-immutable-in-python>`_
269 * `freeze <https://freeze.readthedocs.org/en/latest/>`_
271 Enfin, pour les objects qui s'imbriquent les uns dans les autres, une liste de listes, une classe
272 qui incluent des dictionnaires et des listes, on distingue une copie simple d'une copie intégrale (**deepcopy**).
273 Dans le cas d'une liste de listes, la copie simple recopie uniquement la première liste ::
275 import copy
276 l1 = [ [0,1], [2,3] ]
277 l2 = copy.copy(l1)
278 l1 [0][0] = '##'
279 print(l1,l2) # --> [['##', 1], [2, 3]] [['##', 1], [2, 3]]
281 l1 [0] = [10,10]
282 print(l1,l2) # --> [[10, 10], [2, 3]] [['##', 1], [2, 3]]
284 La copie intégrale recopie également les objets inclus ::
286 import copy
287 l1 = [ [0,1], [2,3] ]
288 l2 = copy.deepcopy(l1)
289 l1 [0][0] = '##'
290 print(l1,l2) # --> [['##', 1], [2, 3]] [[0, 1], [2, 3]]
292 Les deux fonctions s'appliquent à tout object Python : `module copy <https://docs.python.org/3/library/copy.html>`_.
293 """
294 return id(a) == id(b)
297def stringio(text):
298 """
299 returns a StringIO object on a text
301 @param text any text
302 @return StringIO object
304 .. faqref::
305 :tag: python
306 :title: A quoi sert un ``StringIO`` ?
308 La plupart du temps, lorsqu'on récupère des données, elles sont sur le disque dur
309 de votre ordinateur dans un fichier texte. Lorsqu'on souhaite automatiser un processur
310 qu'on répète souvent avec ce fichier, on écrit une fonction qui prend le nom du fichier en entrée.
312 ::
314 def processus_quotidien(nom_fichier) :
315 # on compte les lignes
316 nb = 0
317 with open(nom_fichier,"r") as f :
318 for line in f :
319 nb += 1
320 return nb
322 Et puis un jour, les données ne sont plus dans un fichier mais sur Internet.
323 Le plus simple dans ce cas est de recopier ces données sur disque dur et d'appeler la même fonction.
324 Simple. Un autre les données qu'on doit télécharger font plusieurs gigaoctets. Tout télécharger prend
325 du temps pour finir pour s'apercevoir qu'elles sont corrompues. On a perdu plusieurs heures pour rien.
326 On aurait bien voulu que la fonction ``processus_quotidien`` commence à traiter les données
327 dès le début du téléchargement.
329 Pour cela, on a inventé la notion de **stream** ou **flux** qui sert d'interface entre la fonction
330 qui traite les données et la source des données. Le flux lire les données depuis n'importe quel source
331 (fichier, internet, mémoire), la fonction qui les traite n'a pas besoin d'en connaître la provenance.
333 `StringIO <https://docs.python.org/3/library/io.html#io.StringIO>`_ est un flux qui considère
334 la mémoire comme source de données.
336 ::
338 def processus_quotidien(data_stream):
339 # on compte toujours les lignes
340 nb = 0
341 for line in data_stream :
342 nb += 1
343 return nb
345 La fonction ``processus_quotidien`` fonctionne pour des données en mémoire
346 et sur un fichier.
348 ::
350 fichier = __file__
351 f = open(fichier,"r")
352 nb = processus_quotidien(f)
353 print(nb)
355 text = "ligne1\nligne2"
356 st = io.StringIO(text)
357 nb = processus_quotidien(st)
358 print(nb)
359 """
360 return io.StringIO(text)
363def property_example():
364 """
366 .. faqref::
367 :tag: python
368 :title: property
370 Une `property <https://docs.python.org/3/library/functions.html#property>`_ est
371 une écriture qui sert à transformer l'appel d'une méthode de classe
372 en un attribut.
374 ::
376 class ClasseAvecProperty:
378 def __init__(self,x,y):
379 self._x, self._y = x,y
381 @property
382 def x(self):
383 return self._x
385 @property
386 def y(self):
387 return self._y
389 @property
390 def norm2(self):
391 return self._y**2 + self._x**2
393 c = ClasseAvecProperty(1,2)
394 print(c.x)
395 print(c.y)
397 ``x`` est définit comme une méthode mais elle retourne simplement l'attribut
398 ``_x``. De cette façon, il est impossible de changer ``x`` en écrivant::
400 c.x = 5
402 Cela déclenche l'erreur::
404 Traceback (most recent call last):
405 File "faq_python.py", line 455, in <module>
406 c.x = 5
407 AttributeError: can't set attribute
409 On fait cela parce que l'écriture est plus courte et que cela
410 évite certaines erreurs.
411 """
412 pass
415def enumerate_regex_search(exp, text):
416 """
417 Cette fonction itère sur les différentes occurences d'une expression régulière.
419 @param exp expression régulière
420 @param text text à parser
421 @return itérateur
423 .. faqref::
424 :tag: python
425 :title: Comment itérer sur les résultats d'une expression régulière ?
427 On utilise la méthode `finditer <https://docs.python.org/3/library/re.html#re.regex.finditer>`_.
429 ::
431 found = exp.search(text)
432 for m in exp.finditer(text):
433 # ...
435 Voir également `Petites subtilités avec les expressions régulières en Python
436 <http://www.xavierdupre.fr/blog/2014-12-02_nojs.html>`_.
437 """
438 # found = exp.search(text)
439 if isinstance(exp, str):
440 exp = re.compile(exp)
441 for m in exp.finditer(text):
442 yield m
445def download_from_url(url, filename):
446 """
448 downloads a file given a URL and stores it as binary file
450 @param url url
451 @param filename local filename
452 @return filename
454 .. index:: dropbox
456 .. faqref::
457 :tag: python
458 :title: Télécharger un fichier depuis un notebook?
460 L'exemple suivant illustre comment télécharger puis enregister ce fichier
461 sur le disque local. Il ne faut pas que ce fichier dépasse la taille de la mémoire.
462 L'url donné en exemple est celui utilisé sur DropBox.
464 ::
466 url = "https://www.dropbox.com/[something]/[filename]?dl=1" # dl=1 is important
467 import urllib.request
468 with urllib.request.urlopen(url) as u:
469 data = u.read()
471 with open([filename], "wb") as f :
472 f.write(data)
474 L'exemple est tiré de `Download a file from Dropbox with Python <http://www.xavierdupre.fr/blog/2015-01-20_nojs.html>`_.
475 """
476 with urllib.request.urlopen(url) as u:
477 data = u.read()
479 with open(filename, "wb") as f:
480 f.write(data)
481 return filename
484def sortable_class(cl):
485 """
486 .. faqref::
487 :tag: python
488 :title: Classe sortable
490 Il faut prononcer *sortable* à l'anglaise. Comment rendre une classe
491 *sortable* ? Pour faire simple, on veut écrire ::
493 l = [ o1, o2 ]
494 l.sort()
496 Où ``o1`` et ``o2`` sont des objets d'une classe
497 que vous avez définie ::
499 class MaClasse:
501 ...
503 Pour que cela fonctionne, il suffit juste
504 de surcharger l'opérateur ``<`` ou plus exactement
505 ``__lt__``. Par exemple ::
507 class MaClasse:
509 def __lt__(self, autre_instance):
510 if self.jenesaispas < autre.jenesaispas:
511 return True
512 elif self.jenesaispas > autre.jenesaispas:
513 return False:
514 else:
515 if self.jenesaispas2 < autre.jenesaispas2:
516 return True
517 else:
518 return False
519 """
520 pass
523def list_of_installed_packages():
524 """
525 calls ``pip list`` to retrieve the list of packages
527 .. faqref::
528 :tag: python
529 :title: Obtenir des informations sur les packages installés
531 Le module :epkg:`pip` retourne des informations
532 sur n'importe quel module installé, sa version, sa license ::
534 pip show pandas
536 On peut également l'obtenir depuis l'interpréteur python ::
538 import pip
539 pip.main(["show", "pandas"])
541 Exemple ::
543 Name: pandas
544 Version: 0.16.0
545 Summary: Powerful data structures for data analysis, time series,and statistics
546 Home-page: http://pandas.pydata.org
547 Author: The PyData Development Team
548 Author-email: pydata@googlegroups.com
549 License: BSD
550 Location: c:\\python35_x64\\lib\\site-packages
551 Requires: python-dateutil, pytz, numpy
553 On utilise également ``pip freeze`` pour répliquer l'environnement
554 dans lequel on a développé un programme. `pip freeze <https://pip.pypa.io/en/latest/reference/pip_freeze.html>`_
555 produit la liste des modules avec la version utilisée ::
557 docutils==0.11
558 Jinja2==2.7.2
559 MarkupSafe==0.19
560 Pygments==1.6
561 Sphinx==1.2.2
563 Ce qu'on utilise pour répliquer l'environnement de la manière suivante ::
565 pip freeze > requirements.txt
566 pip install -r requirements.txt
568 Cette façon de faire fonctionne très bien sous Linux mais n'est pas encore
569 opérationnelle sous Windows à moins d'installer le compilateur C++ utilisée pour compiler
570 Python.
571 """
572 from pyquickhelper.pycode.pip_helper import get_packages_list
573 return get_packages_list()
576def information_about_package(name):
577 """
578 Calls ``pip show`` to retrieve information about packages.
580 .. faqref::
581 :tag: python
582 :title: Récupérer la liste des modules installés
584 Le module :epkg:`pip` permet d'installer
585 de nouveaux modules mais aussi d'obtenir la liste des packages installés ::
587 pip list
589 On peut également l'obtenir depuis l'interpréteur python ::
591 import pip
592 pip.main(["list"])
594 .. faqref::
595 :tag: python
596 :title: Pourquoi l'installation de pandas (ou numpy) ne marche pas sous Windows avec pip ?
598 Python est un langage très lent et c'est pourquoi la plupart des modules de calculs numériques
599 incluent des parties implémentées en langage C++.
600 :epkg:`numpy`, :epkg:`pandas`, :epkg:`matplotlib`,
601 :epkg:`scipy`, :epkg:`scikit-learn`,
602 ...
604 Sous Linux, le compilateur est intégré au système et l'installation de ces modules via
605 l'instruction ``pip install <module>`` met implicitement le compilateur à contribution.
606 Sous Windows, il n'existe pas de compilateur C++ par défaut à moins de l'installer.
607 Il faut faire attention alors d'utiliser exactement le même que celui utilisé
608 pour compiler Python (voir
609 `Compiling Python on Windows <https://docs.python.org/3/using/windows.html#compiling-python-on-windows>`_).
611 C'est pour cela qu'on préfère utiliser des distributions comme
612 `Anaconda <http://continuum.io/downloads#py34>`_
613 qui propose par défaut
614 une version de Python accompagnée des modules les plus utilisés. Elle propose également une façon
615 simple d'installer des modules précompilés avec l'instruction ::
617 conda install <module_compile>
618 """
619 from pyquickhelper.pycode.pip_helper import get_package_info
620 return get_package_info(name)
623def get_month_name(date):
624 """
625 returns the month name for a give date
627 @param date datatime
628 @return month name
630 .. faqref::
631 :tag: python
632 :title: Récupérer le nom du mois à partir d'une date
634 ::
636 import datetime
637 dt = datetime.datetime(2016, 1, 1)
638 print(dt.strftime("%B"))
639 """
640 return date.strftime("%B")
643def get_day_name(date):
644 """
645 returns the day name for a give date
647 @param date datatime
648 @return month name
650 .. faqref::
651 :tag: python
652 :title: Récupérer le nom du jour à partir d'une date
654 ::
656 import datetime
657 dt = datetime.datetime(2016, 1, 1)
658 print(dt.strftime("%A"))
659 """
660 return date.strftime("%A")