"__main__" — Environnement d'exécution principal
************************************************

======================================================================

En Python, le nom "__main__" a une fonction particulière. Il
intervient dans deux cas :

1. c'est le nom de l'environnement d'exécution principal, ce qui donne
   lieu au test courant "__name__ == '__main__'" ;

2. c'est aussi le nom du fichier "__main__.py" dans les paquets
   Python.

Les deux sont liés aux modules Python, à la manière de s'en servir en
tant qu'utilisateur et à la manière dont ils interagissent entre eux.
Cette page contient des détails sur les modules Python. Si vous ne les
avez jamais utilisés, commencez par la section qui leur est consacrée
dans le tutoriel : Modules.


"__name__ == '__main__'"
========================

Lorsqu'un module ou un paquet Python est importé, son attribut
"__name__" est défini à son nom, qui est la plupart du temps le nom du
fichier qui le contient sans l'extension ".py" :

   >>> import configparser
   >>> configparser.__name__
   'configparser'

Si le fichier fait partie d'un paquet, "__name__" donne tout le chemin
d'accès :

   >>> from concurrent.futures import process
   >>> process.__name__
   'concurrent.futures.process'

En revanche, si le module est exécuté dans l'environnement d'exécution
principal, la variable "__name__" vaut "'__main__'".


Qu'est-ce que l'« environnement d'exécution principal » ?
---------------------------------------------------------

L'environnement principal a pour nom "__main__". Il s'agit du premier
module Python dont l'exécution a été demandée par l'utilisateur final.
On le qualifie de principal car c'est lui qui importe tous les autres
modules nécessités par le programme. On l'appelle parfois le point
d'entrée de l'application.

L'environnement principal peut prendre diverses formes :

* l'environnement d'une invite de commande interactive :

     >>> __name__
     '__main__'

* le module passé directement en tant que fichier à la commande de
  l'interpréteur Python :

     $ python3 helloworld.py
     Hello, world!

* le module ou paquet passé à l'interpréteur avec l'option "-m" :

     $ python3 -m tarfile
     usage: tarfile.py [-h] [-v] (...)

* le code lu par l'interpréteur depuis l'entrée standard :

     $ echo "import this" | python3
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

* le code passé à l'interpréteur avec l'option "-c" :

     $ python3 -c "import this"
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

Dans chacun de ces cas, l'attribut "__name__" du module principal est
mis à "'__main__'".

Un module peut donc savoir s'il est exécuté dans l'environnement
principal en vérifiant son "__name__", ce qui permet typiquement
d'exécuter du code lorsque le module est initialisé d'une manière
autre que l'importation :

   if __name__ == '__main__':
       # Execute when the module is not initialized from an import statement.
       ...

Voir aussi:

  Pour plus de détails sur la manière dont "__name__" est défini dans
  les divers cas, voir la section Modules dans le tutoriel.


Utilisation idiomatique
-----------------------

Il arrive qu'un module contienne du code qui ne doit s'exécuter que
lorsque le module est utilisé comme script. On peut penser à l'analyse
des arguments passés en ligne de commande, ou bien à la lecture de
l'entrée standard. Il ne faudrait pas que ces opérations soient
effectuées lorsque le module est importé depuis un autre module, comme
pour les tests.

C'est dans ces situations que sert la construction "if __name__ ==
'__main__'". Le code mis à l'intérieur du bloc n'est exécuté que si le
module est dans l'environnement principal.

Putting as few statements as possible in the block below "if __name__
== '__main__'" can improve code clarity and correctness. Most often, a
function named "main" encapsulates the program's primary behavior:

   # echo.py

   import shlex
   import sys

   def echo(phrase: str) -> None:
      """A dummy wrapper around print."""
      # for demonstration purposes, you can imagine that there is some
      # valuable and reusable logic inside this function
      print(phrase)

   def main() -> int:
       """Echo the input arguments to standard output"""
       phrase = shlex.join(sys.argv)
       echo(phrase)
       return 0

   if __name__ == '__main__':
       sys.exit(main())  # next section explains the use of sys.exit

