FAQ de programmation
********************


General questions
=================


Is there a source code-level debugger with breakpoints and single-stepping?
---------------------------------------------------------------------------

Oui.

Plusieurs débogueurs sont décrits ci-dessous et la fonction native
"breakpoint()" permet d'utiliser n'importe lequel d'entre eux.

Le module "pdb" est un débogueur console simple, mais parfaitement
adapté à Python. Il fait partie de la bibliothèque standard de Python,
sa documentation se trouve dans le "manuel de référence". Vous pouvez
vous inspirer du code de "pdb" pour écrire votre propre débogueur.

The IDLE interactive development environment, which is part of the
standard Python distribution (normally available as "idlelib"),
includes a graphical debugger.

*PythonWin* est un environnement de développement intégré (EDI) Python
qui embarque un débogueur graphique basé sur "pdb". Le débogueur
*PythonWin* colore les points d'arrêts et possède quelques
fonctionnalités sympathiques, comme la possibilité de déboguer des
programmes développés sans *PythonWin*.  *PythonWin* est disponible
dans le projet pywin32 et fait partie de la distribution ActivePython.

Eric est un EDI basé sur PyQt et l'outil d'édition Scintilla.

trepan3k est un débogueur semblable à GDB.

Visual Studio Code est un EDI qui contient des outils de débogage. Il
sait interagir avec les outils de gestion de versions.

Il existe de nombreux EDI Python propriétaires qui embarquent un
débogueur graphique, notamment :

* Wing IDE ;

* PyCharm ;


Existe-t-il des outils pour aider à trouver des bogues ou faire de l'analyse statique de code ?
-----------------------------------------------------------------------------------------------

Oui.

Ruff, Pylint and Pyflakes do basic checking that will help you catch
bugs sooner.

Static type checkers such as mypy, ty, Pyrefly, and pytype can check
type hints in Python source code.


Comment créer un binaire autonome à partir d'un script Python ?
---------------------------------------------------------------

Pour créer un programme autonome, c'est-à-dire un programme que
n'importe qui peut télécharger et exécuter sans avoir à installer une
distribution Python au préalable, il n'est pas nécessaire de compiler
du code Python en code C. Il existe en effet plusieurs outils qui
déterminent les modules requis par un programme et lient ces modules
avec un binaire Python pour produire un seul exécutable.

One is to use the freeze tool, which is included in the Python source
tree as Tools/freeze. It converts Python byte code to C arrays; with a
C compiler you can embed all your modules into a new program, which is
then linked with the standard Python modules.

