Algo. et prog. 2 en
Logo de Python
Arnaud COUTURIER - Python 3.10

Namespaces et modules

Namespaces

On appelle namespace ou espace de nommage en français, un mĂ©canisme en Python qui isole les identifiants dans des espaces diffĂ©rents. Ils sont utiles pour aider Ă  l'organisation du code et des identifiants en unitĂ©s logiques plus facile Ă  gĂ©rer car isolĂ©es les unes des autres, plutĂŽt que de mettre tous les identifiants dans un seul et unique sac, cela serait vite ingĂ©rable. Par exemple dans un namespace A on peut avoir les identifiants a, b et c, qui ne seront pas les mĂȘmes que les identifiants a, b et dans un namespace B.

On définit des namespaces à chaque fois qu'on crée une fonction, une classe ou un module. Chaque instance des classes, donc chaque objet, sont aussi des namespaces. Pour les fonctions, l'espace de nommage qu'une fonction définit est créé à chaque fois que la fonction démarre son exécution, et détruit à chaque fois qu'elle se termine. Les classes quant à elles ont leur espace de nommage créé dÚs que le module dans lequel elles existent est importé. (Plus techniquement les namespaces sont liés à la notion d'objet, car chaque namespace est en fait un objet, et chaque objet est un namespace, les deux notions sont liées, une instance et les identifiants dans un namespace sont les attributs de cet objet)

Modules et packages

Jusqu’ici on a créé des programmes Python dans un seul fichier .py. Au fur et Ă  mesure qu’un programme devient long et complexe, le fichier devient difficile Ă  naviguer et Ă  lire, Ă  ce moment on sĂ©pare le code dans plusieurs fichiers .py. Le mĂ©canisme de Python qui permet de faire cela est celui des modules avec l'importation. On appelle module un fichier .py tout simplement. On appelle importation le fait de rendre disponible le contenu d'un module depuis un autre.

Quels morceaux du code vont dans quels fichiers sont au choix des dĂ©veloppeurs, ainsi que le nombre et le nom de ces fichiers. Python offre la possibilitĂ© de le faire, mais nous sommes libres d’utiliser cette fonctionnalitĂ© comme nous l’entendons. En gĂ©nĂ©ral, on place le code liĂ© Ă  une mĂȘme fonctionnalitĂ©, ou un mĂȘme aspect du programme dans un mĂȘme fichier, qui doit ĂȘtre le plus isolĂ© et indĂ©pendant possible des autres fichiers et donc du reste du code.

On appelle aussi les modules bibliothĂšques, ou librairies. Elles peuvent ĂȘtre dans un seul fichier, ou bien dans un ensemble de fichiers cohĂ©rents dans le cas de grosses bibliothĂšques. On appelle packages un ensemble de modules dans un mĂȘme dossier, ils sont un moyen de structurer les modules. mais on ne les verra pas dans ce cours. Ils sont cependant un aspect important de Python Ă  Ă©tudier plus tard.

Quand une bibliothĂšque comporte plusieurs fichiers, ils peuvent ĂȘtre inter-dĂ©pendants, c’est Ă  dire qu’ils se rĂ©fĂ©rencent les uns les autres, mais idĂ©alement le moins possible. En revanche, ils doivent ĂȘtre le plus indĂ©pendants possible de tout code hors de la bibliothĂšque, idĂ©alement 100% indĂ©pendants de l'extĂ©rieur.

Le gros avantage lorsqu’on met du code dans des bibliothĂšques sous forme de modules ou packages, est qu’on peut le rĂ©utiliser oĂč on veut, depuis n’importe quel autre fichier Python. On peut donc rĂ©utiliser des fonctions ou des classes, et Ă©viter de devoir les rĂ©-Ă©crire ou les copier-coller dans plusieurs fichiers.

Exemple

Voyons un exemple de modules: soit les deux modules suivants dans un mĂȘme dossier:


		Un dossier/
		|-- modA.py
		|-- modB.py
	

		# Ceci est le fichier modA.py, c'est donc le module modA

		variable = 'Bonjour modA'

		def f():
			print("Une fonction dans modA")
	

Avec l'instruction import on peut importer un module dans un autre, pour avoir ensuite accÚs à ses variables et fonctions, en préfixant leur nom par celui du module suivi d'un point, comme par exemple modA.variable.


		# Ceci est le fichier modB.py, c'est donc le module modB

		# On importe modA, remarquez qu'on ne met pas .py Ă  la fin
		import modA

		variable = 'Bonjour modB'

		# ↓ Affichera 'Bonjour modB'
		print(variable)

		# ↓ Affichera 'Bonjour modA'
		print(modA.variable)

		# ↓ Affichera 'Une fonction dans modA'
		modA.f()
	

GrĂące Ă  la notion de namespaces les deux modules peuvent contenir des identifiants ayant le mĂȘme nom (variable ou f), et pourtant ils ne sont pas les mĂȘmes, ils rĂ©fĂ©rencent des objets diffĂ©rents.

(schéma des deux modules modA et modB et leurs identifiants)

L'instruction import

AprĂšs qu'un module est importĂ©, un identifiant qui porte son nom est créé dans le namespace oĂč l'instruction import se situe (dans l'exemple ci-dessous le nom "modA" est créé par le "import"). Cet identifiant pointe vers un objet nouvellement créé de type module correspondant au module importĂ©, objet par lequel on accĂšdera au module importĂ© (le module modA.py dans l'exemple ci-dessous). C'est donc par cet objet qu'on peut accĂ©der au namespace du module que l'objet reprĂ©sente, et donc Ă  tous les noms prĂ©sents dans ce module.


		# Ceci est le fichier modB.py
		import modA

		print(modA)
		print(type(modA))
	

Executons modB.py:


<module 'modA' from 'C:\\Users\\Arnaud\\Desktop\\modA.py'>
<class 'module'>

On place gĂ©nĂ©ralement les instructions import tout en haut des fichiers, mais ils peuvent apparaĂźtre n'importe oĂč.

Boucles d'importation

Attention à ne pas créer de boucles dans les importations, c'est à dire deux modules ou plus qui s'importent mutuellement:


		# Ceci est le fichier modA.py
		import modB # <= ERREUR: boucle avec import dans modB
	

		# Ceci est le fichier modB.py
		import modA # <= ERREUR: boucle avec import dans modA
	

Mécanisme d'importation

La premiĂšre fois qu'un module est importĂ©, tout son contenu est exĂ©cutĂ© une fois. Pour cette raison importer plusieurs fois le mĂȘme module, que ça soit depuis le mĂȘme module ou depuis plusieurs modules, n'a aucun coĂ»t en terme de performances.

Si on execute modB.py ci-dessous, on aura Ă  l'affichage modA exĂ©cutĂ© ↓


		# fichier modA.py
		print("modA exécuté")
	

		# fichier modB.py, on execute celui-ci
		import modA
		import modA # <= pas d'erreur, mais inutile
	

Dans un module qui est sert de "stockage" de variables, fonctions et classes, on évite de mettre des instructions globales autres que des déclaration de variables, fonctions et classes hors de toute fonction, car elles seront exécutées lors de la premiÚre importation. Cela peut avoir des effets de bords indésirables pour le développeur qui importera le module. Donc dans un module qui sert de bibliothÚque à importer on ne met surtout pas par exemple de code qui affiche avec print(), qui met en pause le programme avec input(), qui manipule des fichiers ou qui communique sur le réseau hors de fonctions. On met le code dans des fonctions, et le développeur importera le module puis appellera les fonctions qu'il/elle souhaite, afin de garder la maßtrise du déroulement du programme.

L'identifiant __name__ et le module __main__

Chaque module contient un identifiant prĂ©-dĂ©fini __name__ (avec double _ de chaque cĂŽtĂ©) qui pointe vers un objet de type str qui a pour valeur le nom du module. Si on execute modB.py ci-dessous ↓


		# fichier modA.py
		print(__name__)
	

		# fichier modB.py, on execute celui-ci
		import modA
		print(modA.__name__)
		print(__name__)
	

modA
modA
__main__

On peut voir que la variable "__name__" dans modA vaut bien "modA".

Par contre, remarquez une chose spĂ©ciale: ↑ la variable __name__ dans modB.py ne vaut pas "modB" mais la string "__main__". Le module qui est exĂ©cutĂ© par le programme Python (par exemple avec la commande python modB.py), devient le module principal, le point d'entrĂ©e du programme, et son module prend le nom spĂ©cial "__main__". Dans replit le module qui est exĂ©cutĂ© est le fichier main.py par dĂ©faut, quand on clique sur le bouton Run.

Le contexte global

Ce qu'on appelle d'habitude contexte global ou environnement global, pour désigner tout ce qui est extérieur à des fonctions et classes, est en fait global au module dans lequel on se situe. Il n'existe pas de contexte vraiment totalement global dans un programme, seulement des modules chacun ayant leur contexte global. Le véritable terme qu'on devrait employer serait namespace du module, mais par abus de langage on dit souvent contexte global du module ou environnement global du module.

La fonction dir()

Vous pouvez lister le nom de tous les éléments déclarés dans un module avec la fonction dir(), qui prend un objet en paramÚtre, et retourne une liste de tous les identifiants (sous forme de str) qui sont dans le namespace de l'objet. Exemple:


		# fichier modA.py
		une_variable = 'a'
		def une_fonction():
			pass
	

		# fichier modB.py, on execute celui-ci
		import modA
		print(dir(modA))
	

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
	'__package__', '__spec__', 'une_fonction', 'une_variable']