Si, dans le module ci-dessus, le code de "main" était placé
directement dans le "if __name__ == '__main__'", la variable "phrase"
serait globale, et d'autres fonctions pourraient s'en servir par
erreur à la place d'une variable locale. Encapsuler le code dans la
fonction "main" évite cet ennui.

De plus, la fonction "echo" est elle-même séparée du reste, et on peut
l'importer dans un autre module. Le fait d'importer "echo.py" définit
les fonctions "main" et "echo", mais n'en appelle aucune, puisque
"__name__ != '__main__'".


Considérations liées à l'empaquetage
------------------------------------

Les fonctions "main" servent souvent à créer des outils qui
s'exécutent en ligne de commande. Les scripts sont ajoutés au système
à l'aide de points d'entrée, qui demandent à pip de créer un
exécutable. Il le fait en insérant un appel à la fonction à
l'intérieur d'un modèle prédéfini où la valeur qu'elle renvoie est
passée directement à "sys.exit()" :

   sys.exit(main())

Puisque l'appel de "main" est encapsulé dans "sys.exit()", "main" doit
renvoyer une valeur qui convienne comme argument à "sys.exit()", par
exemple un code de retour sous forme d'entier. La valeur "None" est
également acceptée, et c'est d'ailleurs celle que renvoie la fonction
si elle se termine sans rencontrer d'instruction "return".

By proactively following this convention ourselves, our module will
have the same behavior when run directly (i.e. "python3 echo.py") as
it will have if we later package it as a console script entry-point in
a pip-installable package.

En particulier, mieux vaut éviter de renvoyer une chaîne de caractères
depuis la fonction "main". En effet, si "sys.exit()" reçoit une
chaîne, elle l'interprète comme un message d'erreur, qu'elle affiche
sur "sys.stderr" avant de terminer le programme avec le code de retour
1 (erreur). L'exemple de "echo.py" ci-dessus montre la pratique
recommandée.

Voir aussi:

  Le guide de l'empaquetage en Python (en anglais) contient plusieurs
  tutoriels et documents de référence autour de la distribution et de
  l'installation de paquets Python avec des outils modernes.


Le fichier "__main__.py" dans les paquets Python
================================================

Si vous n'êtes pas familier des paquets Python, lisez Les paquets dans
le tutoriel. Un fichier "__main__.py" permet à un paquet de définir
une interface en ligne de commande. Prenons pour exemple un paquet
nommé *bandclass* :

   bandclass
     ├── __init__.py
     ├── __main__.py
     └── student.py

Le fichier "__main__.py" qu'il contient s'exécute lorsque le paquet
est appelé depuis la ligne de commande avec l'option "-m" de
l'interpréteur, comme ceci :

   $ python3 -m bandclass

Cette commande lance l'exécution de "__main__.py". Il vous appartient,
en tant que concepteur du paquet, de déterminer ce qu'elle doit faire.
Dans notre exemple, elle pourrait rechercher un étudiant dans une base
de données :

   # bandclass/__main__.py

   import sys
   from .student import search_students

   student_name = sys.argv[1] if len(sys.argv) >= 2 else ''
   print(f'Found student: {search_students(student_name)}')

Remarquez l'importation "from .student import search_students". Le
point avant "student" sert à rendre le chemin "student" relatif à la
position du module qui l'importe. Pour plus d'informations, voir
Références internes dans un paquet dans la section Modules du
tutoriel.


Utilisation idiomatique
-----------------------

The content of "__main__.py" typically isn't fenced with an "if
__name__ == '__main__'" block.  Instead, those files are kept short
and import functions to execute from other modules.  Those other
modules can then be easily unit-tested and are properly reusable.