Il fonctionne en cherchant de manière récursive les instructions
d'importation (sous les deux formes) dans le code source et en
recherchant ces modules dans le chemin Python standard ainsi que dans
le répertoire source (pour les modules natifs). Il transforme ensuite
le code intermédiaire des modules écrits en Python en code C (des
tableaux pré-remplis qui peuvent être transformés en objets code à
l'aide du module *marshal*) et crée un fichier de configuration
personnalisé qui contient uniquement les modules natifs qui sont
réellement utilisés dans le programme. Il compile ensuite le code C
généré et le lie au reste de l'interpréteur Python pour former un
binaire autonome qui fait exactement la même chose que le script.

Voici quelques paquets qui permettent de créer des exécutables en
ligne de commande comme graphiques :

* Nuitka (multiplateformes) ;

* PyInstaller (Cross-platform)

* PyOxidizer (multi-plateforme) ;

* cx_Freeze (multi-plateforme) ;

* py2app (uniquement pour macOS) ;

* py2exe (uniquement pour Windows).


Existe-t-il des normes de développement ou un guide de style pour écrire des programmes Python ?
------------------------------------------------------------------------------------------------

Oui. Le style de développement que les modules de la bibliothèque
standard doivent obligatoirement respecter est documenté dans la **PEP
8**.


Core language
=============


Pourquoi une UnboundLocalError est levée alors qu'une variable a une valeur ?
-----------------------------------------------------------------------------

Il est parfois surprenant d'obtenir une "UnboundLocalError" dans du
code jusqu'à présent correct, quand celui-ci est modifié en ajoutant
une instruction d'affectation quelque part dans le corps d'une
fonction.

Le code suivant :

>>> x = 10
>>> def bar():
...     print(x)
...
>>> bar()
10

fonctionne, mais le suivant :

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

results in an "UnboundLocalError":

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

Cela est dû au fait que, quand une variable est affectée dans un
contexte, cette variable devient locale à ce contexte et remplace
toute variable du même nom du contexte appelant. Vu que la dernière
instruction dans *foo* affecte une nouvelle valeur à "x", le
compilateur la traite comme une nouvelle variable. Par conséquent,
quand le "print(x)" essaye d'afficher la variable non initialisée, une
erreur se produit.

Dans l'exemple ci-dessus, la variable du contexte appelant reste
accessible en la déclarant globale :

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...
>>> foobar()
10

Cette déclaration explicite est obligatoire pour se rappeler que
(contrairement au cas à peu près similaire avec des variables de
classe et d'instance), c'est la valeur de la variable du contexte
appelant qui est modifiée :

>>> print(x)
11

Une alternative dans un contexte imbriqué consiste à utiliser le mot-
clé "nonlocal" :

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
...
>>> foo()
10
11


Quelles sont les règles pour les variables locales et globales en Python ?
--------------------------------------------------------------------------

En Python, si une variable n'est pas modifiée dans une fonction mais
seulement lue, elle est implicitement considérée comme globale. Si une
valeur lui est affectée, elle est considérée locale (sauf si elle est
explicitement déclarée globale).

Bien que surprenant au premier abord, ce choix s'explique facilement.
D'une part, exiger "global" pour des variables affectées est une
protection contre des effets de bord inattendus. D'autre part, si
"global" était obligatoire pour toutes les références à des objets
globaux, il faudrait mettre "global" partout, car il faudrait dans ce
cas déclarer globale chaque référence à une fonction native ou à un
composant d'un module importé. Le codé serait alors truffé de
déclarations "global", ce qui nuirait à leur raison d'être :
identifier les effets de bords.


Pourquoi des expressions lambda définies dans une boucle avec des valeurs différentes retournent-elles le même résultat ?
-------------------------------------------------------------------------------------------------------------------------

Assume you use a for loop to define a few different lambdas (or even
plain functions), for example:

   >>> squares = []
   >>> for x in range(5):
   ...     squares.append(lambda: x**2)

Le code précédent crée une liste de 5 expressions lambda qui calculent
chacune "x**2". En les exécutant, on pourrait s'attendre à obtenir
"0", "1", "4", "9" et "16". Elles renvoient en réalité toutes "16" :

   >>> squares[2]()
   16
   >>> squares[4]()
   16

This happens because "x" is not local to the lambdas, but is defined
in the outer scope, and it is accessed when the lambda is called ---
not when it is defined.  At the end of the loop, the value of "x" is
"4", so all the functions now return "4**2", that is "16".  You can
also verify this by changing the value of "x" and see how the results
of the lambdas change:

   >>> x = 8
   >>> squares[2]()
   64

Pour éviter ce phénomène, les valeurs doivent être stockées dans des
variables locales aux expressions lambda pour que celles-ci ne se
basent plus sur la variable globale "x" :

   >>> squares = []
   >>> for x in range(5):
   ...     squares.append(lambda n=x: n**2)

Dans ce code, "n=x" crée une nouvelle variable "n", locale à
l'expression. Cette variable est évaluée quand l'expression est
définie donc "n" a la même valeur que "x" à ce moment. La valeur de
"n" est donc "0" dans la première lambda, "1" dans la deuxième, "2"
dans la troisième et ainsi de suite. Chaque expression lambda renvoie
donc le résultat correct :

   >>> squares[2]()
   4
   >>> squares[4]()
   16

Ce comportement n'est pas propre aux expressions lambda, mais
s'applique aussi aux fonctions normales.


Comment partager des variables globales entre modules ?
-------------------------------------------------------

La manière standard de partager des informations entre modules d'un
même programme est de créer un module spécial (souvent appelé *config*
ou *cfg*) et de l'importer dans tous les modules de l'application ; le
module devient accessible depuis l'espace de nommage global. Vu qu'il
n'y a qu'une instance de chaque module, tout changement dans
l'instance est propagé partout. Par exemple :

*config.py* :

   x = 0   # Default value of the 'x' configuration setting

*mod.py* :

   import config
   config.x = 1

*main.py* :

   import config
   import mod
   print(config.x)

Pour les mêmes raisons, l'utilisation d'un module est aussi à la base
de l'implémentation du patron de conception singleton.


Quelles sont les « bonnes pratiques » pour utiliser import dans un module ?
---------------------------------------------------------------------------

De manière générale, il ne faut pas faire "from modulename import *".
Ceci encombre l'espace de nommage de l'importateur et rend la
détection de noms non-définis beaucoup plus ardue pour les analyseurs
de code.

Les modules doivent être importés au début d'un fichier. Ceci permet
d'afficher clairement de quels modules le code à besoin et évite de se
demander si le module est dans le contexte. Faire un seul "import" par
ligne rend l'ajout et la suppression d'une importation de module plus
aisé, mais importer plusieurs modules sur une même ligne prend moins
d'espace.

Il est recommandé d'importer les modules dans l'ordre suivant :

1. standard library modules -- such as "sys", "os", "argparse", "re"

2. third-party library modules (anything installed in Python's site-
   packages directory) -- such as dateutil, requests, tzdata

3. les modules développés en local

Il est parfois nécessaire de déplacer des importations dans une
fonction ou une classe pour éviter les problèmes d'importations
circulaires. Comme le souligne Gordon McMillan :

   Il n'y a aucun souci à faire des importations circulaires tant que
   les deux modules utilisent la forme "import <module>". Ça ne pose
   problème que si le second module cherche à récupérer un nom du
   premier module (*"from module import name"*) et que l'importation
   est dans l'espace de nommage du fichier. Les noms du premier module
   ne sont en effet pas encore disponibles car le premier module est
   occupé à importer le second.

Dans ce cas, si le second module n'est utilisé que dans une fonction,
l'importation peut facilement être déplacée dans cette fonction. Au
moment où l'importation sera appelée, le premier module aura fini de
s'initialiser et le second pourra faire son importation.

Il peut parfois être nécessaire de déplacer des importations de
modules hors de l'espace de plus haut niveau du code si certains de
ces modules dépendent de la machine utilisée. Dans ce cas de figure,
il est parfois impossible d'importer tous les modules au début du
fichier. Dans ce cas, il est recommandé d'importer les modules
adéquats dans le code spécifique à la machine.

Les importations ne devraient être déplacées dans un espace de nommage
local, comme dans la définition d'une fonction, que si cela est
nécessaire pour résoudre un problème comme éviter des dépendances
circulaires ou réduire le temps d'initialisation d'un module. Cette
technique est particulièrement utile si la majorité des importations
est superflue selon le flux d'exécution du programme. Il est également
pertinent de déplacer des importations dans une fonction si le module
n'est utilisé qu'au sein de cette fonction. Le premier chargement d'un
module peut être coûteux à cause du coût fixe d'initialisation d'un
module, mais charger un module plusieurs fois est quasiment gratuit,
cela ne coûte que quelques indirections dans un dictionnaire. Même si
le nom du module est sorti du contexte courant, le module est
probablement disponible dans "sys.modules".


Pourquoi les arguments par défaut sont-ils partagés entre les objets ?
----------------------------------------------------------------------

C'est un problème que rencontrent souvent les programmeurs débutants.
Examinons la fonction suivante :

   def foo(mydict={}):  # Danger: shared reference to one dict for all calls
       ... compute something ...
       mydict[key] = value
       return mydict

Au premier appel de cette fonction, "mydict" ne contient qu'un seul
élément. Au second appel, "mydict" contient deux éléments car quand
"foo()" commence son exécution, "mydict" contient déjà un élément.

On est souvent amené à croire qu'un appel de fonction créé des
nouveaux objets pour les valeurs par défaut. Ce n'est pas le cas. Les
valeurs par défaut ne sont créées qu'une et une seule fois, au moment
où la fonction est définie. Si l'objet est modifié, comme le
dictionnaire dans cet exemple, les appels suivants à cette fonction
font référence à l'objet ainsi modifié.

Par définition, les objets immuables comme les nombres, les chaînes de
caractères, les *n*-uplets et "None" ne sont pas modifiés. Les
changements sur des objets modifiables comme les dictionnaires, les
listes et les instances de classe peuvent porter à confusion.

En raison de cette fonctionnalité, il vaut mieux ne pas utiliser
d'objets modifiables comme valeurs par défaut. Il vaut mieux utiliser
"None" comme valeur par défaut et, à l'intérieur de la fonction,
vérifier si le paramètre est à "None" et créer une nouvelle liste,
dictionnaire ou autre, le cas échéant. Par exemple, il ne faut pas
écrire :

   def foo(mydict={}):
       ...

mais plutôt :

   def foo(mydict=None):
       if mydict is None:
           mydict = {}  # create a new dict for local namespace

Cette fonctionnalité a une utilité. Il est courant de mettre en cache
les paramètres et la valeur de retour de chacun des appels d'une
fonction coûteuse à exécuter, et de renvoyer la valeur stockée en
cache si le même appel est ré-effectué. C'est la technique dite de «
mémoïsation », qui s'implémente de la manière suivante :

   # Callers can only provide two parameters and optionally pass _cache by keyword
   def expensive(arg1, arg2, *, _cache={}):
       if (arg1, arg2) in _cache:
           return _cache[(arg1, arg2)]

       # Calculate the value
       result = ... expensive computation ...
       _cache[(arg1, arg2)] = result           # Store result in the cache
       return result

Il est possible d'utiliser une variable globale contenant un
dictionnaire à la place de la valeur par défaut ; ce n'est qu'une
question de goût.


Comment passer des paramètres optionnels ou nommés d'une fonction à l'autre ?
-----------------------------------------------------------------------------

Il faut récupérer les arguments en utilisant les sélecteurs "*" et
"**" dans la liste des paramètres de la fonction ; ceci donne les
arguments positionnels sous la forme d'un *n*-uplet et les arguments
nommés sous forme de dictionnaire. Ces arguments peuvent être passés à
une autre fonction en utilisant "*" et "**" :

   def f(x, *args, **kwargs):
       ...
       kwargs['width'] = '14.3c'
       ...
       g(x, *args, **kwargs)


Quelle est la différence entre les arguments et les paramètres ?
----------------------------------------------------------------

Les *paramètres* sont les noms qui apparaissent dans une définition de
fonction, alors que les *arguments* sont les valeurs qui sont
réellement passées à une fonction lors de l'appel de celle-ci. Les
paramètres définissent les *types d'arguments* qu'une fonction
accepte. Ainsi, avec la définition de fonction suivante :

   def func(foo, bar=None, **kwargs):
       pass

*foo*, *bar* et *kwargs* sont des paramètres de "func". Mais à l'appel
de "func" avec, par exemple :

   func(42, bar=314, extra=somevar)

les valeurs "42", "314", et "somevar" sont des arguments.


Pourquoi modifier la liste 'y' modifie aussi la liste 'x' ?
-----------------------------------------------------------

Si vous avez écrit du code comme :

   >>> x = []
   >>> y = x
   >>> y.append(10)
   >>> y
   [10]
   >>> x
   [10]

vous vous demandez peut-être pourquoi l'ajout d'un élément à "y" a
aussi changé "x".

Il y a deux raisons qui conduisent à ce comportement :

1. Les variables ne sont que des noms qui font référence à des objets.
   La ligne "y = x" ne crée pas une copie de la liste — elle crée une
   nouvelle variable "y" qui pointe sur le même objet que "x". Ceci
   signifie qu'il n'existe qu'un seul objet (la liste) auquel "x" et
   "y" font référence.

2. Les listes sont des *mutable*, ce qui signifie que leur contenu
   peut être modifié.

After the call to "append()", the content of the mutable object has
changed from "[]" to "[10]".  Since both the variables refer to the
same object, using either name accesses the modified value "[10]".

Si au contraire, on affecte un objet immuable à "x" :

   >>> x = 5  # ints are immutable
   >>> y = x
   >>> x = x + 1  # 5 can't be mutated, we are creating a new object here
   >>> x
   6
   >>> y
   5

on observe que "x" et "y" ne sont ici plus égales. Les entiers sont
des immuables (*immutable*), et "x = x + 1" ne change pas l'entier "5"
en incrémentant sa valeur. Au contraire, un nouvel objet est créé
(l'entier "6") et affecté à "x" (c'est-à-dire qu'on change l'objet
auquel fait référence "x"). Après cette affectation on a deux objets
(les entiers "6" et "5") et deux variables qui font référence à ces
deux objets ("x" fait désormais référence à "6" mais "y" fait toujours
référence à "5").

Certaines opérations (par exemple, "y.append(10)" et "y.sort()")
modifient l'objet, alors que des opérations identiques en apparence
(par exemple "y = y + [10]" et "sorted(y)()") créent un nouvel objet.
En général, en Python, une méthode qui modifie un objet renvoie "None"
(c'est même systématique dans la bibliothèque standard) pour éviter la
confusion entre les deux opérations. Donc écrire par erreur "y.sort()"
en pensant obtenir une copie triée de "y" donne "None", ce qui conduit
très souvent le programme à générer une erreur facile à diagnostiquer.

Il existe cependant une classe d'opérations qui se comporte
différemment selon le type : les opérateurs d'affectation
incrémentaux. Par exemple, "+=" modifie les listes mais pas les
*n*-uplets ni les entiers ("a_list += [1, 2, 3]" équivaut à
"a_list.extend([1, 2, 3])" et modifie "a_list", alors que "some_tuple
+= (1, 2, 3)" et "some_int += 1" créent de nouveaux objets).

En d'autres termes :

* If we have a mutable object (such as "list", "dict", "set"), we can
  use some specific operations to mutate it and all the variables that
  refer to it will see the change.

* If we have an immutable object (such as "str", "int", "tuple"), all
  the variables that refer to it will always see the same value, but
  operations that transform that value into a new value always return
  a new object.

L'opérateur "is" ou la fonction native "id()" permettent de savoir si
deux variables font référence au même objet.


Comment écrire une fonction qui modifie ses paramètres ? (passage par référence)
--------------------------------------------------------------------------------

Remember that arguments are passed by assignment in Python.  Since
assignment just creates references to objects, there's no alias
between an argument name in the caller and callee, and consequently no
call-by-reference.  You can achieve the desired effect in a number of
ways.

1. En renvoyant un *n*-uplet de résultats :

      >>> def func1(a, b):
      ...     a = 'new-value'        # a and b are local names
      ...     b = b + 1              # assigned to new objects
      ...     return a, b            # return new values
      ...
      >>> x, y = 'old-value', 99
      >>> func1(x, y)
      ('new-value', 100)

   C'est presque toujours la meilleure solution.

2. En utilisant des variables globales. Cette approche ne fonctionne
   pas dans des contextes à plusieurs fils d'exécution (elle n'est pas
   *thread-safe*), et n'est donc pas recommandée.

3. En passant un objet mutable (modifiable sur place) :

      >>> def func2(a):
      ...     a[0] = 'new-value'     # 'a' references a mutable list
      ...     a[1] = a[1] + 1        # changes a shared object
      ...
      >>> args = ['old-value', 99]
      >>> func2(args)
      >>> args
      ['new-value', 100]

4. En passant un dictionnaire, qui sera modifié :

      >>> def func3(args):
      ...     args['a'] = 'new-value'     # args is a mutable dictionary
      ...     args['b'] = args['b'] + 1   # change it in-place
      ...
      >>> args = {'a': 'old-value', 'b': 99}
      >>> func3(args)
      >>> args
      {'a': 'new-value', 'b': 100}

5. Ou regrouper les valeurs dans une instance de classe :

      >>> class Namespace:
      ...     def __init__(self, /, **args):
      ...         for key, value in args.items():
      ...             setattr(self, key, value)
      ...
      >>> def func4(args):
      ...     args.a = 'new-value'        # args is a mutable Namespace
      ...     args.b = args.b + 1         # change object in-place
      ...
      >>> args = Namespace(a='old-value', b=99)
      >>> func4(args)
      >>> vars(args)
      {'a': 'new-value', 'b': 100}

   Faire quelque chose d'aussi compliqué est rarement une bonne idée.

La meilleure option reste de renvoyer un *n*-uplet contenant les
différents résultats.


Comment construire une fonction d'ordre supérieur en Python ?
-------------------------------------------------------------

Deux possibilités : on peut utiliser des portées imbriquées ou bien
des objets appelables. Par exemple, supposons que l'on souhaite
définir "linear(a, b)" qui renvoie une fonction "f(x)" qui calcule la
valeur "a*x+b". En utilisant les portées imbriquées :

   def linear(a, b):
       def result(x):
           return a * x + b
       return result

Ou en utilisant un objet appelable :

   class linear:

       def __init__(self, a, b):
           self.a, self.b = a, b

       def __call__(self, x):
           return self.a * x + self.b

Dans les deux cas

   taxes = linear(0.3, 2)

donne un objet appelable où "taxes(10e6) == 0.3 * 10e6 + 2".

L'approche par objet appelable a le désavantage d'être légèrement plus
lente et de produire un code légèrement plus long. Cependant, il faut
noter qu'une collection d'objet appelables peuvent partager leurs
signatures par héritage :

   class exponential(linear):
       # __init__ inherited
       def __call__(self, x):
           return self.a * (x ** self.b)

Les objets peuvent encapsuler un état pour plusieurs méthodes :

   class counter:

       value = 0

       def set(self, x):
           self.value = x

       def up(self):
           self.value = self.value + 1

       def down(self):
           self.value = self.value - 1

   count = counter()
   inc, dec, reset = count.up, count.down, count.set

Ici "inc()", "dec()" et "reset()" agissent comme des fonctions
partageant une même variable compteur.


Comment copier un objet en Python ?
-----------------------------------

En général, essayez "copy.copy()" ou "copy.deepcopy()". Tous les
objets ne peuvent pas être copiés, mais la plupart le peuvent.

Certains objets peuvent être copiés plus facilement que d'autres. Les
dictionnaires ont une méthode "copy()" :

   newdict = olddict.copy()

Les séquences peuvent être copiées via la syntaxe des tranches :

   new_l = l[:]


Comment récupérer les méthodes ou les attributs d'un objet ?
------------------------------------------------------------

Pour une instance "x" d'une classe définie par un utilisateur,
"dir(x)" renvoie une liste alphabétique des noms contenants les
attributs de l'instance, et les attributs et méthodes définies par sa
classe.


Comment un code peut-il obtenir le nom d'un objet ?
---------------------------------------------------

C'est impossible en général, parce qu'un objet n'a pas de nom à
proprement parler. Schématiquement, l'affectation fait correspondre un
nom à une valeur ; c'est vrai aussi pour les instructions "def" et
"class", à la différence près que, dans ce cas, la valeur est un
appelable. Par exemple, dans le code suivant :

   >>> class A:
   ...     pass
   ...
   >>> B = A
   >>> a = B()
   >>> b = a
   >>> print(b)
   <__main__.A object at 0x16D07CC>
   >>> print(a)
   <__main__.A object at 0x16D07CC>

Affirmer que la classe a un nom est discutable. Bien qu'elle soit liée
à deux noms, et qu'elle soit appelée via le nom "B", l'instance créée
déclare tout de même être une instance de la classe "A". De même, il
est impossible de dire si le nom de l'instance est "a" ou "b", les
deux noms étant attachés à la même valeur.

De façon générale, une application ne devrait pas avoir besoin de «
connaître le nom » d'une valeur particulière. À moins d'être
délibérément en train d'écrire un programme introspectif, c'est
souvent l'indication qu'un changement d'approche serait bénéfique.

Sur *comp.lang.python*, Fredrik Lundh a donné un jour une excellente
analogie pour répondre à cette question :

   C'est pareil que trouver le nom du chat qui traîne devant votre
   porte : le chat (objet) ne peut pas vous dire lui-même son nom, et
   il s'en moque un peu ­– alors le meilleur moyen de savoir comment
   il s'appelle est de demander à tous vos voisins (espaces de
   nommage) si c'est leur chat (objet)…

   …et ne soyez pas surpris si vous découvrez qu'il est connu sous
   plusieurs noms, ou s'il n'a pas de nom du tout !


Qu'en est-il de la précédence de l'opérateur virgule ?
------------------------------------------------------

La virgule n'est pas un opérateur en Python. Observez le code suivant
:

   >>> "a" in "b", "a"
   (False, 'a')

Comme la virgule n'est pas un opérateur, mais un séparateur entre deux
expressions, l'expression ci-dessus est évaluée de la même façon que
si vous aviez écrit :

   ("a" in "b"), "a"

et non :

   "a" in ("b", "a")

The same is true of the various assignment operators ("=", "+=", and
so on). They are not truly operators but syntactic delimiters in
assignment statements.


Existe-t-il un équivalent à l'opérateur ternaire « ?: » du C ?
--------------------------------------------------------------

Oui. Sa syntaxe est la suivante :

   [on_true] if [expression] else [on_false]

   x, y = 50, 25
   small = x if x < y else y

Avant l'introduction de cette syntaxe dans Python 2.5, il était
courant d'utiliser les opérateurs de logique :

   [expression] and [on_true] or [on_false]

Cet idiome est dangereux, car il donne un résultat erroné quand
*on_true* a la valeur booléenne fausse. Il faut donc toujours utiliser
la forme "... if ... else ...".


Est-il possible d'écrire des programmes obscurcis (*obfuscated*) d'une ligne en Python ?
----------------------------------------------------------------------------------------

Oui. C'est souvent le cas en imbriquant des "lambda" dans des
"lambda". Par exemple les trois morceaux de code suivants, légèrement
adaptés par Ulf Bartelt :

   from functools import reduce

   # Primes < 1000
   print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
   map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))

   # First 10 Fibonacci numbers
   print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
   f(x,f), range(10))))

   # Mandelbrot set
   print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
   Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
   Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
   i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
   >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
   64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
   ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
   #    \___ ___/  \___ ___/  |   |   |__ lines on screen
   #        V          V      |   |______ columns on screen
   #        |          |      |__________ maximum of "iterations"
   #        |          |_________________ range on y axis
   #        |____________________________ range on x axis

