Modules et imports
Utiliser dans un fichier un autre fichier que vous avez écrit, ce que fait réellement un import, pourquoi `if __name__ == "__main__"` est nécessaire, la bibliothèque standard, et cacher un module derrière un fichier à vous.
Comment voulez-vous apprendre ?
Le problème que nous résolvons
Dans le dernier chapitre, nous avons écrit une fonction line_total. Maintenant, un second programme a besoin de la même chose.
La solution facile est de la recopier. Ça marche, le premier jour.
Puis le taux de taxe change. Vous corrigez un fichier et oubliez l'autre. Maintenant, deux programmes affichent deux totaux différents pour la même commande, et impossible de dire lequel est juste — parce que les deux sont justes, selon leur propre fichier.
Le problème n'est pas l'effort de recopier. Le problème est que la vérité vit maintenant à deux endroits.
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.75Un seul fichier, et autant de programmes que vous voulez pour l'utiliser. Quand le taux change, il change à un seul endroit.
Ce chapitre porte sur cette seule ligne — import — et sur ce qui se passe réellement derrière.
À la fin de ce chapitre vous pourrez
- Importer dans un autre fichier un fichier que vous avez écrit
- Dire ce qui distingue
import x,from x import yetimport x as z - Dire ce qui se passe au moment d'un import, et combien de fois cela se produit
- Expliquer pourquoi on écrit
if __name__ == "__main__": - Importer des modules de la bibliothèque standard
- Savoir où Python cherche, et éviter de cacher un module derrière un fichier à vous
Prérequis : Portée et défauts mutables.
Un module est simplement un fichier
Un fichier appelé pricing.py est un module appelé pricing. Rien n'a besoin d'être déclaré et aucune ligne spéciale n'a besoin d'être écrite — il suffit que le fichier existe.
import pricing importe le module entier, et les éléments qu'il contient s'atteignent avec un point : pricing.TAX_RATE, pricing.line_total(...).
Le point peut sembler un effort de saisie inutile, mais il a un rôle : à la lecture, il indique d'où vient l'élément. Dans un gros fichier, c'est très utile.
N'importer que ce dont vous avez besoin
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... n'importe que le nom demandé, et ce nom s'utilise ensuite directement — sans point.
(Avant la ligne d'erreur, Python affiche aussi quelques lignes de traceback ; dans ce chapitre, seule la dernière ligne est généralement montrée.)
Mais les autres noms ne viennent pas. TAX_RATE n'a pas été demandé, donc il n'est pas là, même s'il se trouve dans le même fichier.
Lequel utiliser ? En gros : from quand vous avez besoin de deux ou trois noms, le module entier quand vous en avez beaucoup. Et une chose à ne jamais faire — from pricing import *. Cela importe discrètement tous les noms, et ensuite il est impossible de savoir d'où vient line_total.
Renommer à l'import
import pricing as p
print(p.line_total(15.0, 3))51.75as donne au module un nom plus court à l'intérieur de ce fichier.
Utilisez-le quand le nom est vraiment long, ou quand il existe une convention pour ce nom. Un nom d'une seule lettre par paresse fait perdre la lisibilité que le point apportait — personne qui lit p.line_total(...) ne peut savoir d'où cela vient.
Importer, c'est exécuter le fichier
C'est la partie que la plupart des gens découvrent plus tard, souvent au mauvais moment.
tools.py :
print("tools is being set up")
def ping():
return "pong"main.py :
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main endsDeux choses à remarquer.
Importer, c'est exécuter le fichier, de haut en bas. Les lignes def créent des fonctions, et tout le reste — comme ce print — se produit réellement. Mettez un print ou un input au niveau principal d'un module et cela tombe sur celui qui l'importe.
Le second import tools n'a rien fait. Un module s'exécute une seule fois par programme ; ensuite Python réutilise celui qu'il a déjà. Importer le même module à dix endroits est donc sans danger et peu coûteux.
__name__ et cette fameuse ligne
Chaque module possède un nom, __name__,, que Python définit lui-même.
tools.py :
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")Lancé avec python tools.py :
module name is __main__
running directlyMais import tools depuis un autre fichier :
module name is toolsLa ligne running directly n'est pas apparue cette fois, parce que la condition était fausse.
La règle est simple : le fichier que vous lancez a __name__ égal à "__main__" ; les fichiers qu'on importe gardent leur propre nom.
Donc ce qui se trouve dans if __name__ == "__main__": ne s'exécute que lorsque le fichier est lancé directement, jamais lorsqu'il est importé.
Pourquoi c'est nécessaire découle de la section précédente. Importer, c'est exécuter le fichier — donc si main() se trouve à la fin d'un fichier, quiconque l'importe lance soudain aussi votre programme. Cette ligne ferme la porte.
La bibliothèque standard
Un grand nombre de modules sont livrés avec Python lui-même. Rien n'a besoin d'être installé.
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30Un autre qui revient souvent :
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penSans random.seed(7), chaque exécution donnerait des nombres différents — ce qui est, après tout, le but. Fixer une graine (seed) fige la suite, donc avec la même version de Python, la sortie ci-dessus sera la même sur votre machine. Pour écrire des tests, c'est très important : pour tester quelque chose d'aléatoire, il faut d'abord le rendre reproductible.
Ce n'est pas du « hasard », c'est du « pseudo-aléatoire ».randomproduit des nombres à partir d'un calcul, donc la même graine donne toujours la même suite. Ne l'utilisez pas pour des mots de passe ou des jetons — le modulesecretsexiste pour cela.
Où Python cherche
Pour import pricing, Python cherche dans cet ordre : d'abord les modules intégrés à l'interpréteur, puis le dossier du script lancé, puis la bibliothèque standard, puis les paquets installés.
Cet ordre a une conséquence, qui piège les gens.
Il y a un fichier appelé random.py dans votre dossier, et vous écrivez :
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'Votre fichier est trouvé avant le vrai random, donc c'est le vôtre qui est importé — et il ne contient pas de choice.
Le message est déroutant, car il dit que random n'a pas de choice, ce qui semble impossible. Mais il parle de votre random.
La règle : ne nommez jamais un fichier comme un module. random.py, string.py, json.py, csv.py — ce sont des noms qui semblent innocents, et c'est exactement pour cela que le piège fonctionne.
Une exception, qui explique la règle. Sur de nombreuses installations (par exemple le système Linux sur lequel les exemples de ce chapitre ont été exécutés), créez un fichiermath.pyetimport mathvous donne quand même le vrai, et non le vôtre, parce que làmathest intégré à l'interpréteur, et les modules intégrés passent en premier dans l'ordre de recherche. Quels modules sont intégrés dépend de la façon dont Python a été compilé. Donc le piège se déclenche pour certains noms et pas pour d'autres — ce qui est plus difficile à retenir que de simplement renommer le fichier.
Ranger les fichiers dans des dossiers
Quand les fichiers se multiplient, on peut les regrouper dans des dossiers. Placez un fichier __init__.py dans le dossier — vide, c'est très bien — et le dossier devient un paquet (package).
shop/
__init__.py
pricing.py
main.pyLe chemin peut alors s'écrire avec des points :
from shop.pricing import line_total
print(line_total(15.0, 3))51.75Cela suffit pour l'instant. Pour le reste de ce chapitre, nous restons avec des fichiers à plat.
Un exemple complet
Trois fichiers, dont les dépendances ne vont que dans un sens.
pricing.py :
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py :
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py :
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()Lancé avec python main.py :
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2Quatre points à remarquer.
Les dépendances vont dans un seul sens. main connaît report, report connaît pricing, et pricing ne connaît personne. Cet ordre forme une chaîne, et le fichier au bout — pricing — peut être testé entièrement seul. Si pricing avait eu besoin d'importer report en retour, aucun des deux ne pourrait plus tenir seul.
pricing.py n'affiche rien et ne demande rien. Il reçoit des valeurs et renvoie des valeurs — ce sont les fonctions pures du chapitre vingt-et-un. Comme importer revient à exécuter le fichier, cette retenue n'est pas une politesse, c'est une obligation.
report.py a lui aussi un if __name__ == "__main__":, et il ne fait rien d'utile — le fichier n'a pas de données propres, donc il n'a rien à montrer. On aurait pu s'en passer. Il est là pour que, si le fichier est un jour lancé directement, il dise au moins quelque chose, plutôt que de ne rien faire en silence.
Aucun fichier, à part main.py, ne fait quoi que ce soit tout seul. Le programme entier a exactement un point d'entrée, et le lire vous dit ce qui va se passer.
Quand ça casse
ModuleNotFoundError: No module named 'priceing' Une faute de frappe, ou le fichier est dans un autre dossier. Vérifiez qu'il se trouve à côté du script que vous lancez.
ImportError: cannot import name 'line_totl' from 'pricing' Le module a été trouvé, mais le nom qu'il contient ne l'a pas été. Le message se termine par le chemin complet du fichier du module entre crochets, et suggère souvent le nom le plus proche — Did you mean: 'line_total'?
AttributeError: module 'random' has no attribute 'choice' Presque toujours, un fichier à vous qui cache le vrai module. Cherchez ce nom dans votre dossier et renommez-le. Cela suffit — une fois le fichier .py supprimé, Python n'utilise pas l'ancienne copie restée dans __pycache__.
ImportError: cannot import name 'rows' from partially initialized module 'table' (most likely due to a circular import) Deux fichiers s'importent mutuellement, et au moins l'un des deux utilise from ... import ... pour demander un nom qui n'existe pas encore dans l'autre fichier — parce que cet autre fichier n'a pas fini de s'exécuter de haut en bas. La solution habituelle est de déplacer ce dont ils ont besoin tous les deux dans un troisième fichier qui n'en importe aucun.
Quelque chose s'est affiché, ou a demandé une saisie, dès que je l'ai importé Il y a du code au niveau principal du module. Mettez-le dans une fonction et appelez-la depuis if __name__ == "__main__":.
J'ai modifié le fichier mais je vois toujours l'ancien comportement Relancez le programme — un programme en cours d'exécution ne relit pas le module. Dans un notebook, redémarrez le noyau.
Étape 4 sur 6 — Prédire
Vérifiez votre compréhension
line_total a été demandé, TAX_RATE non. Que se passe-t-il ?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- A`51.75` s'affiche, puis `None`
- B`51.75` et `0.15` s'affichent
- C`51.75` s'affiche, puis un `NameError`
- DUn `ImportError`
import tools est écrit deux fois. Qu'est-ce qui s'affiche ?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Atools is being set up main starts pong main ends
- Bmain starts pong main ends
- Cmain starts tools is being set up pong main ends
- Dmain starts tools is being set up tools is being set up pong main ends
python main.py est lancé. Qu'est-ce qui s'affiche ?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: main done
- Cname: __main__ direct done
- Dname: tools direct done
Répondre nécessite un compte
Connectez-vous pour vérifier vos réponses
Les questions sont ci-dessus, et les résoudre de tête est ce qui compte vraiment. Connectez-vous pour voir les réponses, les explications et les indices à trois niveaux.
À vous de jouer
Créez trois fichiers dans un même dossier.
textutil.py — deux fonctions : initials(name), qui transforme "rafi ahmed" en "RA", et shorten(text, limit), qui coupe un long texte et ajoute ... à la fin. Aucun print nulle part dans ce fichier.
table.py — importe textutil et propose rows(people), qui prend une liste de noms et renvoie une liste de lignes mises en forme.
main.py — importe table, l'appelle avec quelques noms, et affiche le résultat. Mettez if __name__ == "__main__": dans les trois fichiers.
Ensuite, quatre expériences, qui valent toutes d'être vues de vos propres yeux :
- Ajoutez
print("loaded")tout en haut detextutil.pyet lancezpython main.py. La ligne apparaît-elle ? Avant ou après le premierprintdemain.py? - Écrivez
import tabledeux fois dansmain.py. Combien de foisloadeds'affiche-t-il ? - Créez un fichier vide appelé
statistics.pydans le dossier, puis dansmain.pyimportezstatisticset appelezstatistics.mean([1, 2]). Lisez le message — de quelstatisticsparle-t-il ? - Ajoutez
from table import rowstout en haut detextutil.py, pour que les deux s'importent mutuellement, puis lancezpython main.py. La dernière ligne du message commencera ainsi :ImportError: cannot import name 'rows' from partially initialized module 'table'. (Un simpleimport tablene provoque aucune erreur :textutilobtient seulement un nom pour le module à moitié construit, etrowsest recherché plus tard, quand une fonction s'exécute.from table import rowsdemande le nom tout de suite, alors quetable.pyest encore bloqué à sa première ligne.)
Les deux dernières sont des erreurs qui peuvent coûter des heures si le message est inconnu, et quelques secondes une fois que vous les avez provoquées vous-même.
Étape 6 sur 6
Défi — le quiz du chapitre
Dix questions, de la plus facile à la plus difficile. Les dernières sont difficiles exprès.
Connectez-vous pour faire le quiz