Site de Jean-Michel RICHER

Maître de Conférences en Informatique à l'Université d'Angers

Ce site est en cours de reconstruction certains liens peuvent ne pas fonctionner ou certaines images peuvent ne pas s'afficher.


stacks
counter_1    accueil counter_2    cours counter_3   travaux pratiques

1. Python - Le Langage

1.1. Historique

Le langage Python a été créé à partir de 1980 par Guido van Rossum et le nom Python fait référence au Monty Python's Flying Circus.

La première version (Python 0.9.0) date de Février 1991 et il a évolué jusqu'à la version 3 (Décembre 2008) pour gagner en maturité. Il est souvent enseigné comme premier langage de programmation, grâce à sa lisibilité et sa simplicité malgré quelques défauts liés à sa syntaxe et sa lenteur d'interprétation, son comportement erratique.

Python doit son succès :

  • à sa syntaxe claire et lisible
  • à un large écosystème de bibliothèques couvrant des domaines variés comme l'intelligence artificielle, le calcul scientifique, ou encore le développement web
  • une communauté dynamique, en effet, Python bénéficie d'une communauté active qui contribue à son développement et à son adoption
  • au fait qu'il permet de se concentrer sur les traitement des données et non sur des tâches de bas niveau (allocation mémoire, contrôle des indices des tableaux/listes)

Voici rapidement quelques spécificités du langage :

  • le langage est interprété
  • le typage est dynamique
  • la casse est discriminatoire : on distingue majuscules et minuscules ce qui implique que x et X sont deux choses différentes
  • on utilise les tabulations (ou les espaces) pour indenter le code et faire apparaître sa structure

1.2. Documentation

1.3. Editeur

L'indentation en Python est primordiale car elle fait apparaître la structure du code mais est également utilisée pour savoir à quel bloc une instruction appartient.

Certain éditeurs remplacent les tabulations par des espaces ce qui peut amener Python à générer des erreurs de syntaxe.