Les enfants, ne faites pas ça chez vous !


Que signifie la barre oblique (/) dans la liste des paramètres d'une fonction ?
-------------------------------------------------------------------------------

Une barre oblique dans la liste des arguments d'une fonction indique
que les paramètres la précédant sont uniquement positionnels. Les
paramètres uniquement positionnels ne peuvent pas être référencés par
leur nom depuis l'extérieur. Lors de l'appel d'une fonction qui
accepte des paramètres uniquement positionnels, les arguments sont
affectés aux paramètres en fonction de leur position. Par exemple, la
fonction "divmod()" n'accepte que des paramètres uniquement
positionnels. Sa documentation est la suivante :

   >>> help(divmod)
   Help on built-in function divmod in module builtins:

   divmod(x, y, /)
       Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.

La barre oblique à la fin de la liste des paramètres signifie que les
trois paramètres sont uniquement positionnels. Et donc, appeler
"divmod()" avec des arguments nommés provoque une erreur :

   >>> divmod(x=3, y=4)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: divmod() takes no keyword arguments


Nombres et chaînes de caractères
================================


Comment écrire des entiers hexadécimaux ou octaux ?
---------------------------------------------------

Pour écrire un entier octal, faites précéder la valeur octale par un
zéro, puis un "o" majuscule ou minuscule. Par exemple pour affecter la
valeur octale "10" (8 en décimal) à la variable "a", tapez :

   >>> a = 0o10
   >>> a
   8

L'hexadécimal est tout aussi simple, faites précéder le nombre
hexadécimal par un zéro, puis un "x" majuscule ou minuscule. Les
nombres hexadécimaux peuvent être écrits en majuscules ou en
minuscules. Par exemple, dans l'interpréteur Python :

   >>> a = 0xa5
   >>> a
   165
   >>> b = 0XB2
   >>> b
   178


Pourquoi "-22 // 10" donne-t-il "-3" ?
--------------------------------------