Cependant, un "if __name__ == '__main__'", s'il est présent dans le
"__main__.py", fonctionne correctement. En effet, si "__main__.py" est
importé depuis autre module, son attribut "__name__" contient, avant
"__main__", le nom du paquet dont il fait partie :

   >>> import asyncio.__main__
   >>> asyncio.__main__.__name__
   'asyncio.__main__'

Malgré tout, cela ne fonctionne pas pour les fichiers "__main__.py" à
la racine d'une archive ZIP. Aussi est-il préférable d'écrire des
"__main__.py" dans le style minimal de celui de "venv" mentionné ci-
dessus.

Voir aussi:

  See "venv" for an example of a package with a minimal "__main__.py"
  in the standard library. It doesn't contain a "if __name__ ==
  '__main__'" block. You can invoke it with "python -m venv
  [directory]".

  La documentation du module "runpy" fournit une description complète
  de l'option "-m" de l'interpréteur.

  Le module "zipapp" exécute des applications emballées dans une
  archive ZIP. Dans ce cas, l'interpréteur recherche un fichier
  "__main__.py" à la racine de l'archive.


"import __main__"
=================

Quel que soit le module principal d'un programme, les autres modules
peuvent accéder à l'*espace de nommage* dans lequel il s'exécute en
important le module spécial "__main__". Celui-ci ne correspond pas
forcément à un fichier "__main__.py". Il s'agit simplement du module
qui a reçu le nom "'__main__'".

Voici un exemple d'utilisation du module "__main__" :

   # namely.py

   import __main__

   def did_user_define_their_name():
       return 'my_name' in dir(__main__)

   def print_user_name():
       if not did_user_define_their_name():
           raise ValueError('Define the variable `my_name`!')

       if '__file__' in dir(__main__):
           print(__main__.my_name, "found in file", __main__.__file__)
       else:
           print(__main__.my_name)

Ce code s'utilise comme ceci :

   # start.py

   import sys

   from namely import print_user_name

   # my_name = "Dinsdale"

   def main():
       try:
           print_user_name()
       except ValueError as ve:
           return str(ve)

   if __name__ == "__main__":
       sys.exit(main())

Le programme ci-dessus donne la sortie :

   $ python3 start.py
   Define the variable `my_name`!

Son code de retour est 1, ce qui signifie une erreur. En supprimant la
marque de commentaire en début de ligne "my_name = "Dinsdale"", le
programme est corrigé, et renvoie au système le code 0 car il n'y a
plus d'erreur :

   $ python3 start.py
   Dinsdale found in file /path/to/start.py

On pourrait s'attendre à un problème au moment de l'importation de
"__main__" : cela ne provoque-t-il pas l'exécution anticipée du code
de script sous "if __name__ == '__main__'" dans le module principal
"start" ?

Python inserts an empty "__main__" module in "sys.modules" at
interpreter startup, and populates it by running top-level code. In
our example this is the "start" module which runs line by line and
imports "namely". In turn, "namely" imports "__main__" (which is
really "start"). That's an import cycle! Fortunately, since the
partially populated "__main__" module is present in "sys.modules",
Python passes that to "namely". See Special considerations for
__main__ in the import system's reference for details on how this
works.

L'interpréteur interactif est un autre environnement d'exécution
principal possible. Toute variable qui y est définie appartient à
l'espace de nommage "__main__" :

   >>> import namely
   >>> namely.did_user_define_their_name()
   False
   >>> namely.print_user_name()
   Traceback (most recent call last):
   ...
   ValueError: Define the variable `my_name`!
   >>> my_name = 'Jabberwocky'
   >>> namely.did_user_define_their_name()
   True
   >>> namely.print_user_name()
   Jabberwocky

Dans ce cas, il n'y a pas de variable "__file__", puisque cela n'a pas
de sens dans le mode interactif.

Le module "__main__" est notamment employé dans les implémentations de
"pdb" et "rlcompleter".