Il est donc conseillé d'utiliser toujours le même environnement de développement (IDE). Parmi les IDE les plus populaires pour Python, on trouve :

  • Visual Studio Code (Microsoft, Python : IntelliSense, débogage, Jupyter Notebook)
  • PyCharm (JetBrains - Editeur complet avec autocomplétion, débogage, refactorisation, gestion des tests et virtualenvs
  • Eclipse (Eclipse.org avec le plugin PyDev, environnement complet avec autocomplétion, refactorisation et débogage)
  • Spyder (Anaconda, Spécialisé pour la science des données et le calcul scientifique)
  • Netbeans (Apache - Extensible pour Python avec des plugins)
  • Jupyter Notebook / JupyterLab (Interface web interactive pour l'exécution de code, idéal pour les données et la visualisation. )

1.4. Caractéristiques du langage

1.4.1. langage interprété

On distingue les langages interprétés (PHP, Python, Javascript) des langages compilés (C, C++, Fortran, Pascal).

Pour un langage compilé, un programme appelé compilateur transforme le code en instructions exécutables par le microprocesseur de l'ordinateur.

Pour un langage interprété, un programme appelé interpréteur lit une instruction, l'exécute, puis passe à la suivante.

Le constat est qu'un langage compilé génère du code qui s'exécute beaucoup plus rapidement qu'un langage interprété (cf fractales de Mandelbrot, facteur d'amélioration de 420 / 6 = 70).

On peut, pour mieux appréhender ce concept, utiliser une analogie : imaginons que vous ne parliez que français et que vous soyez amené à consulter un livre en russe. Deux possibilités s'offrent à vous :

  • soit faire traduire une bonne fois pour toute le livre du russe vers le français (compilation)
  • soit demander à un interpréteur de vous traduire en direct le passage que vous désirez lire, cela prendra plus de temps et à chaque fois que vous aurez besoin de lire un passage il faudra faire appel une nouvelle fois à l'interprète

1.4.2. typage dynamique

En informatique, on distingue le typage dynamique du typage statique.

Le typage consiste à attribuer un type (booléen, entier, chaîne, réel, ...) à une variable afin de savoir comment traiter la variable.

1 + 2 => 3		int + int -> int

"bonjour " + "Jean-Michel" => "bonjour Jean-Michel" (str + str -> str, concaténation)

1 + "bonjour" => ?
TypeError: unsupported operand type(s) for +: 'int' and 'str'

"bonjour" + 1 =?> "bonjour1"
TypeError: can only concatenate str (not "int") to str

"bonjour" + str(1) => "bonjour1"
  • Le typage statique attribue un seul type à une variable pour la durée de son existence.
  • Le typage dynamique consiste à modifier le type d'une variable lorsqu'on lui affecte une valeur.
x = 1 (entier, int)
y = 3.14 (réel, float)
s = "bonjour" (chaine, str)

Notons que le typage dynamique est une notion qui est associée aux langages interprétés.

Par exemple en Python :

>>> x = 10
>>> type(x)
<class 'int'>
>>> id(x)	(adresse de x en mémoire)
11753896
>>> x = 3.14
>>> type(x)
<class 'float'>
>>> x = "coucou"
>>> type(x)
<class 'str'>
>>> x = [1,2,3]
>>> type(x)
<class 'list'>
>>> id(x)
123902002645824

1.5. Avantages et inconvénients

1.5.1. Avantages

  • Python est un langage assez simple que l'on apprend facilement
  • Python dispose de nombreuses bibliothèques (numpy, scipy, pyplot, pandas, scikitlearn, tensorflow, pytorch, django, ...) : le développement d'une application est rapide
  • communauté de développement très importante et active
  • portabilité : tout système d'exploitation

1.5.2. inconvénients

  • Python est lent car interprété : cependant il peut être amélioré avec des librairies comme Cython, PyPy (Just-In-Time Compiler)
  • Python est gourmand en mémoire
  • pas forcément adapté à des projets lourds
  • comportement erratique (i.e. qui ne manifeste aucune tendance cohérente)
    Afficher le code    ens/l1/python1/langage/python_erratique.py
    1. # ===================================================================
    2. # créer une matrice
    3. # l'écriture suivante pose problème car 'ligne' est trois fois
    4. # la même variable, donc si on modifie un élément de ligne il
    5. # est répercuté sur les autres lignes
    6. # ===================================================================
    7. ligne = [0, 0, 0, 0, 0]
    8. matrice = [ ligne, ligne, ligne ]
    9.  
    10. matrice[0][3] = 1
    11. print(matrice)
    12. # [[0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0]]
    13.  
    14. # ===================================================================
    15. # oubli
    16. # au lieu d'écrire t[i] == True, on a écrit :
    17. # mais Python n'indique aucune erreur !!
    18. # ===================================================================
    19. i = 1
    20. if [i] == True:
    21.   print("ici")
    22. else:
    23.   print("non, ici !")
    24.  
    25. # ===================================================================
    26. #
    27. # modification d'un élément répercutée sur la liste passée en
    28. # paramètre dans f(x) : ok
    29. #
    30. # ===================================================================
    31. def f(x):
    32.     x[3] = 99
    33.  
    34. z = [0 for _ in range(5)]
    35.  
    36. print(z) # [0, 0, 0, 0, 0]
    37. f(z)
    38. print(z) # [0, 0, 0, 99, 0] OK !
    39.  
    40. # ===================================================================
    41. #
    42. # modification de la structure non répercutée sur la liste x : ko
    43. #
    44. # ===================================================================
    45. def power(x, d):
    46.     if d <= 0:
    47.         for i in range(len(x)):
    48.             x[i] = 0
    49.  
    50.     x = x[d:]           # x = [0,99,0]
    51.     x = x + [0] * d     # x = [0,99,0,0,0]
    52.     # mais manque un "return x"
    53.    
    54. power(z, 2)
    55. print(z) # [0, 0, 0, 99, 0] au lieu de [ 0, 99, 0, 0, 0]
    56.  
    57. # ===================================================================
    58. #
    59. # objet mutable en paramètre
    60. #
    61. # ===================================================================
    62. def func(a, L=[]):
    63.   for i in range(a):
    64.     L.append(i)
    65.   return L
    66.  
    67. # premier appel, L est créée puis vaut [0,1] 
    68. print(func(2))
    69. # second appel, L n'est pas recréée donc L = [0, 1, 0, 1, 2]
    70. print(func(3))
    71.  
    72.  

1.6. Les types de données

Nous allons nous intéresser principalement aux types de données suivants :

  • booléens (bool) : True, False
  • entiers (int) : 1234, -789999
  • range (intervalle entiers - range(a,b,c)) : range(1,10), range(20,1,-1)
  • chaînes de caractères ("str") : "bonjour", f"x={x}", b"hello"
  • nombres flottants (réels - float)
  • listes ($≈$ tableaux [ list ])
  • les tuples (listes non modifiables ( tuple ))
  • dictionnaires (mapping - { dict })
  • ensembles ({ set })

Il existe également d'autres types que nous ne verrons pas dans ce cours : complex, function, frozenset, bytes, bytearray, memoryview, ...

1.6.1. Conversion de type

Il suffit généralement de préfixer la valeur avec le nom du type :

# chaine vers entier
entier = int("125")

# entier vers réel
reel = float(123)

# réel vers entier => 3
entier = int(3.14)

# ensemble vers liste
liste = list({1,2,3})

# liste vers ensemble => { 1,2,3 }
ensemble = set([1, 2, 3, 3, 3, 2, 1, 3])

1.6.2. Booléens (bool)

Les booléens sont des valeurs représentant la vérité : True ou False.

# Exemple
est_vrai = True
print(type(est_vrai))  # Résultat : <class 'bool'>

1.6.3. Entiers (int)

Les nombres entiers représentent des valeurs numériques sans partie décimale.

# Exemple
nombre = 42
print(type(nombre))  # Résultat : <class 'int'>

1.6.4. Range ou intervalle d'entiers

range(b) représente l'intervalle d'entiers $[0,b[$ de $0$ à $b-1$

range(a,b) représente l'intervalle d'entiers $[a,b[$, de $a$ à $b-1$

range(a,b,c) représente l'intervalle d'entiers $[a,b[$ tel que les nombres qu'il représente sont $a + i × c$ jusqu'à $b-1$

range est utilisé avec les boucles for.

# Exemple
r = range(10)
print(list(r)) # Résultat : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

r = range(1,10)
print(list(r))  # Résultat : [1, 2, 3, 4, 5, 6, 7, 8, 9], <class 'range'>

r = range(4,1,-1)
print(list(r))  # Résultat : [4,3,2]

1.6.5. Nombres flottants (float)

Les flottants représentent des nombres réels (cf. flottants).

# Exemple
nombre_decimal = 3.14
epsilon = 8.91e-7
print(type(nombre_decimal))  # Résultat : <class 'float'>

Dans la version 3 de Python, on utilise la représentation IEEE 754 pour les nombres en virgule flottante en double précision :

  • valeurs maximales : $±1.7976931348623157×10^308$ (sys.float_info.max)
  • valeurs minimales : $±2.2250738585072014×10^{−308}$ (sys.float_info.min)
  • précision de 15 à 17 chiffres significatifs

1.6.6. Chaînes de caractères (str)

Les chaînes sont utilisées pour représenter du texte.

# Exemple
texte = "Bonjour, Python !"
print(type(texte))  # Résultat : <class 'str'>

Les chaînes sur plusieurs lignes sont introduites par des triples guillemets simples ou doubles :

# Exemple
une_longue_chaine = '''Bonjour, Python !
voici une chaine sur 
plusieurs lignes'''
print(type(une_longue_chaine))  # Résultat : <class 'str'>

Un caractère est une chaîne composée d'un seul caractère ou de longueur 1.

On obtient la longueur d'une chaîne grâce à la fonction len().

Il existe deux fonctions importantes pour les caractères :

  • ord("c") donne la valeur ASCII/UTF-8 du caractère
  • chr(int) transforme l'entier en un caractère
ord("A")
65
ord("€")
8364 
print(chr(65))
A
print(chr(8364))
€

Le package string contient un nombre important de fonctions qui permettent de gérer les chaînes de caractères.

Le type string dispose également de nombreuses fonctions (cf lien) : split, strip, upper, lower, swapcase, startswith, join, isalpha, isdigit, ...

Enfin, il existe deux autres types de chaînes de caractères :

  • les b-strings (ou chaînes d'octets) sont créées en ajoutant un préfixe b avant les guillemets : b"...". Elles contiennent une séquence brute d'octets (valeurs entre 0 et 255) qui ne sont pas directement compatible avec les chaînes unicode.
  • les f-strings (formatted - introduites dans Python 3.6) permettent d'insérer des expressions Python directement dans une chaîne en utilisant des accolades {}. Elles sont créées avec un préfixe f ou F.
x = 1
pi = 3.14
print(f"{x} {pi/60} {f(3)}") # Résultat 1 0.052333333333333336 9

Par exemple on peut formater les nombres de différentes manières :

x=3
y=4
pi=3.1415927

# x sur 6 caractères, y sur 6 caractères mais précédé par des 0
# pi avec 4 chiffres après la virgule (arrondi)

print(f"{x:6d} {y:06d} {pi:.4f}") 

# donne
# ␣␣␣␣␣3 000004 3.1416

1.6.7. Listes (list)

Les listes sont des collections ordonnées et modifiables. On peut aussi les qualifier de tableaux car on peut en accéder les éléments par leur indice entier.

Une liste peut contenir des éléments de types différents.

# Exemple
ma_liste = [1, 2, 3, "Python", True]
print(type(ma_liste))  # Résultat : <class 'list'>
print(ma_liste[3]) # Résultat : Python

On obtient la longueur d'une liste grâce à la fonction len().

# Exemple
ma_liste = [1, 2, 3, "Python", True]
print(len(ma_liste))  # Résultat 5

1.6.7.a  vérification de la présence d'un élément dans la liste

On utilise in pour vérifier la présence de l'élément ou not in pour vérifier l'absence de l'élément.

Afficher le code    ens/l1/python1/langage/liste_presence.py
  1. l = [1, 3, 4, 7]
  2. if 3 in l:
  3.     print("présence")
  4. if 5 not in l:
  5.     print("absence")
  6.  
présence
absence

1.6.7.b  ajout d'un élément en début de liste

On utilise soit la méthode insert(indice,élément), soit la concaténation de listes :

Afficher le code    ens/l1/python1/langage/liste_insere_debut.py
  1. liste = [ "peugeot", "renault"]
  2.  
  3. # utilisation de la méthode insert
  4. liste.insert(0, "fiat")
  5. print(liste)
  6.  
  7. # utilisation de la concaténation de listes
  8. liste = ["audi"] + liste
  9. print(liste)
  10.  
  11.  
['fiat', 'peugeot', 'renault']
['audi', 'fiat', 'peugeot', 'renault']

1.6.7.c  ajout d'un élément en fin de liste

On utilise soit la méthode append(élément), soit la concaténation de listes :

Afficher le code    ens/l1/python1/langage/liste_insere_fin.py
  1. liste = [ "peugeot", "renault"]
  2.  
  3. # utilisation de append()
  4. liste.append("toyota")
  5. print(liste)
  6.  
  7. # utilisation de la concaténation de liste
  8. liste = liste + ["volkswagen"]
  9. print(liste)
  10.  
['peugeot', 'renault', 'toyota']
['peugeot', 'renault', 'toyota', 'volkswagen']

1.6.7.d  ajout d'un élément dans la liste

On utilise soit la méthode insert(indice,élément), soit la concaténation de listes :

Afficher le code    ens/l1/python1/langage/liste_insere_milieu.py
  1. liste = [ "audi", "fiat", "peugeot", "renault"]
  2. liste.insert(1, "citroën")
  3. print(liste)
  4.  
  5. liste = [ "audi", "fiat", "peugeot", "renault"]
  6. liste = liste[:2] + ["hyundai"] + liste[-2:]
  7. print(liste)
  8.  
['audi', 'citroën', 'fiat', 'peugeot', 'renault']
['audi', 'fiat', 'hyundai', 'peugeot', 'renault']

1.6.7.e  suppression d'un élément en début de liste

On utilise soit l'instruction del, soit la méthode pop(0) :

Afficher le code    ens/l1/python1/langage/liste_supprime_debut.py
  1. liste = [ "audi", "fiat", "peugeot", "renault"]
  2. del liste[0]
  3. print(liste)
  4.  
  5. liste.pop(0)
  6. print(liste)
  7.  
['fiat', 'peugeot', 'renault']
['peugeot', 'renault']

1.6.7.f  suppression d'un élément en fin de liste

On utilise soit l'instruction del, soit la méthode pop() (sans argument) :

Afficher le code    ens/l1/python1/langage/liste_supprime_fin.py
  1. liste = [ "audi", "fiat", "peugeot", "renault"]
  2. del liste[-1]
  3. print(liste)
  4.  
  5. liste.pop()
  6. print(liste)
  7.  
['audi', 'fiat', 'peugeot']
['audi', 'fiat']

1.6.7.g  suppression d'un élément dans la liste

On utilise soit l'instruction del, soit la méthode pop(n) (où $n$ est l'indice de l'élément), soit la méthode remove(élément) :

Afficher le code    ens/l1/python1/langage/liste_supprime_milieu.py
  1. liste = [ "audi", "fiat", "peugeot", "renault", "volkswagen" ]
  2.  
  3. # supprimer l'élément suivant sa valeur
  4. liste.remove("peugeot")
  5. print(liste)
  6.  
  7. # supprimer l'élément suivant son indice avec del
  8. del liste[2]
  9. print(liste)
  10.  
  11. # supprimer l'élément suivant son indice avec pop
  12. liste.pop(1)
  13. print(liste)
  14.  
  15.  
['audi', 'fiat', 'renault', 'volkswagen']
['audi', 'fiat', 'volkswagen']
['audi', 'volkswagen']

1.6.7.h  Création de listes par compréhension

La création de listes par compréhension en Python (ou list comprehension) est une syntaxe concise et élégante (qui s'apparente à la syntaxe du langage naturel) permettant de générer des listes à partir d'itérables, tout en appliquant des transformations ou des filtres. Elle remplace souvent une boucle for classique pour rendre le code plus lisible et plus compact.

La syntaxe est la suivante :

[ nouvel_élément for élément in iterable if condition]

La partie if condition est optionnelle.

Par exemple on peut écrire :

liste = [x for x in range(5)]
print(liste)  # [0, 1, 2, 3, 4]

au lieu de :

liste = []
for x in range(5):
	liste.append(x)

Voici un exemple avec deux for :

combinaisons = [(x, y) for x in range(2) for y in range(2)]
print(combinaisons)  # [(0, 0), (0, 1), (1, 0), (1, 1)]

Afin de créer une liste d'éléments constants, on peut écrire :

liste_de_zeros = [0 for _ in range(5)]
print(liste_de_zeros)  # Résultat : [0, 0, 0, 0, 0]

Voici un autre exemple avec un for et un if :

pairs = [x for x in range(10) if x % 2 == 0]
print(pairs)  # [0, 2, 4, 6, 8]

1.6.8. Tuples (tuple)

Les tuples sont des collections ordonnées mais immuables, ce sont des listes non modifiables. Elles sont introduites par (...).

# Exemple
mon_tuple = (1, 2, 3, "Python")
print(type(mon_tuple))  # Résultat : <class 'tuple'>
print(t[0]) # Résultat : 1
print(t[3]) # Résultat : Python

1.6.9. Dictionnaires (dict)

Les dictionnaires stockent des paires clé-valeur : à une clé est associée une valeur. Ils sont introduits par {...}.

# Exemple
mon_dico = {"nom": "Alice", "âge": 30}
print(type(mon_dico))  # Résultat : <class 'dict'>
print(mon_dico["nom"]) # Résultat : "Alice"
  • on peut accéder les clés grâce à .keys()
  • on peut accéder les valeurs grâce à .values()
  • on peut accéder les (clés,valeurs) grâce à .items()
mon_dico = { 1: "rouge", 2:"vert", 3:"bleu", 0:"noir" }
print(mon_dico.values())
# Résultat : dict_values(['rouge', 'vert', 'bleu', 'noir'])
print(mon_dico.keys())
# Résultat : dict_keys([1, 2, 3, 0])
print(mon_dico.items())
# Résultat : dict_items([(1, 'rouge'), (2, 'vert'), (3, 'bleu'), (0, 'noir')])

1.6.9.a  vérification de la présence d'un élément dans un dictionnaire

On utilise in pour vérifier la présence et not in pour vérifier l'absence :

Afficher le code    ens/l1/python1/langage/dict_presence.py
  1. dico = { 3: "bleu", 0 :  "noir", 2 : "vert", 1: "rouge" }
  2.  
  3. cle = 3
  4. if cle in dico:
  5.     print(dico[cle])
  6. cle = 7
  7. if cle not in dico:
  8.     print("clé absente")
  9.  
bleu
clé absente

1.6.9.b  ajout d'un élément dans un dictionnaire

On utilise dict[clé]=valeur ou la méthode update :

Afficher le code    ens/l1/python1/langage/dict_ajoute.py
  1. dico = { 3: "bleu" }
  2.  
  3. dico.update({ 0 :  "noir", 2 : "vert" })
  4. print(dico)
  5.  
  6. dico[1] = "rouge"
  7. print(dico)
{3: 'bleu', 0: 'noir', 2: 'vert'}
{3: 'bleu', 0: 'noir', 2: 'vert', 1: 'rouge'}

1.6.9.c  suppression d'un élément dans un dictionnaire

On utilise del ou la méthode pop() qui retourne la valeur associée à la clé :

Afficher le code    ens/l1/python1/langage/dict_supprime.py
  1. dico = { 3: "bleu", 0 :  "noir", 2 : "vert", 1: "rouge" }
  2.  
  3. del dico[3]
  4. print(dico)
  5.  
  6. valeur = dico.pop(1)
  7. print(valeur)
  8. print(dico)
  9.  
{0: 'noir', 2: 'vert', 1: 'rouge'}
rouge
{0: 'noir', 2: 'vert'}

1.6.10. Ensembles (set)

Les ensembles sont des collections non ordonnées de valeurs uniques.

# Exemple
mon_ensemble = {1, 2, 3, 3, 2, 1, 3}
print(mon_ensemble)  # Résultat : {1, 2, 3}
print(type(mon_ensemble))  # Résultat : <class 'set'> 

print(mon_ensemble[0]) # Résultat : 1

Les éléments d'un ensemble ne sont pas manipulable grâce à l'opérateur [], il faut transformer l'ensemble en liste ou alors parcourir les éléments de l'ensemble grâce à une boucle for.

1.6.10.a  vérification de la présence d'un élément dans un ensemble

On utilise in pour vérifier la présence et not in pour vérifier l'absence :

Afficher le code    ens/l1/python1/langage/set_presence.py
  1. ensemble = { 1, 3, 5}
  2.  
  3. if 3 in ensemble:
  4.     print("3 est présent")
  5. else:
  6.     print("3 n'est pas présent")
  7.  
  8. if 4 not in ensemble:
  9.     print("4 n'est pas présent")
  10. else:
  11.     print("4 est présent")
  12.  
3 est présent
4 n'est pas présent

1.6.10.b  ajout d'un élément dans un ensemble

On utilise :

  • soit la méthode add(élement)
  • soit la méthode update({élements})
Afficher le code    ens/l1/python1/langage/ensemble_ajoute.py
  1. ensemble = { 1, 2, 3}
  2.  
  3. ensemble.add(4)
  4. print(ensemble)
  5.  
  6. ensemble.update({1, 3, 4, 5, 6})
  7. print(ensemble)
{{1, 2, 3, 4}
{1, 2, 3, 4, 5, 6}

1.6.10.c  suppression d'un élément dans un ensemble

On utilise :

  • soit la méthode remove(élement)
  • soit la méthode discard(élement) qui ne lève pas d'erreur si l'élément n'est pas présent
  • soit la méthode pop() qui supprime un élément quelconque
Afficher le code    ens/l1/python1/langage/ensemble_manipuler.py
  1. mon_ensemble = set([ x for x in range(0,16) if x % 2 != 0])
  2. print(mon_ensemble)
  3.  
  4. if 3 in mon_ensemble:
  5.     print("3 est dans l'ensemble")
  6.  
  7. mon_ensemble.remove(3)
  8.  
  9. if 3 not in mon_ensemble:
  10.     print("3 n'est pas dans l'ensemble")
  11.  
  12. mon_ensemble.discard(3)
  13.  
  14. element = mon_ensemble.pop()
  15. print(element)
  16.  
  17. for i, x in enumerate(mon_ensemble):
  18.     print(f"{i:2} => {x}")
{1, 3, 5, 7, 9, 11, 13, 15}
3 est dans l'ensemble
3 n'est pas dans l'ensemble
1
 0 => 5
 1 => 7
 2 => 9
 3 => 11
 4 => 13
 5 => 15

1.6.11. Les fichiers

Les fichier permettent de sauvegarder de l'information sur le disque (HDD/SSD).

On utilise plusieurs notions pour la lecture ou l'écriture des fichiers. notamment :

  • with open(nom_fichier, mode) as variable
  • les exceptions pour intercepter les erreurs éventuelles (fichier absent, permissions)

Le paramètre mode peut prendre plusieurs valeurs ou la combinaison de ces valeurs :

  • b : fichier binaire
  • t : fichier texte (par défaut)
  • r : ouverture en lecture
  • w : ouverture en écriture avec remise à zéro du fichier
  • a : ouverture en écriture avec ajout en fin du fichier (append)

1.6.11.a  Ecriture dans un fichier texte

On utilise la fonction write :

Afficher le code    ens/l1/python1/langage/exemple_ecriture_fichier.py
  1. chaine = '''Bonjour le monde !
  2. lorem ipsum dolor
  3. '''
  4.  
  5. try:
  6.     # ouverture d'un flux en écriture 'w'
  7.     with open("texte.txt", "w") as fichier:
  8.         fichier.write(chaine)
  9.         print(type(fichier))
  10.         fichier.close()
  11.  
  12. # gestion des exceptions liées à 'try:'
  13. except FileNotFoundError:
  14.     print("fichier non existant")
  15. except PermissionError as e2:
  16.     print(f"{e2}")
  17. except Exception as e3:
  18.     print(f"Une erreur est survenue : {e3}")
<class '_io.TextIOWrapper'>

1.6.11.b  Lecture d'un fichier texte

On utilise read() pour lire la totalité du fichier sous forme d'une chaîne ou readlines() qui retourne une liste de lignes.

Afficher le code    ens/l1/python1/langage/exemple_lecture_fichier.py
  1.  
  2. try:
  3.     nom_fichier = "texte.txt"
  4.     # ouverture d'un flux en lecture 'r'
  5.     with open(nom_fichier, 'r', encoding='utf-8') as fichier:
  6.         # lecture du fichier texte comme une seule chaine
  7.         #texte = fichier.read()
  8.         # ou lecture comme plusieurs lignes
  9.         texte = fichier.readlines()
  10.         print(texte)
  11.         print(type(fichier))
  12.         fichier.close()
  13.  
  14. # gestion des exceptions liées à 'try:'
  15. except FileNotFoundError as e1:
  16.     print(f"{e1}")
  17.     print(f"fichier non trouvé {nom_fichier}")
  18. except Exception as e2:
  19.     print(f"Une erreur est survenue : {e2}")
  20.  
['Bonjour le monde !\n', 'lorem ipsum dolor\n']
<class '_io.TextIOWrapper'>

1.7. Les instructions

1.7.1. L'instruction if-elif-else

Le if appelée conditionnelle permet de réaliser un traitement si une condition est vraie :

Afficher le code    ens/l1/python1/langage/exemple_if.py
  1. a = 2
  2. b = 0
  3.  
  4. if a > 0 and b < 10:
  5.     print("valeurs valides")
  6. elif b == 0:
  7.     print("division par 0 impossible")
  8. else:
  9.     print("valeurs non valides")
  10.  
  11.  
valeurs valides

1.7.2. L'instruction match

Le match (encore appelé switch ou case of dans d'autres langage) et l'équivalent d'un if mais ne s'applique la plupart du temps qu'à des valeurs scalaires comme les entiers :

Afficher le code    ens/l1/python1/langage/exemple_match.py
  1. a = 2
  2.  
  3. match a:
  4.     case 1:
  5.         print("a=1")
  6.  
  7.     case 2:
  8.         print("a=2")
  9.  
  10.     case 3:
  11.         print("a=3")    
  12.  
  13.     case _:
  14.         print("valeur non valide")
  15.  
  16.  
a=2

1.7.3. L'instruction while

Le while permet de réaliser le même traitement en boucle tant qu'une condition est vraie :

Afficher le code    ens/l1/python1/langage/exemple_while.py
  1. i = 1
  2. do_not_exit = True
  3.  
  4. while i < 10 and do_not_exit:
  5.     print(i)
  6.     i = i + 3
  7.     if i * i > 20:
  8.         do_not_exit = False
  9.    
1
4	

1.7.4. L'instruction for

Le for est utilisé pour parcourir containers (list, dict) ou des itérateurs (range) :

Afficher le code    ens/l1/python1/langage/exemple_for.py
  1. # i = 0, .., 9
  2. for i in range(10):
  3.     print(i)
  4.  
  5. # i = 1, .., 9
  6. for i in range(1,10):
  7.     print(i)
  8.  
  9. l = ['renault','peugeot','fiat','volkswagen','audi']
  10.  
  11. # 'renault','peugeot',.., audi
  12. for marque in l:
  13.     print(marque)
  14.  
  15. # 0 renault, 1 peugeot, .., 4 audi
  16. for cle, marque in enumerate(l):
  17.     print(cle, marque)
  18.  
  19.  
  20. dico = { 0: 'noir', 1:'bleu', 2:'rouge', 3:'vert'}
  21.  
  22. # 0 noir, 1 bleu, .., 3 vert
  23. for cle in dico:
  24.     print(cle, dico[cle])
  25.  
  26. # 0 noir, 1 bleu, .., 3 vert
  27. for cle, valeur in dico.items():
  28.     print(cle, valeur)
  29.  
0
1
2
3
4
5
6
7
8
9

1
2
3
4
5
6
7
8
9

renault
peugeot
fiat
volkswagen
audi

0 renault
1 peugeot
2 fiat
3 volkswagen
4 audi
0 noir
1 bleu
2 rouge
3 vert
0 noir
1 bleu
2 rouge
3 vert

1.7.5. L'instruction print

Elle permet l'affichage d'information à l'écran ou l'enregistrement d'information dans un fichier au format texte. Son prototype est le suivant :

print(*args, sep=' ', end='\n', file=None, flush=False)

On affiche un ensemble d'arguments (args) qui sont des entiers, des flottant, decs chaînes, etc.

  • sep représente le séparateur entre les arguments, par défaut c'est un espace
  • end est le caractère affiché après avoir réalisé print, par défaut c'est le retour à la ligne mais on peut le remplacer par un caractère vide end='' pour continuer à écrire sur la même ligne
  • file est un fichier si on ne veut pas écrire sur la sortie standard
  • flush indique si on doit vider le flux d'affichage après print
Afficher le code    ens/l1/python1/langage/exemple_print.py
  1. print("Bonjour", "le", sep='***', end='')
  2. print("--monde--", end='!\n')
Bonjour***le--monde--!

1.7.6. L'instruction input

Elle permet de récupérer une chaîne saisie au clavier et terminée par un retour chariot qui ne sera pas pris en compte. Son format est le suivant :

input(prompt='')

Afficher le code    ens/l1/python1/langage/exemple_input.py
  1. chaine = input("saisir un entier:")
  2. entier = int(chaine)
  3. print(type(chaine), type(entier))
  4. print(entier)
  5.  
  6. chaine = input("saisir 3 prénoms: ")
  7. print(chaine)
saisir un entier:70
<class 'str'> <class 'int'>
70
saisir 3 prénoms: jean-michel karla sarah
jean-michel karla sarah

1.8. Les sous-programmes (procédures et fonctions)

Un sous-programme est introduit par le mot clé def suivi du nom du sous-programme et de ses paramètres :

  • une fonction est un sous-programme qui retourne une valeur
  • une procédure est un sous-programme qui ne retourne pas de valeur : la valeur obtenue sera None
Afficher le code    ens/l1/python1/langage/exemple_fonction.py
  1. def ma_fonction(x, y):
  2.     return x + y
  3.  
  4. print(ma_fonction(1,2))
  5.  

Il est nécessaire d'ajouter des commentaires aux sous-programmes afin d'indiquer à d'autres personnes qui voudraient les utiliser, comment ils fonctionnent :

Afficher le code    ens/l1/python1/langage/exemple_fonction_detaillee.py
  1. def addition( x, y : int) -> int:
  2.     """
  3.        Calcule la somme de x et y
  4.    """
  5.  
  6.     return x + y
  7.  
  8. def car(l : list[int]) -> int:
  9.     """
  10.        Implantation de la fonction Lisp car (Contents of
  11.        the Address part of the Register) qui retourne
  12.        la liste passée en paramètre sans son premier
  13.        élément
  14.  
  15.        Si la liste est vide une exception est levée.
  16.    """
  17.     if len(l) == 0:
  18.         raise ValueError("liste vide")
  19.    
  20.     return l[0]
  21.    
  22. def cdr(l : list[int]) -> int:
  23.     """
  24.        Implantation de la fonction Lisp cdr (Contents of
  25.        the Decrement part of the Register) qui retourne
  26.        la liste passée en paramètre sans son premier
  27.        élément
  28.  
  29.        Si la liste est vide une exception est levée.
  30.    """
  31.     if len(l) == 0:
  32.         raise ValueError("liste vide")
  33.    
  34.     x = l.pop(0)
  35.     return l
  36.  
  37. x = 1
  38. y = 2
  39. print(f"{x}+{y} = ", addition(x,y))
  40.  
  41. ma_liste=[1,2,3]
  42. premier = car(ma_liste)
  43. ma_liste = cdr(ma_liste)
  44. print(f"premier élément de la liste = {premier}")
  45. print(f"liste sans son premier élément = {ma_liste}")
  46.  

1.8.1. Valeur de retour

Une fonction peut retourner plusieurs valeurs :

Afficher le code    ens/l1/python1/langage/exemple_fonction_retour.py
  1. def quotient_reste(x, y):
  2.     return x // y, x % y
  3.  
  4. # a=quotient, b=reste
  5. a, b = quotient_reste(77,8)
  6. print(a,b)
  7.  
  8. # (quotient,reste) tuple
  9. t = quotient_reste(59,8)
  10. print(t)
  11.  
  12. # ignore la première valeur de retour
  13. _, b = quotient_reste(59,8)
  14. print(b)
  15.  
on récupère les résultats dans deux variables différentes
9 5
on récupère les résultats sous forme d'un tuple
(7, 3)
on ne récupère que le reste
3

1.8.2. Passage par valeur et par adresse

En Python, il n'existe pas de passage de paramètres par adresse à proprement parler, comme dans des langages comme C ou C++. Cependant, Python utilise un mécanisme appelé passage par objet-référence qui implique que :

  • les objets mutables (comme les listes, dictionnaires, ou objets personnalisés) peuvent être modifiés directement dans une fonction, ce qui simule un comportement proche du passage par adresse
  • les objets immuables (comme les entiers, chaînes de caractères, ou tuples) ne peuvent pas être modifiés directement dans une fonction. Si vous modifiez leur valeur, cela crée un nouvel objet, et la modification ne s'applique pas à la variable d'origine

Pour simuler un passage par adresse avec des objets immuables, on peut encapsuler la valeur dans un objet mutable comme une liste ou un dictionnaire :

Afficher le code    ens/l1/python1/langage/passage_par_adresse.py
  1. def ma_fonction(param):
  2.     """
  3.      Simulation d'un passage par adresse
  4.    """
  5.     param[0] += 1  
  6.  
  7. valeur = 10
  8. parametre = [valeur]
  9. ma_fonction(parametre)
  10. valeur = parametre[0]
  11. # Affiche 11
  12. print(valeur)  

1.8.3. Typage des paramètres et du type de retour

Le typage des paramètres et des valeurs de retour des fonctions a pour objectif :

  • d'améliorer la qualité du code (lisibilité, robustesse, maintenabilité)
  • de faciliter la collaboration et la documentation
  • d'aider à prévenir les erreurs grâce à l’analyse statique

Cependant, le typage en Python :

  • est facultatif et n’est pas enforçable à l’exécution
  • nécessite des outils externes comme mypy pour tirer pleinement partie de ses avantages
  • peut ajouter un peu de complexité ou de verbosité, surtout pour les fonctions complexes

Par exemple dans le code suivant, bien qu'on ait spécifié que la fonction addition prenait des paramètres entiers, il est toujours possible de passer des chaînes :

Afficher le code    ens/l1/python1/langage/non_enforcable_a_l_execution.py
  1. def addition(a: int, b: int) -> int:
  2.     return a + b
  3.  
  4. # Appel avec des types inattendus
  5. print(addition("1", "2"))  # Résultat : "12" (concaténation de chaînes)
  6.  

Voici quelques exemples de typage de base ainsi que l'utilisation du package typing :

Afficher le code    ens/l1/python1/langage/exemple_typage.py
  1. # -----------------------------------------------------
  2. # définition du type des paramètres
  3. def enregistrer_utilisateur(nom: str, age: int, actif: bool) -> None:
  4.     pass
  5.  
  6. # -----------------------------------------------------
  7. # cas d'un dictionnaire
  8. def transformer_dict(data: dict[str, int]) -> dict[int, str]:
  9.     return {value: key for key, value in data.items()}
  10.  
  11. # -----------------------------------------------------
  12. # utilisation de tuple
  13. def extraire_coordonnees(coordonnees: tuple[float, float]) -> float:
  14.     x, y = coordonnees
  15.     return (x**2 + y**2)**0.5
  16.  
  17. # -----------------------------------------------------
  18. # typage avec liste d'entiers
  19. def afficher_nombres(nombres: list[int] | None) -> None:
  20.     if nombres is None:
  21.         print("Pas de nombres à afficher.")
  22.     else:
  23.         print(nombres)
  24.  
  25. # -----------------------------------------------------
  26. # union de valeurs
  27. def calculer(valeur: int | float) -> float:
  28.     return valeur ** 2
  29.  
  30. # -----------------------------------------------------
  31. # Any pour les listes génériques
  32. from typing import Any, List
  33.  
  34. def afficher_elements(elements: List[Any]) -> None:
  35.     for element in elements:
  36.         print(element)
  37.  
  38. # -----------------------------------------------------
  39. # combiner List et Union
  40. from typing import Union, List
  41.  
  42. def traiter_donnees(donnees: List[Union[int, str]]) -> None:
  43.     for donnee in donnees:
  44.         if isinstance(donnee, int):
  45.             print(f"Nombre : {donnee}")
  46.         else:
  47.             print(f"Texte : {donnee}")
  48.  
  49. # -----------------------------------------------------
  50. # utilisation de Dict
  51. from typing import Dict
  52.  
  53. def process_users(users: Dict[str, int]) -> None:
  54.     for name, age in users.items():
  55.         print(f"{name} a {age} ans")
  56.  
  57. # -----------------------------------------------------
  58. # utilisation de Literal
  59. from typing import Literal
  60.  
  61. def choisir_action(action: Literal["start", "stop", "pause"]) -> None:
  62.     print(f"Action choisie : {action}")
  63.  
  64. # -----------------------------------------------------
  65. # utilisation de TypeVar pour liste générique
  66. from typing import TypeVar
  67.  
  68. T = TypeVar("T")
  69.  
  70. def inverser_liste(elements: list[T]) -> list[T]:
  71.     return elements[::-1]
  72.  
  73.  

1.8.4. Les fonctions intégrées

Une fonction intégrée est une fonction propre au langage et peut parfois être appliquée à différents types :

1.8.4.a  Les fonctions de base

  • len() : renvoie la longueur d'un objet (list, chaîne, dictionnaire)
  • type() : renvoie le type de l'objet
  • print() : affiche un message ou une valeur
  • id() : renvoie l'identifiant unique d'un objet
  • isinstance() : vérifie si un objet est une instance d'une classe

1.8.4.b  Les fonctions numériques

  • abs() : valeur absolue
  • round() : arrondi au plus proche
  • pow() : exponentiation (équivalent à **)
  • divmod() : renvoie le quotient et le reste de la division
  • min(), max() : valeurs minimale et maximale
  • sum() : somme des éléments d'un itérable

1.8.4.c  Les fonctions liées aux chaînes et objets itérables

  • all() : renvoie True si tous les éléments d'un itérable sont vrais
  • any() : renvoie True si au moins un élément est vrai
  • sorted() : renvoie une version triée d'un itérable
  • reversed() : renvoie un itérable inversé
  • enumerate() : renvoie un itérable avec indices
  • filter(): filtre les éléments d'un itérable.
  • map() : applique une fonction à tous les éléments d'un itérable
  • zip() : regroupe plusieurs itérables

1.8.4.d  Les fonctions liées aux types

  • int(), float(), str(), bool() : conversions de types
  • list(), tuple(), set(), dict() : crée ou convertit des types de collections
  • bytes(), bytearray() : manipulation des données binaires
  • complex() : crée un nombre complexe

1.8.4.e  Les fonctions liées aux entrées / sorties

  • open() : ouvre un fichier
  • input() : lit une chaîne au clavier
  • exit(), quit() : quitte le programme

1.9. Packages et import

Lorsque l'on crée un fichier Python, l'ensemble des variables globales et sous-programmes qu'il définit peuvent être réutilisés dans un autre fichier.

Imaginons que vous avons le fichier suivant fonction.py :

Afficher le code    ens/l1/python1/langage/fonction.py
  1. """
  2.   Package de calcul : définition d'une fonction f(x) et
  3.   d'une fonction f_prime qui calcule la dérivée pour
  4.   une fonction f(x) existante
  5. """
  6.  
  7. # définition d'une constante
  8. EPSILON = 1e-8
  9.  
  10.  
  11. def f(x : float) -> float:
  12.     """
  13.        Calcule f(x) = x**2 pour tout x flottant
  14.    """
  15.  
  16.     return x**2
  17.  
  18. def f_prime(x : float) -> float:
  19.     """
  20.        Calcule la dérivée de f(x) grâce à la formule
  21.  
  22.        f'(x) = (f(x+epsilon) - f(x)) / epsilon
  23.    """
  24.  
  25.     return (f(x+EPSILON) - f(x)) / EPSILON
  26.  
  27.  

Afin d'utiliser dans un autre fichier Python, les sous-programmes qu'il définit, trois possibilités s'offrent à nous :

  • utiliser le nom du fichier en tant que préfixe de référence
  • renommer le nom du fichier pour utiliser un autre préfixe : en général plus court pour avoir moins à écrire
  • importer l'ensemble des fonctions et variables globales et y faire référence sans préfixe : il ne faut pas qu'il y ait de conflit avec un autre fichier

1.9.1. import du fichier

Afficher le code    ens/l1/python1/langage/evalue_fonction_v1.py
  1. # import du fichier mais utilisation du nom du package
  2. # en tant que préfixe pour accèder aux sous-programmes
  3. # du fichier fonction.py
  4. import fonction
  5.  
  6. import numpy as np
  7.  
  8. x = np.arange(-2.0 ,2.0, 0.5)
  9. print(x)
  10.  
  11. # on utilise le préfixe 'fonction' devant f(x) et f_prime(x)
  12. y = fonction.f(x)
  13. y_prime = fonction.f_prime(x)
  14.  
  15. for i in range(len(y)):
  16.     print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")
  17.  
  18.  
[-2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5]
-2.000  4.00 -3.999999975690116
-1.500  2.25 -2.999999981767587
-1.000  1.00 -1.9999999989472883
-0.500  0.25 -0.9999999883714139
0.000  0.00 1e-08
0.500  0.25 1.0000000161269895
1.000  1.00 1.999999987845058
1.500  2.25 2.999999981767587

1.9.2. import du fichier avec renommage

Afficher le code    ens/l1/python1/langage/evalue_fonction_v2.py
  1. # import du fichier et renommage du fichier en tant que 'fn'
  2. # utilisé en tant que préfixe pour accèder aux sous-programmes
  3. # du fichier fonction.py
  4. import fonction as fn
  5.  
  6. import numpy as np
  7.  
  8. x = np.arange(-2.0 ,2.0, 0.5)
  9. print(x)
  10.  
  11. # on utilise le préfixe 'fn' devant f(x) et f_prime(x)
  12. y = fn.f(x)
  13. y_prime = fn.f_prime(x)
  14.  
  15. for i in range(len(y)):
  16.     print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")

1.9.3. import de tous les éléments du fichier

Afficher le code    ens/l1/python1/langage/evalue_fonction_v3.py
  1. # import de tous les éléments (constantes et sous-programmes)
  2. # du fichier fonction.py de manière à ne pas utiliser de préfixe
  3. from fonction import *
  4.  
  5. import numpy as np
  6.  
  7. x = np.arange(-2.0 ,2.0, 0.5)
  8. print(x)
  9.  
  10. # on utilise directement f(x) et f_prime(x)
  11. y = f(x)
  12. y_prime = f_prime(x)
  13.  
  14. for i in range(len(y)):
  15.     print(f"{x[i]:5.3f} {y[i]:5.2f} {y_prime[i]}")

1.9.4. version d'un package

Certains packages nécessitent une version particulière d'un autre package pour pouvoir fonctionner correctement.

On peut connaître la version d'un package :

Soit en ligne de commande :

\$ pip list | grep numpy
numpy                       1.26.4

Soit dans l'interpréteur Python ou n exécutant un programme :

\$ python3
Python 3.12.3 (main, Nov  6 2024, 18:32:19) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> print(numpy.__version__)
1.26.4

1.10. Amélioration de l'efficacité du code

Voici un petit exemple qui montre comment utiliser pythran afin d'améliorer le temps d'exécution d'un programme Python.

Pythran est un compilateur Python vers C++ conçu pour accélérer les calculs numériques et optimiser les performances des programmes Python, en particulier ceux utilisant des tableaux NumPy. Il transforme des fonctions Python annotées en extensions Python compilées en C++, ce qui réduit considérablement le temps d'exécution pour des tâches computationnelles lourdes.

1.10.1. Sans pythran

On considère le module suivant qui fournit une fonction somme_sin_cos :

On importe le package math de Python.

Afficher le code    ens/l1/python1/langage/calcul.py
  1. import math
  2.  
  3. # pythran export somme_sin_cos(float64[])
  4. def somme_sin_cos(t):
  5.     """
  6.        Calcul de la somme des sin(x)*cos(x)
  7.        pour tout x de la liste t
  8.    """
  9.     total = 0.0
  10.     for x in t:
  11.         total += math.sin(x) * math.cos(x)
  12.     return total
  13.  
  14.  
  15.  

Ainsi que le programme qui utilise cette fonction :

Afficher le code    ens/l1/python1/langage/utilisation_calcul.py
  1. import numpy as np
  2. import calcul  # The compiled module
  3. import time
  4.  
  5. # tableau de 100 millions de valeurs aléatoires
  6. tableau = np.random.rand(100000000)
  7.  
  8. debut = time.time()
  9. resultat = calcul.somme_sin_cos(tableau)
  10. fin = time.time()
  11.  
  12. print("temps d'exécution=", (fin-debut))
  13. print(f"résultat={resultat}")
  14.  
  15.  

Si on exécute simplement ce programme en Python sur un AMD Ryzen 5 5600G, on obtient :

\$ time python3 utilisation_calcul.py
temps d'exécution= 7.702448606491089
résultat=35401505.8430852

real	0m8,248s
user	0m8,253s
sys	0m0,753s

Soit un temps d'exécution total de $8,253$ secondes dont $7,702$ secondes pour exécuter le code de la fonction somme_sin_cos.

1.10.2. Avec pythran

Il suffit de précompiler le code grâce à la commande qui suit, ce qui crée le fichier calcul.cpython-312-x86_64-linux-gnu.so :

\$ pythran calcul.py>
\$ ls *.so
calcul.cpython-312-x86_64-linux-gnu.so

Un fichier avec l’extension .so sous Linux est une bibliothèque partagée (Shared Object). Il s'agit d’un fichier contenant du code compilé et des données qui peuvent être utilisés par plusieurs programmes en même temps. Ces fichiers sont comparables aux fichiers .dll sur Windows.

Ici cela ne fonctionne uniquement parce qu'on a ajouté une directive :

pythran export somme_sin_cos(float64[])

devant la fonction à optimiser qui indique quel type de donnée est fourni en entrée à la fonction. Pythran génère alors du code assembleur tout comme le ferait un compilateur C/C++.

Si maintenant on exécute le code comme précédemment, Python va utiliser le fichier objet partagé, mais cela est transparent pour l'utilisateur :

\$ time python3 utilisation_calcul.py
temps d'exécution= 0.9642660617828369
résultat=35404787.98554713

real	0m1,505s
user	0m1,502s
sys	0m0,761s

On met alors moins d'une seconde pour exécuter la fonction compilée.

Dans le fichier .so, on retrouve le code assembleur qui correspond au calcul :

3d80:       movsd  xmm0,QWORD PTR [r15]                  ; charger x ou t[i]
3d85:       mov    rsi,rbp 
3d88:       mov    rdi,r12
3d8b:       add    r15,0x8
3d8f:       call   2420 < sincos@plt>                    ; calculer sin(x) et cos(x)
3d94:       movsd  xmm0,QWORD PTR [rsp+0x18]               
3d9a:       mulsd  xmm0,QWORD PTR [rsp+0x10]             ; multiplier sin(x) par cos(x)
3da0:       addsd  xmm0,QWORD PTR [rsp]                  ; ajouter au résultat
3da5:       movsd  QWORD PTR [rsp],xmm0
3daa:       cmp    rbx,r15                               ; continuer tant qu'on est pas
3dad:       jne    3d80 <PyInit_calcul@@Base+0xa10>      ; à la fin du tableau
3daf:       lea    rdi,[rsp+0x40]

La fonction sincos appartient normalement à la librairie mathématique et son protoype est __sincos (double x, double *sinx, double *cosx).

Pour résumer :

 Sans Pythran   Avec Pythran 
 7,70s   0,96s 
Optimisation Pythran sur exemple avec AMD Ryzen 5 5600G

On notera cependant qu'on n'obtient pas le même résultat car les valeurs ne sont pas les mêmes dans les deux cas.