Cela est principalement dû à la volonté que "i % j" ait le même signe
que j. Si vous voulez en plus que :

   i == (i // j) * j + (i % j)

alors la division entière doit renvoyer l'entier inférieur. Le C
impose également que cette égalité soit vérifiée, et donc les
compilateurs qui tronquent "i // j" ont besoin que "i % j" ait le même
signe que "i".

Il y a peu de cas d'utilisation réels pour "i % j" quand "j" est
négatif. Quand "j" est positif, il y en a beaucoup, et dans
pratiquement tous, il est plus utile que "i % j" soit ">=0".  Si
l'horloge affiche 10 h maintenant, qu'affichait-elle il y a 200 heures
? "-190 % 12 == 2" est utile ; "-190 % 12 == -10" est un bogue en
puissance.


Pourquoi ai-je une erreur de syntaxe en essayant de lire un attribut d'un entier littéral ?
-------------------------------------------------------------------------------------------

Essayer d'utiliser l'opérateur d'accès à un attribut sur un entier
littéral lève une "SyntaxError" car le point est compris comme un
séparateur décimal en notation anglo-saxonne :

   >>> 1.__class__
     File "<stdin>", line 1
     1.__class__
      ^
   SyntaxError: invalid decimal literal

Il faut séparer l'entier du point, soit avec une espace, soit avec des
parenthèses.

>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>


Comment convertir une chaîne de caractères en nombre ?
------------------------------------------------------

For integers, use the built-in "int()" type constructor, for example,
"int('144') == 144".  Similarly, "float()" converts to a floating-
point number, for example, "float('144') == 144.0".

Par défaut, ces fonctions interprètent les nombres comme des décimaux,
de telle façon que "int('0144') == 144" et "int('0x144')" lève une
"ValueError". Le second argument (optionnel) de "int(string, base)"
est la base dans laquelle convertir, donc "int('0x144', 16) == 324".
Si la base donnée est 0, le nombre est interprété selon les règles
Python : un préfixe "0o" indique de l'octal et "0x" indique de
l'hexadécimal.

N'utilisez pas la fonction native "eval()" pour convertir des chaînes
de caractères en nombres. "eval()" est beaucoup plus lente et pose des
problèmes de sécurité : quelqu'un pourrait vous envoyer une expression
Python pouvant avoir des effets de bord indésirables. Par exemple,
quelqu'un pourrait passer "__import__('os').system("rm -rf $HOME")" ce
qui effacerait votre répertoire personnel.

"eval()" also has the effect of interpreting numbers as Python
expressions, so that, for example, "eval('09')" gives a syntax error
because Python does not allow leading '0' in a decimal number (except
'0').


Comment convertir un nombre en chaîne de caractères ?
-----------------------------------------------------

For example, to convert the number "144" to the string "'144'", use
the built-in type constructor "str()".  If you want a hexadecimal or
octal representation, use the built-in functions "hex()" or "oct()".
For fancy formatting, see the f-strings and Format string syntax
sections. For example, ""{:04d}".format(144)" yields "'0144'" and
""{:.3f}".format(1.0/3.0)" yields "'0.333'".


Comment modifier une chaîne de caractères « sur place » ?
---------------------------------------------------------

You can't, because strings are immutable.  In most situations, you
should simply construct a new string from the various parts you want
to assemble it from.  However, if you need an object with the ability
to modify in-place Unicode data, try using an "io.StringIO" object or
the "array" module:

   >>> import io
   >>> s = "Hello, world"
   >>> sio = io.StringIO(s)
   >>> sio.getvalue()
   'Hello, world'
   >>> sio.seek(7)
   7
   >>> sio.write("there!")
   6
   >>> sio.getvalue()
   'Hello, there!'

   >>> import array
   >>> a = array.array('w', s)
   >>> print(a)
   array('w', 'Hello, world')
   >>> a[0] = 'y'
   >>> print(a)
   array('w', 'yello, world')
   >>> a.tounicode()
   'yello, world'


Comment utiliser des chaînes de caractères pour appeler des fonctions/méthodes ?
--------------------------------------------------------------------------------

Il y a plusieurs façons de faire.

* La meilleure est d'utiliser un dictionnaire qui fait correspondre
  les chaînes de caractères à des fonctions. Le principal avantage de
  cette technique est que les chaînes n'ont pas besoin d'être égales
  aux noms de fonctions. C'est aussi la façon principale d'imiter la
  construction "case" :

     def a():
         pass

     def b():
         pass

     dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs

     dispatch[get_input()]()  # Note trailing parens to call function

* Utiliser la fonction "getattr()" :

     import foo
     getattr(foo, 'bar')()

  Notez que "getattr()" marche sur n'importe quel objet, ceci inclut
  les classes, les instances de classes, les modules et ainsi de
  suite.

  Ceci est utilisé à plusieurs reprises dans la bibliothèque standard,
  de cette façon :

     class Foo:
         def do_foo(self):
             ...

         def do_bar(self):
             ...

     f = getattr(foo_instance, 'do_' + opname)
     f()

* Utilisez "locals()" pour résoudre le nom de la fonction :

     def myFunc():
         print("hello")

     fname = "myFunc"

     f = locals()[fname]
     f()


Is there an equivalent to Perl's "chomp()" for removing trailing newlines from strings?
---------------------------------------------------------------------------------------

Vous pouvez utiliser "S.rstrip("\r\n")" pour retirer toutes les
occurrences de tout marqueur de fin de ligne à la fin d'une chaîne de
caractère "S", sans en enlever aucune espace. Si la chaîne "S"
représente plus d'une ligne, avec plusieurs lignes vides, les
marqueurs de fin de ligne de chaque ligne vide seront retirés :

   >>> lines = ("line 1 \r\n"
   ...          "\r\n"
   ...          "\r\n")
   >>> lines.rstrip("\n\r")
   'line 1 '

Vu que cela ne sert presque qu'à lire un texte ligne à ligne, utiliser
"S.rstrip()" de cette manière fonctionne correctement.


Is there a "scanf()" or "sscanf()" equivalent?
----------------------------------------------

Pas exactement.

For simple input parsing, the easiest approach is usually to split the
line into whitespace-delimited words using the "split()" method of
string objects and then convert decimal strings to numeric values
using "int()" or "float()".  "split()" supports an optional "sep"
parameter which is useful if the line uses something other than
whitespace as a separator.

Pour des analyses plus compliquées, les expressions rationnelles sont
plus puissantes que la fonction "sscanf" de C et mieux adaptées à la
tâche.


What does "UnicodeDecodeError" or "UnicodeEncodeError" error mean?
------------------------------------------------------------------

Voir Guide Unicode.


Can I end a raw string with an odd number of backslashes?
---------------------------------------------------------

A raw string ending with an odd number of backslashes will escape the
string's quote:

   >>> r'C:\this\will\not\work\'
     File "<stdin>", line 1
       r'C:\this\will\not\work\'
       ^
   SyntaxError: unterminated string literal (detected at line 1)

There are several workarounds for this. One is to use regular strings
and double the backslashes:

   >>> 'C:\\this\\will\\work\\'
   'C:\\this\\will\\work\\'

Another is to concatenate a regular string containing an escaped
backslash to the raw string:

   >>> r'C:\this\will\work' '\\'
   'C:\\this\\will\\work\\'

It is also possible to use "os.path.join()" to append a backslash on
Windows:

   >>> os.path.join(r'C:\this\will\work', '')
   'C:\\this\\will\\work\\'

Note that while a backslash will "escape" a quote for the purposes of
determining where the raw string ends, no escaping occurs when
interpreting the value of the raw string. That is, the backslash
remains present in the value of the raw string:

   >>> r'backslash\'preserved'
   "backslash\\'preserved"

Also see the specification in the language reference.


Performances
============


Mon programme est trop lent. Comment l'accélérer ?
--------------------------------------------------

That's a tough one, in general.  First, here is a list of things to
remember before diving further:

* Les performances varient en fonction des implémentations de Python.
  Cette FAQ ne traite que de *CPython*.

* Les comportements peuvent différer d'un système d'exploitation à
  l'autre, tout particulièrement quand il s'agit d'entrée/sortie ou de
  fils d'exécution multiples.

* Il faut toujours essayer de trouver où sont les points de contention
  d'un programme *avant* d'essayer d'optimiser du code (voir le module
  "profile").

* Écrire des scripts d'évaluation de performances permet de progresser
  rapidement dans la recherche d'améliorations (voir le module
  "timeit").

* Il est très fortement recommandé d'avoir une bonne couverture de
  code (avec des tests unitaires ou autre) avant d'ajouter des erreurs
  dans des optimisations sophistiquées.

Ceci étant dit, il y a beaucoup d'astuces pour accélérer du code
Python. Voici quelques principes généraux qui peuvent aider à
atteindre des niveaux de performance satisfaisants :

* Améliorer les algorithmes (ou en changer pour des plus performants)
  peut produire de bien meilleurs résultats que d'optimiser çà et là
  de petites portions du code.

* Utiliser les structures de données adaptées. Se référer à la
  documentation des Types natifs et du module "collections".

* Quand la bibliothèque standard fournit une implémentation pour
  quelque chose, il y a de fortes chances (même si ce n'est pas
  systématique) que cette implémentation soit plus rapide que la
  vôtre. C'est d'autant plus vrai pour les routines écrites en C,
  comme les routines natives et certaines extensions de types. Par
  exemple, il faut utiliser la méthode native "list.sort()" ou la
  fonction "sorted()" similaire pour classer (et se référer à la
  section Les techniques de tri pour des exemples d'utilisation
  courante).