↑ On obtient une liste de str, oĂč chaque Ă©lĂ©ment est le nom d'un identifiant: variable, fonction, classe etc.... Remarquez qu'il y a beaucoup d'identifiants prĂ©-dĂ©finies dans le module, ceux entourĂ©s de deux caractĂšres underscore _, qu'on peut ignorer pour l'instant. Mais on retrouve la variable __name__, et on peut voir notre fonction une_fonction et notre variable une_variable Ă  la fin.

Sans paramÚtres dir() retourne la liste de tous les identifiants dans le namespace dans lequel elle est appelée, donc par exemple dans le contexte global pour avoir les identifiants du module dans lequel elle est appelée.

Les fonctions vars() et globals()

vars() fait Ă  peu prĂšs la mĂȘme chose que dir() mais retourne un dictionnaire au lieu d'une liste, qui a pour clĂ© les identifiants, et pour valeurs les objets associĂ©s. Soit pour un objet/namespace en particulier passĂ© en paramĂštre, soit pour le namespace depuis lequel elle est appelĂ©e si aucun paramĂštre ne lui est passĂ©.

La fonction prĂ©dĂ©finie globals() est trĂšs similaire Ă  vars() mais ne prend jamais aucun paramĂštre, et peu importe oĂč elle est appelĂ©e elle retourne toujours tous les identifiants du contexte global du module dans lequel elle est appelĂ©e, et leurs objets associĂ©s, dans un dictionnaire comme pour vars().

