5. Fondamentaux de Python#
5.1. Vue d’ensemble#
Nous avons couvert beaucoup de matière assez rapidement, en nous concentrant sur des exemples.
Voyons maintenant certaines fonctionnalités fondamentales de Python de manière plus systématique.
Cette approche est moins passionnante mais aide à clarifier certains détails.
5.2. Types de données#
Les programmes informatiques suivent généralement toute une gamme de types de données.
Par exemple, 1.5 est un nombre à virgule flottante, tandis que 1 est un entier.
Les programmes doivent distinguer ces deux types pour diverses raisons.
L’une est qu’ils sont stockés différemment en mémoire.
Une autre est que les opérations arithmétiques sont différentes
Par exemple, l’arithmétique en virgule flottante est mise en œuvre sur la plupart des machines par une unité de calcul en virgule flottante (FPU) spécialisée.
En général, les flottants sont plus informatifs mais les opérations arithmétiques sur les entiers sont plus rapides et plus précises.
Python fournit de nombreux autres types de données Python intégrés, dont certains que nous avons déjà rencontrés
chaînes de caractères, listes, etc.
Apprenons-en un peu plus à leur sujet.
5.2.1. Types de données primitifs#
5.2.1.1. Valeurs booléennes#
Un type de données simple est celui des valeurs booléennes, qui peuvent être soit True, soit False
x = True
x
True
Nous pouvons vérifier le type de n’importe quel objet en mémoire à l’aide de la fonction type().
type(x)
bool
Dans la ligne de code suivante, l’interpréteur évalue l’expression à droite de = et lie y à cette valeur
y = 100 < 10
y
False
type(y)
bool
Dans les expressions arithmétiques, True est converti en 1 et False est converti en 0.
Cela s’appelle l”arithmétique booléenne et est souvent utile en programmation.
Voici quelques exemples
x + y
1
x * y
0
True + True
2
bools = [True, True, False, True] # Liste de valeurs booléennes
sum(bools)
3
5.2.1.2. Types numériques#
Les types numériques sont également des types de données primitifs importants.
Nous avons déjà vu les types integer et float.
Les nombres complexes sont un autre type de données primitif en Python
x = complex(1, 2)
y = complex(2, 1)
print(x * y)
type(x)
5j
complex
5.2.2. Conteneurs#
Python possède plusieurs types de base pour stocker des collections de données (éventuellement hétérogènes).
Nous avons déjà abordé les listes.
Un type de données apparenté est celui des tuples, qui sont des listes « immuables »
x = ('a', 'b') # Parenthèses au lieu des crochets
x = 'a', 'b' # Ou sans crochets --- la signification est identique
x
('a', 'b')
type(x)
tuple
En Python, un objet est appelé immuable si, une fois créé, l’objet ne peut plus être modifié.
Inversement, un objet est mutable s’il peut encore être modifié après sa création.
Les listes Python sont mutables
x = [1, 2]
x[0] = 10
x
[10, 2]
Mais les tuples ne le sont pas
x = (1, 2)
x[0] = 10
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[13], line 2
1 x = (1, 2)
----> 2 x[0] = 10
TypeError: 'tuple' object does not support item assignment
Nous en dirons davantage sur le rôle des données mutables et immuables un peu plus tard.
Les tuples (et les listes) peuvent être « déballés » comme suit
integers = (10, 20, 30)
x, y, z = integers
x
10
y
20
Vous avez en fait déjà vu un exemple de cela.
Le déballage de tuple est pratique et nous l’utiliserons souvent.
5.2.2.1. Notation de tranche#
Pour accéder à plusieurs éléments d’une séquence (une liste, un tuple ou une chaîne), vous pouvez utiliser la notation de tranche de Python.
Par exemple,
a = ["a", "b", "c", "d", "e"]
a[1:]
['b', 'c', 'd', 'e']
a[1:3]
['b', 'c']
La règle générale est que a[m:n] renvoie n - m éléments, en commençant par a[m].
Les nombres négatifs sont également autorisés
a[-2:] # Les deux derniers éléments de la liste
['d', 'e']
Vous pouvez également utiliser le format [start:end:step] pour spécifier le pas
a[::2]
['a', 'c', 'e']
En utilisant un pas négatif, vous pouvez renvoyer la séquence dans l’ordre inverse
a[-2::-1] # Parcourir à rebours de l'avant-dernier élément jusqu'au premier élément
['d', 'c', 'b', 'a']
La même notation de tranche fonctionne sur les tuples et les chaînes
s = 'foobar'
s[-3:] # Sélectionner les trois derniers éléments
'bar'
5.2.2.2. Ensembles et dictionnaires#
Deux autres types de conteneurs que nous devrions mentionner avant de poursuivre sont les ensembles et les dictionnaires.
Les dictionnaires ressemblent beaucoup aux listes, sauf que les éléments sont nommés au lieu d’être numérotés
d = {'name': 'Frodo', 'age': 33}
type(d)
dict
d['age']
33
Les noms 'name' et 'age' sont appelés les clés.
Les objets vers lesquels les clés sont mappées ('Frodo' et 33) sont appelés les values (valeurs).
Les ensembles sont des collections non ordonnées sans doublons, et les méthodes d’ensemble fournissent les opérations ensemblistes habituelles
s1 = {'a', 'b'}
type(s1)
set
s2 = {'b', 'c'}
s1.issubset(s2)
False
s1.intersection(s2)
{'b'}
La fonction set() crée des ensembles à partir de séquences
s3 = set(('foo', 'bar', 'foo'))
s3
{'bar', 'foo'}
5.3. Entrée et sortie#
Passons brièvement en revue la lecture et l’écriture dans des fichiers texte, en commençant par l’écriture
f = open('newfile.txt', 'w') # Ouvrir 'newfile.txt' en écriture
f.write('Testing\n') # Ici '\n' signifie nouvelle ligne
f.write('Testing again')
f.close()
Ici
La fonction intégrée
open()crée un objet fichier pour écrire dedans.write()etclose()sont tous deux des méthodes des objets fichier.
Où se trouve ce fichier que nous avons créé ?
Rappelez-vous que Python maintient une notion de répertoire de travail courant (pwd) qui peut être localisé depuis Jupyter ou IPython via
%pwd
'/home/runner/work/lecture-python-programming.fr/lecture-python-programming.fr/lectures'
Si aucun chemin n’est spécifié, c’est là que Python écrit.
Nous pouvons également utiliser Python pour lire le contenu de newline.txt comme suit
f = open('newfile.txt', 'r')
out = f.read()
out
'Testing\nTesting again'
print(out)
Testing
Testing again
En fait, l’approche recommandée dans le Python moderne est d’utiliser une instruction with pour garantir que les fichiers sont correctement acquis et libérés.
Contenir les opérations dans le même bloc améliore aussi la clarté de votre code.
Note
Ce type de bloc est formellement appelé un contexte.
Essayons de convertir les deux exemples ci-dessus en une instruction with.
Nous modifions d’abord l’exemple d’écriture
with open('newfile.txt', 'w') as f:
f.write('Testing\n')
f.write('Testing again')
Notez que nous n’avons pas besoin d’appeler la méthode close() puisque le bloc with
garantira que le flux est fermé à la fin du bloc.
Avec de légères modifications, nous pouvons également lire des fichiers en utilisant with
with open('newfile.txt', 'r') as fo:
out = fo.read()
print(out)
Testing
Testing again
Supposons maintenant que nous voulions lire une entrée depuis un fichier et écrire une sortie dans un autre.
Voici comment nous pourrions accomplir cette tâche tout en acquérant et en restituant correctement
les ressources au système d’exploitation à l’aide d’instructions with :
with open("newfile.txt", "r") as f:
file = f.readlines()
with open("output.txt", "w") as fo:
for i, line in enumerate(file):
fo.write(f'Line {i}: {line} \n')
Le fichier de sortie sera
with open('output.txt', 'r') as fo:
print(fo.read())
Line 0: Testing
Line 1: Testing again
Nous pouvons simplifier l’exemple ci-dessus en regroupant les deux instructions with sur une seule ligne
with open("newfile.txt", "r") as f, open("output2.txt", "w") as fo:
for i, line in enumerate(f):
fo.write(f'Line {i}: {line} \n')
Le fichier de sortie sera identique
with open('output2.txt', 'r') as fo:
print(fo.read())
Line 0: Testing
Line 1: Testing again
Supposons que nous voulions continuer à écrire dans le fichier existant au lieu de l’écraser.
nous pouvons passer au mode a qui signifie mode d’ajout (append)
with open('output2.txt', 'a') as fo:
fo.write('\nThis is the end of the file')
with open('output2.txt', 'r') as fo:
print(fo.read())
Line 0: Testing
Line 1: Testing again
This is the end of the file
Note
Notez que nous n’avons couvert ici que les modes r, w et a, qui sont les modes les plus couramment utilisés.
Python fournit une variété de modes
que vous pourriez expérimenter.
5.3.1. Chemins#
Notez que si newfile.txt ne se trouve pas dans le répertoire de travail courant, alors cet appel à open() échoue.
Dans ce cas, vous pouvez déplacer le fichier vers le pwd ou spécifier le chemin complet vers le fichier
f = open('insert_full_path_to_file/newfile.txt', 'r')
5.4. Itération#
L’une des tâches les plus importantes en informatique est de parcourir une séquence de données et d’effectuer une action donnée.
L’une des forces de Python est son interface simple et flexible pour ce type d’itération via
la boucle for.
5.4.1. Boucler sur différents objets#
De nombreux objets Python sont « itérables », dans le sens où l’on peut boucler dessus.
Pour donner un exemple, écrivons le fichier us_cities.txt, qui liste des villes américaines et leur population, dans le répertoire de travail courant.
%%writefile us_cities.txt
new york: 8244910
los angeles: 3819702
chicago: 2707120
houston: 2145146
philadelphia: 1536471
phoenix: 1469471
san antonio: 1359758
san diego: 1326179
dallas: 1223229
Overwriting us_cities.txt
Ici %%writefile est une commande magique de cellule IPython.
Supposons que nous voulions rendre l’information plus lisible, en mettant les noms en majuscules et en ajoutant des virgules pour marquer les milliers.
Le programme ci-dessous lit les données et effectue la conversion :
data_file = open('us_cities.txt', 'r')
for line in data_file:
city, population = line.split(':') # Déballage de tuple
city = city.title() # Mettre en majuscule les noms de ville
population = f'{int(population):,}' # Ajouter des virgules aux nombres
print(city.ljust(15) + population)
data_file.close()
New York 8,244,910
Los Angeles 3,819,702
Chicago 2,707,120
Houston 2,145,146
Philadelphia 1,536,471
Phoenix 1,469,471
San Antonio 1,359,758
San Diego 1,326,179
Dallas 1,223,229
Ici f' est une f-string utilisée pour insérer des variables dans des chaînes.
Le reformatage de chaque ligne est le résultat de trois méthodes de chaîne différentes, dont les détails peuvent être laissés pour plus tard.
La partie intéressante de ce programme pour nous est la ligne 2, qui montre que
L’objet fichier
data_fileest itérable, dans le sens où il peut être placé à droite deindans une bouclefor.L’itération parcourt chaque ligne du fichier.
Cela conduit à la syntaxe propre et pratique présentée dans notre programme.
De nombreux autres types d’objets sont itérables, et nous en aborderons certains plus tard.
5.4.2. Boucler sans indices#
Une chose que vous avez peut-être remarquée est que Python tend à privilégier les boucles sans indexation explicite.
Par exemple,
x_values = [1, 2, 3] # Un x itérable
for x in x_values:
print(x * x)
1
4
9
est préféré à
for i in range(len(x_values)):
print(x_values[i] * x_values[i])
1
4
9
Lorsque vous comparez ces deux alternatives, vous pouvez voir pourquoi la première est préférée.
Python fournit certaines facilités pour simplifier les boucles sans indices.
L’une est zip(), qui est utilisée pour parcourir des paires provenant de deux séquences.
Par exemple, essayez d’exécuter le code suivant
countries = ('Japan', 'Korea', 'China')
cities = ('Tokyo', 'Seoul', 'Beijing')
for country, city in zip(countries, cities):
print(f'The capital of {country} is {city}')
The capital of Japan is Tokyo
The capital of Korea is Seoul
The capital of China is Beijing
La fonction zip() est également utile pour créer des dictionnaires — par
exemple
names = ['Tom', 'John']
marks = ['E', 'F']
dict(zip(names, marks))
{'Tom': 'E', 'John': 'F'}
Si nous avons réellement besoin de l’indice d’une liste, une option est d’utiliser enumerate().
Pour comprendre ce que fait enumerate(), considérez l’exemple suivant
letter_list = ['a', 'b', 'c']
for index, letter in enumerate(letter_list):
print(f"letter_list[{index}] = '{letter}'")
letter_list[0] = 'a'
letter_list[1] = 'b'
letter_list[2] = 'c'
5.4.3. Compréhensions de liste#
Nous pouvons également simplifier considérablement le code pour générer la liste de tirages aléatoires en utilisant quelque chose appelé une compréhension de liste.
Les compréhensions de liste sont un outil Python élégant pour créer des listes.
Considérez l’exemple suivant, où la compréhension de liste se trouve du côté droit de la deuxième ligne
animals = ['dog', 'cat', 'bird']
plurals = [animal + 's' for animal in animals]
plurals
['dogs', 'cats', 'birds']
Voici un autre exemple
range(8)
range(0, 8)
doubles = [2 * x for x in range(8)]
doubles
[0, 2, 4, 6, 8, 10, 12, 14]
5.5. Comparaisons et opérateurs logiques#
5.5.1. Comparaisons#
De nombreux types d’expressions différents s’évaluent à l’une des valeurs booléennes (c’est-à-dire True ou False).
Un type courant est celui des comparaisons, telles que
x, y = 1, 2
x < y
True
x > y
False
L’une des belles fonctionnalités de Python est que nous pouvons chaîner les inégalités
1 < 2 < 3
True
1 <= 2 <= 3
True
Comme nous l’avons vu précédemment, pour tester l’égalité nous utilisons ==
x = 1 # Affectation
x == 2 # Comparaison
False
Pour « différent de » utilisez !=
1 != 2
True
Notez que lors du test de conditions, nous pouvons utiliser n’importe quelle expression Python valide
x = 'yes' if 42 else 'no'
x
'yes'
x = 'yes' if [] else 'no'
x
'no'
Que se passe-t-il ici ?
La règle est :
Les expressions qui s’évaluent à zéro, à des séquences ou conteneurs vides (chaînes, listes, etc.) et
Nonesont toutes équivalentes àFalse.par exemple,
[]et()sont équivalents àFalsedans une clauseif
Toutes les autres valeurs sont équivalentes à
True.par exemple,
42est équivalent àTruedans une clauseif
5.5.2. Combiner des expressions#
Nous pouvons combiner des expressions à l’aide de and, or et not.
Ce sont les connecteurs logiques standard (conjonction, disjonction et négation)
1 < 2 and 'f' in 'foo'
True
1 < 2 and 'g' in 'foo'
False
1 < 2 or 'g' in 'foo'
True
not True
False
not not True
True
Rappelez-vous
P and QestTruesi les deux sontTrue, sinonFalseP or QestFalsesi les deux sontFalse, sinonTrue
Nous pouvons également utiliser all() et any() pour tester une séquence d’expressions
all([1 <= 2 <= 3, 5 <= 6 <= 7])
True
all([1 <= 2 <= 3, "a" in "letter"])
False
any([1 <= 2 <= 3, "a" in "letter"])
True
Note
all()renvoieTruelorsque toutes les valeurs/expressions booléennes de la séquence sontTrueany()renvoieTruelorsque l’une quelconque des valeurs/expressions booléennes de la séquence estTrue
5.6. Style de codage et documentation#
Un style de codage cohérent et l’utilisation de la documentation peuvent rendre le code plus facile à comprendre et à maintenir.
5.6.1. Directives de style Python : PEP8#
Vous pouvez trouver la philosophie de programmation de Python en tapant import this à l’invite de commande.
Entre autres choses, Python favorise fortement la cohérence dans le style de programmation.
Nous avons tous entendu le dicton sur la cohérence et les petits esprits.
En programmation, comme en mathématiques, le contraire est vrai
Un article mathématique où les symboles \(\cup\) et \(\cap\) seraient inversés serait très difficile à lire, même si l’auteur vous le disait à la première page.
En Python, le style standard est exposé dans PEP8.
(Occasionnellement, nous nous écarterons de PEP8 dans ces cours pour mieux correspondre à la notation mathématique)
5.6.2. Docstrings#
Python dispose d’un système pour ajouter des commentaires aux modules, classes, fonctions, etc. appelé docstrings.
Ce qui est agréable avec les docstrings, c’est qu’elles sont disponibles au moment de l’exécution.
Essayez d’exécuter ceci
def f(x):
"""
This function squares its argument
"""
return x**2
Après avoir exécuté ce code, la docstring est disponible
f?
Type: function
String Form:<function f at 0x2223320>
File: /home/john/temp/temp.py
Definition: f(x)
Docstring: This function squares its argument
f??
Type: function
String Form:<function f at 0x2223320>
File: /home/john/temp/temp.py
Definition: f(x)
Source:
def f(x):
"""
This function squares its argument
"""
return x**2
Avec un point d’interrogation, nous affichons la docstring, et avec deux, nous obtenons aussi le code source.
Vous pouvez trouver les conventions pour les docstrings dans PEP257.
5.7. Exercices#
Résolvez les exercices suivants.
(Pour certains, la fonction intégrée sum() s’avère pratique).
Exercice 5.1
Partie 1 : Étant donné deux listes ou tuples numériques x_vals et y_vals de longueur égale, calculez
leur produit scalaire en utilisant zip().
Partie 2 : En une seule ligne, comptez le nombre de nombres pairs dans 0,…,99.
Partie 3 : Étant donné pairs = ((2, 5), (4, 2), (9, 8), (12, 10)), comptez le nombre de paires (a, b)
telles que a et b soient tous deux pairs.
Indication
x % 2 renvoie 0 si x est pair, 1 sinon.
Solution
Solution de la partie 1 :
Voici une solution possible
x_vals = [1, 2, 3]
y_vals = [1, 1, 1]
sum([x * y for x, y in zip(x_vals, y_vals)])
6
Cela fonctionne aussi
sum(x * y for x, y in zip(x_vals, y_vals))
6
Solution de la partie 2 :
Une solution est
sum([x % 2 == 0 for x in range(100)])
50
Cela fonctionne aussi :
sum(x % 2 == 0 for x in range(100))
50
Quelques alternatives moins naturelles qui aident néanmoins à illustrer la flexibilité des compréhensions de liste sont
len([x for x in range(100) if x % 2 == 0])
50
et
sum([1 for x in range(100) if x % 2 == 0])
50
Solution de la partie 3 :
Voici une possibilité
pairs = ((2, 5), (4, 2), (9, 8), (12, 10))
sum([x % 2 == 0 and y % 2 == 0 for x, y in pairs])
2
Exercice 5.2
Considérez le polynôme
Écrivez une fonction p telle que p(x, coeff) calcule la valeur dans (5.1) étant donné un point x et une liste de coefficients coeff (\(a_1, a_2, \cdots a_n\)).
Essayez d’utiliser enumerate() dans votre boucle.
Solution
Voici une solution :
def p(x, coeff):
return sum(a * x**i for i, a in enumerate(coeff))
p(1, (2, 4))
6
Exercice 5.3
Écrivez une fonction qui prend une chaîne comme argument et renvoie le nombre de lettres majuscules dans la chaîne.
Indication
'foo'.upper() renvoie 'FOO'.
Solution
Voici une solution :
def f(string):
count = 0
for letter in string:
if letter == letter.upper() and letter.isalpha():
count += 1
return count
f('The Rain in Spain')
3
Une alternative, solution plus pythonique :
def count_uppercase_chars(s):
return sum([c.isupper() for c in s])
count_uppercase_chars('The Rain in Spain')
3
Exercice 5.4
Écrivez une fonction qui prend deux séquences seq_a et seq_b comme arguments et
renvoie True si chaque élément de seq_a est également un élément de seq_b, sinon
False.
Par « séquence », nous entendons une liste, un tuple ou une chaîne.
Faites l’exercice sans utiliser les ensembles ni les méthodes d’ensemble.
Solution
Voici une solution :
def f(seq_a, seq_b):
for a in seq_a:
if a not in seq_b:
return False
return True
# == test == #
print(f("ab", "cadb"))
print(f("ab", "cjdb"))
print(f([1, 2], [1, 2, 3]))
print(f([1, 2, 3], [1, 2]))
True
False
True
False
Une alternative, solution plus pythonique utilisant all() :
def f(seq_a, seq_b):
return all([i in seq_b for i in seq_a])
# == test == #
print(f("ab", "cadb"))
print(f("ab", "cjdb"))
print(f([1, 2], [1, 2, 3]))
print(f([1, 2, 3], [1, 2]))
True
False
True
False
Bien sûr, si nous utilisons le type de données sets, la solution est plus facile
def f(seq_a, seq_b):
return set(seq_a).issubset(set(seq_b))
Exercice 5.5
Lorsque nous couvrirons les bibliothèques numériques, nous verrons qu’elles incluent de nombreuses alternatives pour l’interpolation et l’approximation de fonctions.
Néanmoins, écrivons notre propre routine d’approximation de fonction comme exercice.
En particulier, sans utiliser aucun import, écrivez une fonction linapprox qui prend comme arguments
Une fonction
fmappant un certain intervalle \([a, b]\) dans \(\mathbb R\).Deux scalaires
aetbfournissant les limites de cet intervalle.Un entier
ndéterminant le nombre de points de grille.Un nombre
xsatisfaisanta <= x <= b.
et renvoie l”interpolation linéaire par morceaux de f en x, basée sur n points de grille régulièrement espacés a = point[0] < point[1] < ... < point[n-1] = b.
Visez la clarté, pas l’efficacité.
Solution
Voici une solution :
def linapprox(f, a, b, n, x):
"""
Evaluates the piecewise linear interpolant of f at x on the interval
[a, b], with n evenly spaced grid points.
Parameters
==========
f : function
The function to approximate
x, a, b : scalars (floats or integers)
Evaluation point and endpoints, with a <= x <= b
n : integer
Number of grid points
Returns
=======
A float. The interpolant evaluated at x
"""
length_of_interval = b - a
num_subintervals = n - 1
step = length_of_interval / num_subintervals
# === trouver le premier point de grille plus grand que x === #
point = a
while point <= x:
point += step
# === x doit se situer entre les points de grille (point - step) et point === #
u, v = point - step, point
return f(u) + (x - u) * (f(v) - f(u)) / (v - u)
Exercice 5.6
En utilisant la syntaxe de compréhension de liste, nous pouvons simplifier la boucle dans le code suivant.
import numpy as np
rng = np.random.default_rng()
n = 100
ϵ_values = []
for i in range(n):
e = rng.standard_normal()
ϵ_values.append(e)
Solution
Voici une solution.
rng = np.random.default_rng()
n = 100
ϵ_values = [rng.standard_normal() for i in range(n)]