* Les abstractions ont tendance à créer des indirections et obligent
  l'interpréteur à faire plus d'efforts. Si le niveau d'indirection
  dépasse la quantité de travail effectif, le programme sera ralenti.
  Il faut toujours éviter trop d'indirections, en particulier sous la
  forme de fonctions ou méthodes trop petites (qui nuisent aussi
  souvent à la clarté du code).

Si vous atteignez les limites de ce que du Python « pur » permet de
faire, il y a des outils qui permettent d'aller plus loin. Par
exemple, Cython peut compiler une version légèrement modifiée de code
Python en une extension C et est disponible sur de nombreuses plate-
formes. Cython peut bénéficier de la compilation (et de l'annotation,
optionnelle, des types) pour rendre votre code beaucoup plus rapide
que s'il était interprété. Si vous avez confiance en vos capacités de
programmation en C, vous pouvez aussi écrire un module d'extension en
C vous-même.

Voir aussi: La page wiki dédiée aux astuces de performance.


Quelle est la manière la plus efficace de concaténer un grand nombre de chaînes de caractères ?
-----------------------------------------------------------------------------------------------

Les objets "str" et "bytes" sont immuables, par conséquent concaténer
un grand nombre de chaînes de caractères entre elles n'est pas très
efficace car chaque concaténation crée un nouvel objet. Dans le cas
général, la complexité est quadratique par rapport à la taille totale
de la chaîne.

Pour mettre bout à bout un grand nombre d'objets "str", la technique
recommandée consiste à toutes les mettre dans une liste et appeler la
méthode "str.join()" à la fin :

   chunks = []
   for s in my_strings:
       chunks.append(s)
   result = ''.join(chunks)

(Another reasonably efficient idiom is to use "io.StringIO".)

Pour concaténer un grand nombre d'objets "bytes", la technique
recommandée consiste à étendre un objet "bytearray" en utilisant la
concaténation en-place (l'opérateur "+=") :

   result = bytearray()
   for b in my_bytes_objects:
       result += b


Sequences (tuples/lists)
========================


Comment convertir les listes en *n*-uplets et inversement ?
-----------------------------------------------------------

Le constructeur de type "tuple(seq)" convertit toute séquence (plus
précisément, tout itérable) en un *n*-uplet avec les mêmes éléments
dans le même ordre.

Par exemple "tuple([1, 2, 3])" renvoie "(1, 2, 3)" et "tuple('abc')"
renvoie "('a', 'b', 'c')". Si l'argument est un *n*-uplet, cela ne
crée pas de copie, mais renvoie le même objet, ce qui fait de
"tuple()" une fonction économique à appeler quand vous ne savez pas si
votre objet est déjà un *n*-uplet.

Le constructeur de type "list(seq)" convertit toute séquence ou
itérable en liste contenant les mêmes éléments dans le même ordre. Par
exemple, "list((1,2,3))" renvoie "[1,2,3]" et "list('abc')" renvoie
"['a','b','c']". Si l'argument est une liste, il renvoie une copie, de
la même façon que "seq[:]".


Qu'est-ce qu'un indice négatif ?
--------------------------------

Les séquences Python sont indicées avec des nombres positifs aussi
bien que négatifs. Pour les nombres positifs, 0 est le premier indice,
1 est le deuxième, et ainsi de suite. Pour les indices négatifs, "-1"
est le dernier indice, "-2" est le pénultième (avant-dernier), et
ainsi de suite. On peut aussi dire que "seq[-n]" est équivalent à
"seq[len(seq)-n]".

Utiliser des indices négatifs peut être très pratique. Par exemple
"S[:-1]" représente la chaîne tout entière à l'exception du dernier
caractère, ce qui est pratique pour retirer un caractère de fin de
ligne à la fin d'une chaîne.


Comment itérer à rebours sur une séquence ?
-------------------------------------------

Utilisez la fonction native "reversed()" :

   for x in reversed(sequence):
       ...  # do something with x ...

Cela ne modifie pas la séquence initiale, mais construit à la place
une copie en ordre inverse pour itérer dessus.


Comment retirer les doublons d'une liste ?
------------------------------------------

Lisez le « livre de recettes » Python pour trouver une longue
discussion sur les nombreuses approches possibles :

   https://code.activestate.com/recipes/52560/

Si changer l'ordre de la liste ne vous dérange pas, commencez par
ordonner celle-ci, puis parcourez-la d'un bout à l'autre, en
supprimant les doublons trouvés en chemin :

   if mylist:
       mylist.sort()
       last = mylist[-1]
       for i in range(len(mylist)-2, -1, -1):
           if last == mylist[i]:
               del mylist[i]
           else:
               last = mylist[i]

If all elements of the list may be used as set keys (that is, they are
all *hashable*) this is often faster:

   mylist = list(set(mylist))

Ceci convertit la liste en un ensemble, ce qui supprime
automatiquement les doublons, puis la transforme à nouveau en liste.


How do you remove multiple items from a list?
---------------------------------------------

As with removing duplicates, explicitly iterating in reverse with a
delete condition is one possibility.  However, it is easier and faster
to use slice replacement with an implicit or explicit forward
iteration. Here are three variations:

   mylist[:] = filter(keep_function, mylist)
   mylist[:] = (x for x in mylist if keep_condition)
   mylist[:] = [x for x in mylist if keep_condition]

La liste en compréhension est peut-être la plus rapide :


Comment construire un tableau en Python ?
-----------------------------------------

Utilisez une liste :

   ["this", 1, "is", "an", "array"]

Les listes ont un coût équivalent à celui des tableaux C ou Pascal ;
la principale différence est qu'une liste Python peut contenir des
objets de différents types.

The "array" module also provides methods for creating arrays of fixed
types with compact representations, but they are slower to index than
lists.  Also note that NumPy and other third-party packages define
array-like structures with various characteristics as well.

Pour obtenir des listes chaînées à la sauce Lisp, vous pouvez émuler
les *cons cells* en utilisant des *n*-uplets :

   lisp_list = ("like",  ("this",  ("example", None) ) )

Si vous voulez pouvoir modifier les éléments, utilisez une liste
plutôt qu'un *n*-uplet. Ici la version équivalente du *car* de Lisp
est "lisp_list[0]" et l'équivalent de *cdr* est "lisp_list[1]". Ne
faites ceci que si vous êtes réellement sûr d'en avoir besoin, cette
méthode est en général bien plus lente que les listes Python.


Comment créer une liste à plusieurs dimensions ?
------------------------------------------------

Vous avez probablement essayé de créer une liste à plusieurs
dimensions de cette façon :

   >>> A = [[None] * 2] * 3

Elle semble correcte si on l'affiche :

   >>> A
   [[None, None], [None, None], [None, None]]

Mais quand vous affectez une valeur, celle-ci apparaît à plusieurs
endroits :

   >>> A[0][0] = 5
   >>> A
   [[5, None], [5, None], [5, None]]

Dupliquer une liste en utilisant "*" ne crée en réalité pas de copie
mais seulement des références aux objets existants. Le "*3" crée une
liste contenant trois références à la même liste de longueur deux. Un
changement dans une colonne apparaîtra donc dans toutes les colonnes,
ce qui n'est très probablement pas ce que vous souhaitiez.

L'approche suggérée est d'abord de créer une liste de la longueur
désirée, puis de remplir tous les éléments avec une nouvelle chaîne :

   A = [None] * 3
   for i in range(3):
       A[i] = [None] * 2

Cela génère une liste contenant elle-même trois listes distinctes, de
longueur deux. Vous pouvez aussi utiliser la syntaxe des listes en
compréhension :

   w, h = 2, 3
   A = [[None] * w for i in range(h)]

Vous pouvez aussi utiliser une extension qui fournit un type matriciel
natif ; NumPy est la plus répandue.


Comment appliquer une méthode ou une fonction à une séquence d'objets ?
-----------------------------------------------------------------------

To call a method or function and accumulate the return values in a
list, a *list comprehension* is an elegant solution:

   result = [obj.method() for obj in mylist]

   result = [function(obj) for obj in mylist]

To just run the method or function without saving the return values, a
plain "for" loop will suffice:

   for obj in mylist:
       obj.method()

   for obj in mylist:
       function(obj)


Pourquoi "a_tuple[i] += ['item']" lève-t-il une exception alors que l'addition fonctionne ?
-------------------------------------------------------------------------------------------

Ceci est dû à la combinaison de deux facteurs : le fait que les
opérateurs d'affectation incrémentaux sont des opérateurs
d'*affectation* et à la différence entre les objets mutables et
immuables en Python.

Cette discussion est valable, en général, quand des opérateurs
d'affectation incrémentale sont appliqués aux éléments d'un *n*-uplet
qui pointe sur des objets mutables, mais on prendra "list" et "+="
comme exemple.

Si vous écrivez :

   >>> a_tuple = (1, 2)
   >>> a_tuple[0] += 1
   Traceback (most recent call last):
      ...
   TypeError: 'tuple' object does not support item assignment

La cause de l'exception est claire : "1" est ajouté à l'objet
"a_tuple[0]" qui pointe sur ("1"), ce qui produit l'objet résultant
"2", mais, lorsque l'on tente d'affecter le résultat du calcul, "2", à
l'élément "0" du *n*-uplet, on obtient une erreur car il est
impossible de modifier la cible sur laquelle pointe un élément d'un
*n*-uplet.

Sous le capot, une instruction d'affectation incrémentale fait à peu
près ceci :

   >>> result = a_tuple[0] + 1
   >>> a_tuple[0] = result
   Traceback (most recent call last):
     ...
   TypeError: 'tuple' object does not support item assignment

C'est la partie de l'affectation de l'opération qui génère l'erreur,
vu qu'un *n*-uplet est immuable.

Quand vous écrivez un code du style :

   >>> a_tuple = (['foo'], 'bar')
   >>> a_tuple[0] += ['item']
   Traceback (most recent call last):
     ...
   TypeError: 'tuple' object does not support item assignment

L'exception est un peu plus surprenante et, chose encore plus étrange,
malgré l'erreur, l'ajout a fonctionné :

   >>> a_tuple[0]
   ['foo', 'item']

To see why this happens, you need to know that (a) if an object
implements an "__iadd__()" magic method, it gets called when the "+="
augmented assignment is executed, and its return value is what gets
used in the assignment statement; and (b) for lists, "__iadd__()" is
equivalent to calling "extend()" on the list and returning the list.
That's why we say that for lists, "+=" is a "shorthand" for
"list.extend()":

   >>> a_list = []
   >>> a_list += [1]
   >>> a_list
   [1]

C’est équivalent à :

   >>> result = a_list.__iadd__([1])
   >>> a_list = result

L'objet sur lequel pointe "a_list" a été modifié et le pointeur vers
l'objet modifié est réaffecté à "a_list". *In fine*, l'affectation ne
change rien, puisque c'est un pointeur vers le même objet que sur
lequel pointait "a_list", mais l'affectation a tout de même lieu.

Donc, dans notre exemple avec un *n*-uplet, il se passe quelque chose
équivalent à :

   >>> result = a_tuple[0].__iadd__(['item'])
   >>> a_tuple[0] = result
   Traceback (most recent call last):
     ...
   TypeError: 'tuple' object does not support item assignment

L'appel à "__iadd__()" réussit et la liste est étendue, mais bien que
"result" pointe sur le même objet que "a_tuple[0]", l'affectation
finale échoue car les *n*-uplets ne sont pas mutables.


Je souhaite faire un classement compliqué : peut-on faire une transformation de Schwartz en Python ?
----------------------------------------------------------------------------------------------------

Cette technique, attribuée à Randal Schwartz de la communauté Perl,
ordonne les éléments d'une liste à l'aide une transformation qui fait
correspondre chaque élément à sa "valeur de tri". En Python, ceci est
géré par l'argument "key" de la méthode "list.sort()" :

   Isorted = L[:]
   Isorted.sort(key=lambda s: int(s[10:15]))


Comment ordonner une liste en fonction des valeurs d'une autre liste ?
----------------------------------------------------------------------

Merge them into an iterator of tuples, sort the resulting list, and
then pick out the element you want.

>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']


Objets
======


Qu'est-ce qu'une classe ?
-------------------------

Une classe est le type d'objet particulier créé par l'exécution d'une
déclaration de classe. Les objets de classe sont utilisés comme
modèles pour créer des objets, qui incarnent à la fois les données
(attributs) et le code (méthodes) spécifiques à un type de données.

Une classe peut être fondée sur une ou plusieurs autres classes,
appelée sa (ou ses) classe(s) de base. Elle hérite alors des attributs
et des méthodes de ses classes mères. Cela permet à un modèle d'objet
d'être successivement raffiné par héritage. Vous pourriez avoir une
classe générique "Mailbox", qui fournit des méthodes d'accès de base
pour une boîte aux lettres, et des sous-classes telles que
"MboxMailbox", "MaildirMailbox", "OutlookMailbox" qui gèrent les
plusieurs formats spécifiques de boîtes aux lettres.


Qu'est-ce qu'une méthode ?
--------------------------

Une méthode est une fonction sur un  objet "x" qu'on appelle de
manière générale sous la forme "x.name(arguments…)". Les méthodes sont
définies comme des fonctions à l'intérieur de la définition de classe
:

   class C:
       def meth(self, arg):
           return arg * 2 + self.attribute


Qu'est-ce que self ?
--------------------

Par convention, le premier argument d'une méthode est appelé self. Une
méthode "meth(self, a, b, c)" doit être appelée sous la forme
"x.meth(a, b, c)" où "x" est une instance de la classe dans laquelle
cette méthode est définie ; tout se passe comme si la méthode était
appelée comme "meth(x, a, b, c)".

Voir aussi Pourquoi self doit-il être explicitement utilisé dans les
définitions et les appels de méthodes ?.


Comment vérifier si un objet est une instance d'une classe donnée ou d'une sous-classe de celle-ci ?
----------------------------------------------------------------------------------------------------

Use the built-in function "isinstance(obj, cls)".  You can check if an
object is an instance of any of a number of classes by providing a
tuple instead of a single class, for example, "isinstance(obj,
(class1, class2, ...))", and can also check whether an object is one
of Python's built-in types, for example, "isinstance(obj, str)" or
"isinstance(obj, (int, float, complex))".