Exemple

Prenons le code ci-dessous, et voyons les différents namespaces qui sont créés automatiquement:

Namespace du module, ou namespace global:

(Attention, les objets sont toujours stockés dans l'espace unique des objets (voir partie sur le modÚle de données), mais pour rendre le schéma plus lisible les objets sont montrés éparpillés autour du namespace.)

Le namespace ci-dessous est celui une fois la totalité du fichier exécuté ou importé. C'est important de le préciser, car ligne aprÚs ligne des nouveaux identifiants sont créés et sont ajoutés dans le namespace du module, donc les noms présents ou pas dans un namespace à un point précis du programme dépendent des lignes qui ont été exécutées jusqu'à ce point.

Namespace de la fonction f, ou namespace local Ă  f:

(Attention, les objets sont toujours stockés dans l'espace unique des objets (voir partie sur le modÚle de données), mais pour rendre le schéma plus lisible les objets sont montrés éparpillés autour du namespace.)

Namespace du constructeur __init__ de la classe:

(Attention, les objets sont toujours stockés dans l'espace unique des objets (voir partie sur le modÚle de données), mais pour rendre le schéma plus lisible les objets sont montrés éparpillés autour du namespace.)

Namespace des deux instances de MaClasse:

Les namespaces des objets sont un peu spĂ©ciaux, car on n'exĂ©cute jamais du code dans le "contexte" d'un objet, mais dans des mĂ©thodes, oĂč on y fait rĂ©fĂ©rence toujours par le paramĂštre "self", comme dans le constructeur tel qu'illustrĂ© dans le schĂ©ma plus haut. C'est pour cette raison que les autres variables n'apparaissent pas sur le schĂ©ma ci-dessous, car la notion de visibilitĂ© n'a pas vraiment de sens, seule la notion de nom DANS le namespace de chaque objet est pertinente. Mais conceptuellement on peut reprĂ©senter les namespaces des deux instances "mon_objet_1" et "mon_objet_2" comme ci-dessous.

(Attention, les objets sont toujours stockés dans l'espace unique des objets (voir partie sur le modÚle de données), mais pour rendre le schéma plus lisible les objets sont montrés éparpillés autour du namespace.)

BibliothĂšque standard de Python

De trĂšs nombreuses fonctionnalitĂ©s de Python sont sĂ©parĂ©es dans des modules qu'il faut importer spĂ©cifiquement. Ces modules ne font pas partie du langage en lui-mĂȘme, mais forment ce qu'on appelle la bibliothĂšque standard. Une implĂ©mentation de Python n'est pas obligĂ©e d'inclure cette bibliothĂšque standard. Vous pouvez voir la liste exhaustive de tous les modules standards dans la documentation officielle.

Il existe un module particulier nommĂ© builtins (qui signifie "intĂ©grĂ©s" en anglais) qui est importĂ© automatiquement d'une façon qui lui est particuliĂšre. Il contient les fonctions qu'on a utilisĂ©es depuis toujours, comme print(), input() etc... Pour obtenir une rĂ©fĂ©rence vers builtins (objet de type module) il faut quand mĂȘme l'importer explicitement avec import builtins. Une implĂ©mentation de Python DOIT inclure ce module, car il contient des Ă©lĂ©ments fondamentaux du langage. Vous avez dans la documentation officielle la liste complĂšte de toutes les fonctions, tous les types, toutes les constantes et exceptions qui existent dans le module builtins.