Note that "isinstance()" also checks for virtual inheritance from an
*abstract base class*.  So, the test will return "True" for a
registered class even if hasn't directly or indirectly inherited from
it.  To test for "true inheritance", scan the *method resolution
order* (MRO) of the class:

   from collections.abc import Mapping

   class P:
        pass

   class C(P):
       pass

   Mapping.register(P)

   >>> c = C()
   >>> isinstance(c, C)        # direct
   True
   >>> isinstance(c, P)        # indirect
   True
   >>> isinstance(c, Mapping)  # virtual
   True

   # Actual inheritance chain
   >>> type(c).__mro__
   (<class 'C'>, <class 'P'>, <class 'object'>)

   # Test for "true inheritance"
   >>> Mapping in type(c).__mro__
   False

Notez que la plupart des programmes n'utilisent que rarement
"isInstance()" sur les classes définies par l'utilisateur. Si vous
développez vous-même des classes, une approche plus orientée-objet
consiste définir des méthodes sur les classes qui sont porteuses d'un
comportement particulier, plutôt que de vérifier la classe de l'objet
et de faire un traitement ad-hoc. Par exemple, si vous avez une
fonction qui fait quelque chose :

   def search(obj):
       if isinstance(obj, Mailbox):
           ...  # code to search a mailbox
       elif isinstance(obj, Document):
           ...  # code to search a document
       elif ...

Une meilleure approche est de définir une méthode "search()" dans
toutes les classes et qu'il suffit d'appeler de la manière suivante :

   class Mailbox:
       def search(self):
           ...  # code to search a mailbox

   class Document:
       def search(self):
           ...  # code to search a document

   obj.search()


Qu'est-ce que la délégation ?
-----------------------------

Delegation is an object-oriented technique (also called a design
pattern). Let's say you have an object "x" and want to change the
behaviour of just one of its methods.  You can create a new class that
provides a new implementation of the method you're interested in
changing and delegates all other methods to the corresponding method
of "x".

Les programmeurs Python peuvent facilement mettre en œuvre la
délégation. Par exemple, la classe suivante implémente une classe qui
se comporte comme un fichier, mais convertit toutes les données
écrites en majuscules :

   class UpperOut:

       def __init__(self, outfile):
           self._outfile = outfile

       def write(self, s):
           self._outfile.write(s.upper())

       def __getattr__(self, name):
           return getattr(self._outfile, name)

Here the "UpperOut" class redefines the "write()" method to convert
the argument string to uppercase before calling the underlying
"self._outfile.write()" method.  All other methods are delegated to
the underlying "self._outfile" object.  The delegation is accomplished
via the "__getattr__()" method; consult the language reference for
more information about controlling attribute access.

Note that for more general cases delegation can get trickier. When
attributes must be set as well as retrieved, the class must define a
"__setattr__()" method too, and it must do so carefully.  The basic
implementation of "__setattr__()" is roughly equivalent to the
following:

   class X:
       ...
       def __setattr__(self, name, value):
           self.__dict__[name] = value
       ...

Many "__setattr__()" implementations call "object.__setattr__()" to
set an attribute on self without causing infinite recursion:

   class X:
       def __setattr__(self, name, value):
           # Custom logic here...
           object.__setattr__(self, name, value)

Alternatively, it is possible to set attributes by inserting entries
into "self.__dict__" directly.


Comment appeler une méthode définie dans une classe mère depuis une classe dérivée qui la surcharge ?
-----------------------------------------------------------------------------------------------------

Utilisez la fonction native "super()" :

   class Derived(Base):
       def meth(self):
           super().meth()  # calls Base.meth

Dans cet exemple, "super()" détermine automatiquement l'instance dont
une méthode l'appelle (l'objet "self"), observe l'*ordre de résolution
des méthodes* obtenu avec "type(self).__mro__", et renvoie la classe
qui suit "Derived" dans cet ordre, donc "Base".


Comment organiser un code pour permettre de changer la classe mère plus facilement ?
------------------------------------------------------------------------------------

You could assign the base class to an alias and derive from the alias.
Then all you have to change is the value assigned to the alias.
Incidentally, this trick is also handy if you want to decide
dynamically (such as depending on availability of resources) which
base class to use.  Example:

   class Base:
       ...

   BaseAlias = Base

   class Derived(BaseAlias):
       ...


Comment créer des données statiques de classe et des méthodes statiques de classe ?
-----------------------------------------------------------------------------------

Les données statiques et les méthodes statiques (au sens C++ ou Java)
sont prises en charge en Python.

Pour les données statiques, il suffit de définir un attribut de
classe. Pour attribuer une nouvelle valeur à l'attribut, vous devez
explicitement utiliser le nom de classe dans l'affectation :

   class C:
       count = 0   # number of times C.__init__ called

       def __init__(self):
           C.count = C.count + 1

       def getcount(self):
           return C.count  # or return self.count

"c.count" se réfère également à "C.count" pour tout "c" tel que
"isInstance (c, C)" est vrai, sauf remplacement par "c" lui-même ou
par une classe sur le chemin de recherche de classe mère de
"c.__class__" jusqu'à "C".

Attention : dans une méthode de C, une affectation comme "self.count =
42" crée une nouvelle instance sans rapport avec le nom "count" dans
le dictionnaire de données de "self". La redéfinition d'une donnée
statique de classe doit toujours spécifier la classe, que l'on soit à
l'intérieur d'une méthode ou non :

   C.count = 314

Il est possible d'utiliser des méthodes statiques :

   class C:
       @staticmethod
       def static(arg1, arg2, arg3):
           # No 'self' parameter!
           ...

Cependant, d'une manière beaucoup plus simple pour obtenir l'effet
d'une méthode statique se fait par une simple fonction au niveau du
module :

   def getcount():
       return C.count

Si votre code est structuré de manière à définir une classe (ou bien
la hiérarchie des classes connexes) par module, ceci fournira
l'encapsulation souhaitée.


Comment surcharger les constructeurs (ou méthodes) en Python ?
--------------------------------------------------------------

Cette réponse s'applique en fait à toutes les méthodes, mais la
question se pose généralement dans le contexte des constructeurs.

In C++ you'd write:

   class C {
       C() { cout << "No arguments\n"; }
       C(int i) { cout << "Argument is " << i << "\n"; }
   }

En Python, vous devez écrire un constructeur unique qui considère tous
les cas en utilisant des arguments par défaut. Par exemple :

   class C:
       def __init__(self, i=None):
           if i is None:
               print("No arguments")
           else:
               print("Argument is", i)

Ce n'est pas tout à fait équivalent, mais suffisamment proche dans la
pratique.

You could also try a variable-length argument list, for example:

   def __init__(self, *args):
       ...

La même approche fonctionne pour toutes les définitions de méthode.


J'essaie d'utiliser "__spam" et j'obtiens une erreur à propos de "_SomeClassName__spam".
----------------------------------------------------------------------------------------

Les noms de variables commençant avec deux tirets bas sont « déformés
», c'est un moyen simple mais efficace de définir des variables
privées à une classe. Tout identifiant de la forme "__spam"
(commençant par au moins deux tirets bas et se terminant par au plus
un tiret bas) est textuellement remplacé par "_classname__spam", où
"classname" est le nom de la classe en cours sans les éventuels tirets
bas du début.

The identifier can be used unchanged within the class, but to access
it outside the class, the mangled name must be used:

   class A:
       def __one(self):
           return 1
       def two(self):
           return 2 * self.__one()

   class B(A):
       def three(self):
           return 3 * self._A__one()

   four = 4 * A()._A__one()

In particular, this does not guarantee privacy since an outside user
can still deliberately access the private attribute; many Python
programmers never bother to use private variable names at all.

Voir aussi:

  La spécification des transformations de noms pour  les détails et
  cas particuliers.


Ma classe définit "__del__" mais elle n'est pas appelée lorsque je supprime l'objet.
------------------------------------------------------------------------------------

Il y a plusieurs explications possibles.

The "del" statement does not necessarily call "__del__()" -- it simply
decrements the object's reference count, and if this reaches zero
"__del__()" is called.

If your data structures contain circular links (for example, a tree
where each child has a parent reference and each parent has a list of
children) the reference counts will never go back to zero.  Once in a
while Python runs an algorithm to detect such cycles, but the garbage
collector might run some time after the last reference to your data
structure vanishes, so your "__del__()" method may be called at an
inconvenient and random time. This is inconvenient if you're trying to
reproduce a problem. Worse, the order in which object's "__del__()"
methods are executed is arbitrary.  You can run "gc.collect()" to
force a collection, but there *are* pathological cases where objects
will never be collected.

Despite the cycle collector, it's still a good idea to define an
explicit "close()" method on objects to be called whenever you're done
with them.  The "close()" method can then remove attributes that refer
to subobjects.  Don't call "__del__()" directly -- "__del__()" should
call "close()" and "close()" should make sure that it can be called
more than once for the same object.

Une alternative pour éviter les références cycliques consiste à
utiliser le module "weakref", qui permet de faire référence à des
objets sans incrémenter leur compteur de références. Par exemple, les
structures d'arbres devraient utiliser des références faibles entre
pères et fils (si nécessaire !).

Finally, if your "__del__()" method raises an exception, a warning
message is printed to "sys.stderr".


Comment obtenir toutes les instances d'une classe ?
---------------------------------------------------

Python ne tient pas de registre de toutes les instances d'une classe
(ni de n'importe quel type natif). Il est cependant possible de
programmer le constructeur de la classe de façon à tenir un tel
registre, en maintenant une liste de références faibles vers chaque
instance.


Pourquoi le résultat de "id()" peut-il être le même pour deux objets différents ?
---------------------------------------------------------------------------------

La fonction native "id()" renvoie un entier dont l'unicité est
garantie durant toute la vie de l'objet. Vu qu'en CPython cet entier
est en réalité l'adresse mémoire de l'objet, il est fréquent qu'un
nouvel objet soit alloué à une adresse mémoire identique à celle d'un
objet venant d'être supprimé. Comme l'illustre le code suivant :

>>> id(1000)
13901272
>>> id(2000)
13901272

Les deux identifiants appartiennent à des objets entiers créés juste
avant l'appel à "id()" et détruits immédiatement après. Pour s'assurer
que les objets dont on veut examiner les identifiants sont toujours en
vie, créons une nouvelle référence à l'objet :

>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296


Quand puis-je raisonnablement utiliser le test d'identité *is* ?
----------------------------------------------------------------

L'opérateur "is" détermine si deux objets sont identiques,
c'est-à-dire le même objet. Le test "a is b" est équivalent à "id(a)
== id(b)".

La propriété la plus importante du test d'identité est qu'un objet est
toujours identique à lui-même. Quelle que soit la valeur de *a*, "a is
a" vaut toujours "True". Un test d'identité est généralement plus
rapide qu'un test d'égalité. De plus, contrairement à l'opérateur
"==", l'opérateur "is" renvoie toujours un booléen, "True" ou "False".

Cependant, les tests d'identité ne peuvent remplacer les tests
d'égalité que si l'identité est garantie. C'est le cas dans les trois
situations suivantes :

1. Assignments create new names but do not change object identity.
   After the assignment "new = old", it is guaranteed that "new is
   old".

2. Putting an object in a container that stores object references does
   not change object identity.  After the list assignment "s[0] = x",
   it is guaranteed that "s[0] is x".

3. If an object is a singleton, it means that only one instance of
   that object can exist.  After the assignments "a = None" and "b =
   None", it is guaranteed that "a is b" because "None" is a
   singleton.

Dans la plupart des autres cas, un test d'identité n'est pas approprié
par rapport à un test d'égalité. En particulier, il ne faut pas
utiliser "is" pour comparer à des constantes comme les entiers (type
"int") ou des chaînes de caractères (type "str") car ces valeurs ne
sont pas nécessairement des singletons :

   >>> a = 10_000_000
   >>> b = 5_000_000
   >>> c = b + 5_000_000
   >>> a is c
   False

   >>> a = 'Python'
   >>> b = 'Py'
   >>> c = b + 'thon'
   >>> a is c
   False

De même, deux instances fraîchement créées d'un type de conteneurs
mutables ne sont jamais identiques :

   >>> a = []
   >>> b = []
   >>> a is b
   False

Voici quelques exemples de la bibliothèque standard qui illustrent
comment utiliser correctement les tests d'identité dans certaines
situations particulières :

1. As recommended by **PEP 8**, an identity test is the preferred way
   to check for "None".  This reads like plain English in code and
   avoids confusion with other objects that may have boolean values
   that evaluate to false.

2. Detecting optional arguments can be tricky when "None" is a valid
   input value.  In those situations, you can create a singleton
   sentinel object guaranteed to be distinct from other objects.  For
   example, here is how to implement a method that behaves like
   "dict.pop()":

      _sentinel = sentinel('_sentinel')

      def pop(self, key, default=_sentinel):
          if key in self:
              value = self[key]
              del self[key]
              return value
          if default is _sentinel:
              raise KeyError(key)
          return default

3. Container implementations sometimes need to augment equality tests
   with identity tests.  This prevents the code from being confused by
   objects such as "float('NaN')" that are not equal to themselves.

For example, here is the implementation of
"collections.abc.Sequence.__contains__()":

   def __contains__(self, value):
       for v in self:
           if v is value or v == value:
               return True
       return False


Comment définir dans une classe fille les attributs d'une instance immuable ?
-----------------------------------------------------------------------------

When subclassing an immutable type, override the "__new__()" method
instead of the "__init__()" method.  The latter only runs *after* an
instance is created, which is too late to alter data in an immutable
instance.

Toutes les classes d'objets immuables suivantes ont des signatures de
constructeur différentes de leur classe mère :

   import datetime as dt

   class FirstOfMonthDate(dt.date):
       "Always choose the first day of the month"
       def __new__(cls, year, month, day):
           return super().__new__(cls, year, month, 1)

   class NamedInt(int):
       "Allow text names for some numbers"
       xlat = {'zero': 0, 'one': 1, 'ten': 10}
       def __new__(cls, value):
           value = cls.xlat.get(value, value)
           return super().__new__(cls, value)

   class TitleStr(str):
       "Convert str to name suitable for a URL path"
       def __new__(cls, s):
           s = s.lower().replace(' ', '-')
           s = ''.join([c for c in s if c.isalnum() or c == '-'])
           return super().__new__(cls, s)

Ces classes s'utilisent comme ceci :

   >>> FirstOfMonthDate(2012, 2, 14)
   FirstOfMonthDate(2012, 2, 1)
   >>> NamedInt('ten')
   10
   >>> NamedInt(20)
   20
   >>> TitleStr('Blog: Why Python Rocks')
   'blog-why-python-rocks'


Comment mettre en cache le résultat d'une méthode ?
---------------------------------------------------

Il existe deux outils principaux pour mettre en cache la valeur de
retour d'une méthode, à savoir "functools.cached_property()", qui
stocke les valeurs au niveau de l'instance, et
"functools.lru_cache()", qui le fait au niveau de la classe.

The "cached_property" approach only works with methods that do not
take any arguments.  It does not create a reference to the instance.
The cached method result will be kept only as long as the instance is
alive.

L'avantage est que le cache est supprimé aussitôt que l'instance est
détruite. L'inconvénient est que les caches peuvent s'accumuler avec
les instances, sans limite de nombre.

The "lru_cache" approach works with methods that have *hashable*
arguments.  It creates a reference to the instance unless special
efforts are made to pass in weak references.

L'avantage de l'algorithme LRU est d'offrir une limitation sur la
taille du cache, que l'on peut régler avec le paramètre *maxsize* (NdT
: LRU signifie *Least Recently Used* en anglais, les entrées du cache
les plus anciennes sont évincées pour conserver la taille).
L'inconvénient est qu'une référence forte vers l'instance est
conservée dans le cache, ce qui maintient l'instance hors de portée du
ramasse-miettes jusqu'à ce que l'entrée soit effacée du cache ou que
le cache soit remis à zéro.

Voici une démonstration des différentes techniques :

   class Weather:
       "Lookup weather information on a government website"

       def __init__(self, station_id):
           self._station_id = station_id
           # The _station_id is private and immutable

       def current_temperature(self):
           "Latest hourly observation"
           # Do not cache this because old results
           # can be out of date.

       @cached_property
       def location(self):
           "Return the longitude/latitude coordinates of the station"
           # Result only depends on the station_id

       @lru_cache(maxsize=20)
       def historic_rainfall(self, date, units='mm'):
           "Rainfall on a given date"
           # Depends on the station_id, date, and units.

The above example assumes that the *station_id* never changes.  If the
relevant instance attributes are mutable, the "cached_property"
approach can't be made to work because it cannot detect changes to the
attributes.

To make the "lru_cache" approach work when the *station_id* is
mutable, the class needs to define the "__eq__()" and "__hash__()"
methods so that the cache can detect relevant attribute updates:

   class Weather:
       "Example with a mutable station identifier"

       def __init__(self, station_id):
           self.station_id = station_id

       def change_station(self, station_id):
           self.station_id = station_id

       def __eq__(self, other):
           return self.station_id == other.station_id

       def __hash__(self):
           return hash(self.station_id)

       @lru_cache(maxsize=20)
       def historic_rainfall(self, date, units='cm'):
           'Rainfall on a given date'
           # Depends on the station_id, date, and units.


Modules
=======


Comment créer des fichiers ".pyc" ?
-----------------------------------

Quand un module est importé pour la première fois (ou si le fichier
source a été modifié depuis la création du fichier compilé), un
fichier ".pyc" contenant le code précompilé est créé dans un sous-
dossier "__pycache__" du dossier contentant le fichier ".py". Le nom
du fichier ".pyc" est identique au fichier ".py" et se termine par
".pyc", avec une partie centrale qui dépend du binaire "python" qui
l'a créé (voir la **PEP 3147** pour de plus amples précisions).

Une des raisons pour lesquelles un fichier ".pyc" peut ne pas être
créé est un problème de droits sur le dossier qui contient le fichier
source, ce qui veut dire qu'il est impossible de créer le sous-dossier
"__pycache__". Ceci peut arriver, par exemple, si vous développez en
tant qu'un certain utilisateur, mais que le code est exécuté en tant
qu'un autre utilisateur, par exemple pour tester un serveur Web.

Unless the "PYTHONDONTWRITEBYTECODE" environment variable is set,
creation of a .pyc file is automatic if you're importing a module and
Python has the ability (permissions, free space, and so on) to create
a "__pycache__" subdirectory and write the compiled module to that
subdirectory.

Running Python on a top-level script is not considered an import and
no ".pyc" will be created.  For example, if you have a top-level
module "foo.py" that imports another module "xyz.py", when you run
"foo" (by typing "python foo.py" as a shell command), a ".pyc" will be
created for "xyz" because "xyz" is imported, but no ".pyc" file will
be created for "foo" since "foo.py" isn't being imported.

Pour créer un fichier ".pyc" pour "foo" — c'est-à-dire créer un
fichier ".pyc" pour un module qui n'est pas importé — il existe les
modules "py_compile" et "compileall".

Le module "py_compile" peut compiler n'importe quel module
manuellement. Il est ainsi possible d'appeler la fonction "compile()"
de manière interactive :

   >>> import py_compile
   >>> py_compile.compile('foo.py')

This will write the ".pyc" to a "__pycache__" subdirectory in the same
location as "foo.py" (or you can override that with the optional
parameter *cfile*).

Tous les fichiers d'un ou plusieurs dossiers peuvent aussi être
compilés avec le module "compileall". C'est possible depuis l'invite
de commande en exécutant "compileall.py" avec le chemin du dossier
contenant les fichiers Python à compiler :

   python -m compileall .


Comment obtenir le nom du module actuel ?
-----------------------------------------

Un module peut déterminer son propre nom en examinant la variable
globale prédéfinie "__name__". Si celle-ci vaut "'__main__'", c'est
que le programme est exécuté comme un script. Beaucoup de modules qui
doivent normalement être importés pour pouvoir être utilisés
fournissent aussi une interface en ligne de commande ou un test
automatique. Ils n'exécutent cette portion du code qu'après avoir
vérifié la valeur de "__name__" :

   def main():
       print('Running test...')
       ...

   if __name__ == '__main__':
       main()


Comment avoir des modules qui s'importent mutuellement ?
--------------------------------------------------------

Considérons les modules suivants :

"foo.py" :

   from bar import bar_var
   foo_var = 1

"bar.py" :

   from foo import foo_var
   bar_var = 2

Le problème réside dans les étapes que l'interpréteur va réaliser :

* *main* importe *foo* ;

* Les variables globales (vides) de *foo* sont créées ;

* *foo* est compilé et commence à s'exécuter ;

* *foo* importe *bar* ;

* Les variables globales (vides) de *bar* sont créées ;

* *bar* est compilé et commence à s'exécuter ;

* *bar* importe *foo* (en réalité, rien ne passe car il y a déjà un
  module appelé *foo*) ;

* La mécanique des importations tente de lire *foo_var* dans les
  variables globales de *foo* pour procéder à l'affectation
  "bar.foo_var = foo.foo_var".

La dernière étape échoue car Python n'a pas fini d'interpréter "foo"
et le dictionnaire global des symboles de "foo" est encore vide.

Le même phénomène arrive quand on utilise "import foo", et qu'on
essaye ensuite d'accéder à "foo.foo_var" dans le code global.

Il y a (au moins) trois façons de contourner ce problème.

Guido van Rossum déconseille d'utiliser "from <module> import ..." et
de mettre tout le code dans des fonctions.  L'initialisation des
variables globales et des variables de classe ne doit utiliser que des
constantes ou des fonctions natives.  Ceci implique que tout ce qui
est fourni par un module soit référencé par "<module>.<nom>".

Jim Roskind recommande d'effectuer les étapes suivantes dans cet ordre
dans chaque module :

* les exportations (variables globales, fonctions et les classes qui
  ne nécessitent d'importer des classes mères)

* les instructions "import"

* le code (avec les variables globales qui sont initialisées à partir
  de valeurs importées).

van Rossum désapprouve cette approche car les importations se trouvent
à un endroit bizarre, mais cela fonctionne.

Matthias Urlichs conseille de restructurer le code pour éviter les
importations récursives.

Ces solutions peuvent être combinées.


"__import__('x.y.z')" renvoie "<module 'x'>" ; comment accéder à "z" ?
----------------------------------------------------------------------

Utilisez plutôt la fonction "import_module()" de "importlib" :

   z = importlib.import_module('x.y.z')


Quand j'édite un module et que je le réimporte, je ne vois pas les changements. Pourquoi ?
------------------------------------------------------------------------------------------

Pour des raisons de performance et de cohérence, Python ne lit le
fichier d'un module que la première fois où celui-ci est importé. Si
ce n'était pas le cas, dans un programme composé d'un très grand
nombre de modules qui importent tous le même module de base, ce module
de base serait analysé et ré-analysé un très grand nombre de fois.
Pour forcer la relecture d'un module, il faut faire :

   import importlib
   import modname
   importlib.reload(modname)

Warning: this technique is not 100% fool-proof.  In particular,
modules containing statements like:

   from modname import some_objects

continuent de fonctionner avec l'ancienne version des objets importés.
Si le module contient une définition de classe, les instances déjà
existantes de celle-ci ne sont *pas* mises à jour avec la nouvelle
définition de la classe. Ceci peut conduire au comportement paradoxal
suivant :

   >>> import importlib
   >>> import cls
   >>> c = cls.C()                # Create an instance of C
   >>> importlib.reload(cls)
   <module 'cls' from 'cls.py'>
   >>> isinstance(c, cls.C)       # isinstance is false?!?
   False

La nature du problème apparaît clairement en affichant « l'identité »
des objets de la classe :

   >>> hex(id(c.__class__))
   '0x7352a0'
   >>> hex(id(cls.C))
   '0x4198d0'
