FAQ histoire et design
**********************


Pourquoi Python utilise-t-il l'indentation pour grouper les instructions ?
==========================================================================

Guido van Rossum considère que l'usage de l'indentation pour regrouper
les blocs d'instruction est élégant et contribue énormément à la
clarté globale du programme Python. La plupart des gens finissent par
aimer cette particularité au bout d'un moment.

Comme il n'y a pas d'accolades de début/fin, il ne peut y avoir de
différence entre le bloc perçu par l'analyseur syntaxique et le
lecteur humain. Parfois les programmeurs C pourront trouver un morceau
de code comme celui-ci :

   if (x <= y)
           x++;
           y--;
   z++;

Seule l'instruction "x++" sera exécutée si la condition est vraie,
mais l'indentation pourrait vous faire penser le contraire. Mêmes des
développeurs C expérimentés resteront pendant un moment à se demander
pourquoi "y" est décrémenté même si "x > y".

Comme il n'y a pas d'accolades de début/fin, Python est moins sujet
aux conflits de style de code. En C, on peut placer les accolades de
nombreuses façons. Si vous êtes habitués à lire et écrire selon un
style particulier, vous pourriez vous sentir perturbé en lisant (ou en
devant écrire) avec un autre style.

Nombre de styles de programmation utilisent des accolades de début/fin
sur une ligne à part. Cela rend les programmes beaucoup plus longs et
fait perdre une bonne partie de l'espace visible sur l'écran,
empêchant un peu d'avoir une vue globale du programme. Idéalement, une
fonction doit être visible sur un écran (environ 20 ou 30 lignes). 20
lignes de Python peuvent faire beaucoup plus que 20 lignes de C. Ce
n'est pas seulement dû à l'absence d'accolades de début/fin --
l'absence de déclarations et la présence de types de haut-niveau en
sont également responsables -- mais la syntaxe basée sur l'indentation
aide sûrement.


Pourquoi ai-je d'étranges résultats suite à de simples opérations arithmétiques ?
=================================================================================

Voir la question suivante.


Pourquoi les calculs à virgules flottantes sont si imprécis ?
=============================================================

Les gens sont très souvent surpris par des résultats comme celui-ci :

   >>> 1.2 - 1.0
   0.19999999999999996

et pensent que c'est un bogue dans Python. Ça ne l'est pas. Ceci n'a
d'ailleurs que peu à voir avec Python, mais avec la manière dont la
plateforme sous-jacente gère les nombres à virgule flottante.

La classe "float" dans CPython utilise le type double du langage C
comme stockage. La valeur d'un objet "float" est stockée dans un
format binaire à virgule flottante avec une précision fixe
(généralement 53 bits). Python utilise des opérations qui proviennent
du langage C qui à leur tour reposent sur l'implémentation au niveau
du processeur afin d'effectuer des opérations en virgule flottante.
Cela signifie que dans le cadre des opérations sur les nombres à
virgule flottante, Python se comporte comme beaucoup de langages
populaires dont C et Java.

Beaucoup de nombres pouvant être écrits facilement en notation
décimale ne peuvent pas s'exprimer de manière exacte en binaire à
virgule flottante. Par exemple, après :

   >>> x = 1.2

la valeur stockée pour "x" est une (très bonne) approximation de la
valeur décimale "1.2", mais cette valeur n'est pas exacte. Sur une
machine typique, la valeur stockée est en fait :

   1.0011001100110011001100110011001100110011001100110011 (binary)

qui est, exactement :

   1.1999999999999999555910790149937383830547332763671875 (decimal)

La précision typique de 53 bits des *floats* Python permet une
précision de 15--16 décimales.

Veuillez vous référer au chapitre sur floating point arithmetic du
tutoriel python pour de plus amples informations.


Pourquoi les chaînes de caractères Python sont-elles immuables ?
================================================================

Il y a plusieurs avantages.

La première concerne la performance : savoir qu'une chaîne de
caractères est immuable signifie que l'allocation mémoire allouée lors
de la création de cette chaîne est fixe et figée. C'est aussi l'une
des raisons pour lesquelles on fait la distinction entre les *tuples*
et les listes.

Un autre avantage est que les chaînes en Python sont considérées aussi
"élémentaires" que les nombres. Aucun processus ne changera la valeur
du nombre 8 en autre chose, et en Python, aucun processus changera la
chaîne de caractère "huit" en autre chose.


Pourquoi "self" doit-il être explicitement utilisé dans les définitions et les appels de méthode ?
==================================================================================================

L'idée a été empruntée à Modula-3. Il s'avère être très utile, pour
diverses raisons.

Tout d'abord, il est plus évident d'utiliser une méthode ou un
attribut d'instance par exemple au lieu d'une variable locale. Lire
"self.x"  ou "self.meth()" est sans ambiguïté sur le fait que c'est
une variable d'instance ou une méthode qui est utilisée, même si vous
ne connaissez pas la définition de classe par cœur. En C++, vous
pouvez les reconnaitre par l'absence d'une déclaration de variable
locale (en supposant que les variables globales sont rares ou
facilement reconnaissables) -- mais en Python, il n'y a pas de
déclarations de variables locales, de sorte que vous devez chercher la
définition de classe pour être sûr. Certaines normes de programmation
C++ et Java préfixent les attributs d'instance par "m_". Cette syntaxe
explicite est ainsi utile également pour ces langages.

Ensuite, ça veut dire qu'aucune syntaxe spéciale n'est nécessaire si
vous souhaitez explicitement référencer ou appeler la méthode depuis
une classe en particulier. En C++, si vous utilisez la méthode d'une
classe de base elle-même surchargée par une classe dérivée, vous devez
utiliser l'opérateur "::" -- en Python vous pouvez écrire
"baseclass.methodname(self, <argument list>)". C'est particulièrement
utile pour les méthodes "__init__()", et de manière générale dans les
cas où une classe dérivée veut étendre la méthode du même nom de la
classe de base, devant ainsi appeler la méthode de la classe de base
d'une certaine manière.

Enfin, pour des variables d'instance, ça résout un problème
syntactique pour l'assignation : puisque les variables locales en
Python sont (par définition !) ces variables auxquelles les valeurs
sont assignées dans le corps d'une fonction (et n'étant pas déclarées
explicitement globales), il doit y avoir un moyen de dire à
l'interpréteur qu'une assignation est censée assigner une variable
d'instance plutôt qu'une variable locale, et doit de préférence être
syntactique (pour des raisons d'efficacité). C++ fait ça au travers de
déclarations, mais Python n'a pas de déclarations et ça serait dommage
d'avoir à les introduire juste pour cette raison. Utiliser
explicitement "self.var" résout ça avec élégance. Pareillement, pour
utiliser des variables d'instance, avoir à écrire "self.var" signifie
que les références vers des noms non-qualifiés au sein d'une méthode
n'ont pas à être cherchés dans l'annuaire d'instances. En d'autres
termes, les variables locales et les variables d'instance vivent dans
deux différents espaces de nommage, et vous devez dire à Python quel
espace de nommage utiliser.


Pourquoi ne puis-je pas utiliser d'assignation dans une expression ?
====================================================================

Depuis Python 3.8, c'est possible !

Les expressions d'affectation qui utilisent l'opérateur morse *:=*
affectent une variable dans une expression :

   while chunk := fp.read(200):
      print(chunk)

Voir la **PEP 572** pour plus d'informations.


Pourquoi Python utilise des méthodes pour certaines fonctionnalités (ex : "list.index()") mais des fonctions pour d'autres (ex : "len(list)") ?
===============================================================================================================================================

Comme l'a dit Guido :

   (a) Pour certaines opérations, la notation préfixe se lit mieux que
   celle suffixe -- les opérations préfixe (et infixe !) sont une
   longue tradition en mathématique, où on apprécie les notations qui
   aident visuellement le mathématicien à réfléchir sur un problème.
   Comparez la facilité avec laquelle nous réécrivons une formule
   comme x*(a+b) en x*a + x*b à la lourdeur de faire la même chose
   avec une notation orientée objet brute.

   (b) Quand je lis du code qui dit "len(x)" *je sais* qu'il demande
   la longueur de quelque chose. Cela me dit deux choses : le résultat
   est un entier, et l'argument est une sorte de conteneur. Au
   contraire, quand je lis "x.len()", je dois déjà savoir que x est
   une sorte de conteneur implémentant une interface ou héritant d'une
   classe qui a un "len()" standard. Voyez la confusion qui arrive
   parfois quand une classe qui n'implémente pas une interface de
   dictionnaire a une méthode "get()" ou "key()", ou quand un objet
   qui n'est pas un fichier implémente une méthode "write()".

   -- https://mail.python.org/pipermail/python-3000/2006-November/004
   643.html


Pourquoi "join()" est une méthode de chaîne plutôt qu'une de liste ou de tuple ?
================================================================================

Les chaînes sont devenues bien plus comme d'autres types standards à
partir de Python 1.6, lorsque les méthodes ont été ajoutées
fournissant ainsi les mêmes fonctionnalités que celles qui étaient
déjà disponibles en utilisant les fonctions du module string. La
plupart de ces nouvelles méthodes ont été largement acceptées, mais
celle qui semble rendre certains programmeurs inconfortables est :

   ", ".join(['1', '2', '4', '8', '16'])

qui donne le résultat :

   "1, 2, 4, 8, 16"

Il y a deux arguments fréquents contre cet usage.

Le premier se caractérise par les lignes suivantes : "C'est vraiment
moche d'utiliser une méthode de chaîne littérale (chaîne constante)",
à laquelle la réponse est qu'il se peut, mais une chaîne littérale est
juste une valeur fixe. Si la méthode est autorisée sur des noms liés à
des chaînes, il n'y a pas de raison logique à les rendre indisponibles
sur des chaînes littérales.

La deuxième objection se réfère typiquement à : "Je suis réellement en
train de dire à une séquence de joindre ses membres avec une constante
de chaîne". Malheureusement, vous ne l'êtes pas. Pour quelque raison,
il semble être bien moins difficile d'avoir "split()" en tant que
méthode de chaîne, puisque dans ce cas il est facile de voir que

   "1, 2, 4, 8, 16".split(", ")

est une instruction à une chaîne littérale de renvoyer les sous-
chaînes délimitées par le séparateur fournit (ou, par défaut, les
espaces, ou groupes d'espaces).

"join()" est une méthode de chaîne parce qu'en l'utilisant vous dites
au séparateur de chaîne d'itérer une séquence de chaînes et de
s'insérer entre les éléments adjacents. Cette méthode peut être
utilisée avec n'importe quel argument qui obéit aux règles d'objets
séquence, incluant n'importe quelles nouvelles classes que vous
pourriez définir vous-même. Des méthodes similaires existent pour des
objets "bytes" et "bytearray".


À quel point les exceptions sont-elles rapides ?
================================================

Un bloc "try" / "except" est extrêmement efficient tant qu'aucune
exception ne sont levée. En effet, intercepter une exception s'avère
coûteux. Dans les versions de précédant Python 2.0, il était courant
d'utiliser cette pratique :

   try:
       value = mydict[key]
   except KeyError:
       mydict[key] = getvalue(key)
       value = mydict[key]

Cela n'a de sens que si vous vous attendez à ce que le dictionnaire
ait la clé presque tout le temps. Si ce n'était pas le cas, vous
l'auriez codé comme suit :

   if key in mydict:
       value = mydict[key]
   else:
       value = mydict[key] = getvalue(key)

Pour ce cas, vous pouvez également utiliser "value =
dict.setdefault(key, getvalue(key))", mais seulement si l'appel à
"getvalue()" est suffisamment peu coûteux car il est évalué dans tous
les cas.


Pourquoi n'y a-t-il pas une instruction *switch* ou une structure similaire à *switch / case* en Python ?
=========================================================================================================

Vous pouvez le faire assez facilement avec une séquence de "if...
elif... elif... else". Il y a eu quelques propositions pour la syntaxe
de l'instruction "switch", mais il n'y a pas (encore) de consensus sur
le cas des intervalles. Voir la **PEP 275** pour tous les détails et
l'état actuel.

Dans les cas où vous devez choisir parmi un très grand nombre de
possibilités, vous pouvez créer un dictionnaire faisant correspondre
des valeurs à des fonctions à appeler. Par exemple :

   def function_1(...):
       ...

   functions = {'a': function_1,
                'b': function_2,
                'c': self.method_1, ...}

   func = functions[value]
   func()

Pour appeler les méthodes sur des objets, vous pouvez simplifier
davantage en utilisant la fonction native "getattr()" pour récupérer
les méthodes avec un nom donné :

   def visit_a(self, ...):
       ...
   ...

   def dispatch(self, value):
       method_name = 'visit_' + str(value)
       method = getattr(self, method_name)
       method()

Il est suggéré que vous utilisiez un préfixe pour les noms de
méthodes, telles que "visit_" dans cet exemple. Sans ce préfixe, si
les valeurs proviennent d'une source non fiable, un attaquant serait
en mesure d'appeler n'importe quelle méthode sur votre objet.


Est-il possible d'émuler des fils d'exécution dans l'interpréteur plutôt que se baser sur les implémentations spécifique aux OS ?
=================================================================================================================================

Réponse 1 : Malheureusement, l'interpréteur pousse au moins un bloc de
pile C (*stack frame*) pour chaque bloc de pile de Python. Aussi, les
extensions peuvent rappeler dans Python à presque n'importe quel
moment. Par conséquent, une implémentation complète des fils
d'exécution nécessiterai un support complet en C.

Réponse 2 : heureusement, il existe Stackless Python, qui a
complètement ré-architecturé la boucle principale de l'interpréteur
afin de ne pas utiliser la pile C.


Pourquoi les expressions lambda ne peuvent pas contenir d'instructions ?
========================================================================

Les expressions lambda de Python ne peuvent pas contenir
d'instructions parce que le cadre syntaxique de Python ne peut pas
gérer les instructions imbriquées à l'intérieur d'expressions.
Cependant, en Python, ce n'est pas vraiment un problème. Contrairement
aux formes lambda dans d'autres langages, où elles ajoutent des
fonctionnalités,  les expressions lambda de Python sont seulement une
notation concise si vous êtes trop paresseux pour définir une
fonction.

Les fonctions sont déjà des objets de première classe en Python et
peuvent être déclarées dans une portée locale. L'unique avantage
d'utiliser une fonction lambda au lieu d'une fonction définie
localement est que vous n'avez nullement besoin d'un nom pour la
fonction -- Mais c'est juste une variable locale à laquelle est
affecté l'objet fonction (qui est exactement le même type d'objet qui
donne une expression lambda) !


Python peut-il être compilé en code machine, en C ou dans un autre langage ?
============================================================================

Cython compile une version modifiée de Python avec des annotations
optionnelles en extensions C. Nuitka est un nouveau compilateur de
Python vers C++, visant à supporter le langage Python entièrement.
Pour compiler en Java, vous pouvez regarder VOC.


Comment Python gère la mémoire ?
================================

Les détails de la gestion de la mémoire en Python dépendent de
l'implémentation. En effet, l'implémentation standard de Python,
*CPython*, utilise des compteurs de références afin de détecter des
objets inaccessibles et un autre mécanisme pour collecter les
références circulaires, exécutant périodiquement un algorithme de
détection de cycles qui recherche les cycles inaccessibles et supprime
les objets impliqués. Le module "gc" fournit des fonctions pour lancer
le ramasse-miettes, d'obtenir des statistiques de débogage et ajuster
ses paramètres.

Cependant, d'autres implémentations (par exemple Jython ou PyPy)
peuvent compter sur un mécanisme différent comme un véritable ramasse-
miette. Cette différence peut causer de subtils problèmes de
portabilité si votre code Python dépend du comportement de
l'implémentation du compteur de références.

Dans certaines implémentations de Python, le code suivant (qui marche
parfaitement avec *CPython*) aurait probablement manqué de
descripteurs de fichiers :

   for file in very_long_list_of_files:
       f = open(file)
       c = f.read(1)

En effet, à l'aide du comptage de références et du destructeur
d'objets de *CPython*, chaque nouvelle affectation à *f* ferme le
fichier précédent.  Cependant, avec un *GC* classique, ces objets
seront seulement recueillies (et fermés) à intervalles variables et
possiblement avec de longs intervalles.

Si vous souhaitez écrire du code qui fonctionne avec n'importe quelle
implémentation de Python, vous devez explicitement fermer le fichier
ou utiliser l'instruction "with" ; ceci fonctionnera indépendamment du
système de gestion de la mémoire :

   for file in very_long_list_of_files:
       with open(file) as f:
           c = f.read(1)


Pourquoi CPython n'utilise-il pas un ramasse-miette plus traditionnel ?
=======================================================================

D'une part, ce n'est pas une caractéristique normalisé en C et par
conséquent ce n'est pas portable. (Oui, nous connaissons la
bibliothèque *GC Boehm*. Elle contient du code assembleur pour la
plupart des plates-formes classiques, mais pas toutes, et bien qu'elle
soit le plus souvent transparent, c'est loin d'être le cas, des
correctifs sont nécessaires afin que Python fonctionne correctement
avec.)

Le *GC* classique devient également un problème lorsque Python est
incorporé dans d'autres applications. Bien que dans une application
Python, il ne soit pas gênant de remplacer  les fonctions "malloc()"
et "free()` avec les versions fournies par la bibliothèque *GC*, une
application incluant Python peut vouloir avoir ses propres
implémentations de ``malloc()" et "free()" et peut ne pas vouloir
celles de Python. À l'heure actuelle, CPython fonctionne avec
n'importe quelle implémentation correcte de "malloc()" et "free()".


Pourquoi toute la mémoire n'est pas libérée lorsque *CPython* s'arrête ?
========================================================================

Les objets référencés depuis les espaces de nommage globaux des
modules Python ne sont pas toujours désalloués lorsque Python
s'arrête. Cela peut se produire s'il y a des références circulaires.
Il y a aussi certaines parties de mémoire qui sont alloués par la
bibliothèque C qui sont impossibles à libérer (par exemple un outil
comme *Purify* s'en plaindra). Python est, cependant, agressif sur le
nettoyage de la mémoire en quittant et cherche à détruire chaque
objet.

Si vous voulez forcer Python à désallouer certains objets en quittant,
utilisez le module "texit" pour exécuter une fonction qui va forcer
ces destructions.


Pourquoi les *tuples* et les *list* sont deux types de données séparés ?
========================================================================

Les listes et les *tuples*, bien que semblable à bien des égards, sont
généralement utilisés de façons fondamentalement différentes. Les
*tuples* peuvent être considérés comme étant similaires aux dossiers
en Pascal ou aux structures en C; Ce sont de petites collections de
données associées qui peuvent être de différents types qui sont
utilisées ensemble. Par exemple, un repère cartésien est correctement
représenté comme un *tuple* de deux ou trois nombres.

Les listes, ressemblent davantage à des tableaux dans d'autres
langues. Elles ont tendance à contenir un nombre variable d'objets de
même type manipulés individuellement. Par exemple, "os.listdir('.')"
renvoie une liste de chaînes représentant les fichiers dans le dossier
courant. Les fonctions travaillant sur cette sortie accepteraient
généralement sans aucun problème que vous ajoutiez un ou deux fichiers
supplémentaire dans le dossier.

Les *tuples* sont immuables, ce qui signifie que lorsqu'un *tuple* a
été créé, vous ne pouvez remplacer aucun de ses éléments par une
nouvelle valeur. Les listes sont muables, ce qui signifie que vous
pouvez toujours modifier les éléments d'une liste. Seuls des éléments
immuables peuvent être utilisés comme clés de dictionnaires, et donc
de "tuple" et "list" seul des *tuples* peuvent être utilisés comme
clés.


Comment les listes sont-elles implémentées dans CPython ?
=========================================================

Les listes en CPython sont de vrais tableaux de longueur variable
contrairement à des listes orientées *Lisp* (c.-à-d. des listes
chaînées). L'implémentation utilise un tableau contigu de références à
d'autres objets. Elle conserve également un pointeur vers ce tableau
et la longueur du tableau dans une structure de tête de liste.

Cela rend l'indexation d'une liste "a[i]" une opération dont le coût
est indépendant de la taille de la liste ou de la valeur de l'indice.

Lorsque des éléments sont ajoutés ou insérés, le tableau de références
est redimensionné. Un savoir-faire ingénieux permet l'amélioration des
performances lors de l'ajout fréquent d'éléments ; Lorsque le tableau
doit être étendu, un certain espace supplémentaire est alloué de sorte
que pour la prochaine fois, ceci ne nécessite plus un
redimensionnement effectif.


Comment les dictionnaires sont-ils implémentés dans CPython ?
=============================================================

Les dictionnaires CPython sont implémentés sous forme de tables de
hachage redimensionnables.  Par rapport aux *B-trees*, cela donne de
meilleures performances pour la recherche (l'opération la plus
courante de loin) dans la plupart des circonstances, et leur
implémentation est plus simple.

Les dictionnaires fonctionnent en calculant un code de hachage pour
chaque clé stockée dans le dictionnaire en utilisant la fonction
"hash()".  Le code de hachage varie grandement selon la clé et du
nombre de processus utilisés ; Par exemple, la chaine de caractère
"Python" pourrait avoir comme code de hachage une valeur allant
jusqu'à-539294296 tandis que la chaine "python",qui se distingue de la
première par un seul bit, pourrait avoir comme code de hachage une
valeur allant jusqu'à 1142331976.  Le code de hachage est ensuite
utilisé pour calculer un emplacement dans un tableau interne où la
valeur est stockée.  Dans l'hypothèse où vous stockez les clés qui ont
toutes des valeurs de hachage différentes, cela signifie que le temps
pour récupérer une clé est constant -- O(1), en notation grand O de
Landau.


Pourquoi les clés du dictionnaire sont immuables ?
==================================================

L'implémentation de la table de hachage des dictionnaires utilise une
valeur de hachage calculée à partir de la valeur de la clé pour
trouver la clé elle-même.  Si la clé était un objet muable, sa valeur
peut changer, et donc son hachage pourrait également changer.  Mais
toute personne modifiant l'objet clé ne peut pas dire qu'elle a été
utilisée comme une clé de dictionnaire. Il ne peut déplacer l'entrée
dans le dictionnaire.  Ainsi, lorsque vous essayez de rechercher le
même objet dans le dictionnaire, il ne sera pas disponible parce que
sa valeur de hachage est différente. Si vous essayez de chercher
l'ancienne valeur, elle serait également introuvable car la valeur de
l'objet trouvé dans cet emplacement de hachage serait différente.

Si vous voulez un dictionnaire indexé avec une liste, il faut
simplement convertir la liste en un  *tuple*  ; la fonction "tuple(L)"
crée un *tuple* avec les mêmes entrées que la liste "L".  Les *tuples*
sont immuables et peuvent donc être utilisés comme clés du
dictionnaire.

Certaines solutions insatisfaisantes qui ont été proposées :

* Les listes de hachage par leur adresse (*ID* de l'objet).  Cela ne
  fonctionne pas parce que si vous créez une nouvelle liste avec la
  même valeur, elle ne sera pas retrouvée; par exemple :

     mydict = {[1, 2]: '12'}
     print(mydict[[1, 2]])

  cela lèverait une exception "KeyError" car l'ID de "[1, 2]" utilisé
  dans la deuxième ligne diffère de celle de la première ligne. En
  d'autres termes, les clés de dictionnaire doivent être comparées à
  l'aide du comparateur "==" et non à l'aide du mot clé "is".

* Faire une copie lors de l'utilisation d'une liste en tant que clé.
  Cela ne fonctionne pas puisque la liste, étant un objet muable,
  pourrait contenir une référence à elle-même ou avoir une boucle
  infinie au niveau du code copié.

* Autoriser les listes en tant que clés, mais indiquer à l'utilisateur
  de ne pas les modifier. Cela permettrait un ensemble de bogues
  difficiles à suivre dans les programmes lorsque vous avez oublié ou
  modifié une liste par accident. Cela casse également un impératif
  important des dictionnaires : chaque valeur de "d.keys()" est
  utilisable comme clé du dictionnaire.

* Marquer les listes comme étant en lecture seule une fois qu'elles
  sont utilisées comme clé de dictionnaire. Le problème est que ce
  n'est pas seulement l'objet de niveau supérieur qui pourrait changer
  sa valeur; vous pourriez utiliser un tuple contenant une liste comme
  clé.  Utiliser n'importe quoi comme une clé dans un dictionnaire
  nécessiterait de marquer tous les objets accessibles à partir de là
  comme en lecture seule -- et encore une fois, les objets se faisant
  référence pourraient provoquer une boucle infinie.

Il y a un truc pour contourner ceci si vous en avez besoin, mais
utilisez-le à vos risques et périls. Vous pouvez encapsuler une
structure mutable à l'intérieur d'une instance de classe qui a à la
fois une méthode "__eq__()" et "__hash__()". Vous devez ensuite vous
assurer que la valeur de hachage pour tous ces objets *wrapper* qui
résident dans un dictionnaire (ou une autre structure basée sur le
hachage), restent fixes pendant que l'objet est dans le dictionnaire
(ou une autre structure).

   class ListWrapper:
       def __init__(self, the_list):
           self.the_list = the_list

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

       def __hash__(self):
           l = self.the_list
           result = 98767 - len(l)*555
           for i, el in enumerate(l):
               try:
                   result = result + (hash(el) % 9999999) * 1001 + i
               except Exception:
                   result = (result % 7777777) + i * 333
           return result

Notez que le calcul de hachage peut être compliqué car il est possible
que certains membres de la liste peuvent être impossible à hacher et
aussi par la possibilité de débordement arithmétique.

De plus, il faut toujours que, si "o1 == o2" (par exemple
"o1.__eq__(o2) vaut True") alors "hash(o1) == hash(o2)" (par exemple,
"o1.__hash__() == o2.__hash__()"), que l’objet se trouve dans un
dictionnaire ou pas. Si vous ne remplissez pas ces conditions, les
dictionnaires et autres structures basées sur le hachage se
comporteront mal.

Dans le cas de *ListWrapper*, chaque fois que l'objet *wrapper* est
dans un dictionnaire, la liste encapsulée ne doit pas changer pour
éviter les anomalies. Ne faites pas cela à moins que vous n’ayez pensé
aux potentielles conséquences de ne pas satisfaire entièrement ces
conditions. Vous avez été prévenus.


Pourquoi "list.sort()" ne renvoie pas la liste triée ?
======================================================

Dans les situations où la performance est importante, faire une copie
de la liste juste pour la trier serait un gaspillage. Par conséquent,
"list.sort()" trie la liste en place. Afin de vous le rappeler, il ne
retourne pas la liste triée.  De cette façon, vous ne serez pas dupés
en écrasant accidentellement une liste lorsque vous avez besoin d’une
copie triée, mais vous devrez également garder sous la main la version
non triée.

Si vous souhaitez retourner une nouvelle liste, utilisez plutôt la
fonction native "sorted()".  Cette fonction crée une nouvelle liste à
partir d’un itérable fourni, la trie et la retourne.  Par exemple,
voici comment itérer sur les clefs d’un dictionnaire dans l’ordre trié
:

   for key in sorted(mydict):
       ...  # do whatever with mydict[key]...


Comment spécifiez-vous et appliquez-vous une spécification d’interface en Python ?
==================================================================================

Une spécification d'interface pour un module fourni par des langages
tels que C++ et Java décrit les prototypes pour les méthodes et les
fonctions du module. Beaucoup estiment que la vérification au moment
de la compilation des spécifications d'interface aide à la
construction de grands programmes.

Python 2.6 ajoute un module "abc" qui vous permet de définir des
classes de base abstraites (ABCs). Vous pouvez ensuite utiliser
"isinstance()" et "issubclass()" pour vérifier si une instance ou une
classe implémente une ABC particulière. Le module "collections.abc"
définit un ensemble d'ABCs utiles telles que "Iterable", "Container"
et "collections.abc.MutableMapping".

Pour Python, la plupart des avantages des spécifications d'interface
peuvent être obtenus par une discipline de test appropriée pour les
composants.

Une bonne suite de tests pour un module peut à la fois fournir un test
de non régression et servir de spécification d'interface de module
ainsi qu'un ensemble d'exemples. De nombreux modules Python peuvent
être exécutés en tant que script pour fournir un simple « auto-test ».
Même les modules qui utilisent des interfaces externes complexes
peuvent souvent être testés isolément à l'aide d'émulations triviales
embryonnaires de l'interface externe. Les modules "doctest" et
"UnitTest" ou des frameworks de test tiers peuvent être utilisés pour
construire des suites de tests exhaustives qui éprouvent chaque ligne
de code dans un module.

Une discipline de test appropriée peut aider à construire des
applications complexes de grande taille en Python aussi bien que le
feraient des spécifications d'interface. En fait, c'est peut être même
mieux parce qu'une spécification d'interface ne peut pas tester
certaines propriétés d'un programme. Par exemple, la méthode
"Append()" est censée ajouter de nouveaux éléments à la fin d'une
liste « sur place » ; une spécification d'interface ne peut pas tester
que votre implémentation de "append()" va réellement le faire
correctement, mais il est trivial de vérifier cette propriété dans une
suite de tests.

L'écriture des suites de tests est très utile, et vous voudrez peut-
être concevoir votre code de manière à le rendre facilement testable.
Une technique de plus en plus populaire, le développement dirigé par
les tests, requiert d'écrire d'abord des éléments de la suite de
tests, avant d'écrire le code réel. Bien sûr, Python vous permet
d'être laxiste et de ne pas écrire de test du tout.


Pourquoi n'y a-t-il pas de "goto" en Python ?
=============================================

Dans les années 1970, les gens se sont aperçus que le foisonnement de
*goto* conduisait à du code « spaghetti » difficile à comprendre et à
modifier. Dans les langages de haut niveau, c'est d'autant moins
nécessaire qu'il existe différentes manières de créer des branches (en
Python, les instructions "if" et les expressions "or", "and" et "if-
else") et de boucler (avec les instructions "while" et "for", qui
peuvent contenir des "continue" et "break").

Vous pouvez utiliser les exceptions afin de mettre en place un «
*goto* structuré » qui fonctionne même à travers les appels de
fonctions. Beaucoup de personnes estiment que les exceptions sont une
façon commode d'émuler l'utilisation raisonnable des constructions
*go* ou *goto* du C, du Fortran ou d'autres langages de programmation.
Par exemple :

   class label(Exception): pass  # declare a label

   try:
       ...
       if condition: raise label()  # goto label
       ...
   except label:  # where to goto
       pass
   ...

Cela ne vous permet pas de sauter au milieu d'une boucle. Néanmoins,
dans tous les cas cela est généralement considéré comme un abus de
"goto".  À Utiliser avec parcimonie.


Pourquoi les chaînes de caractères brutes (r-strings) ne peuvent-elles pas se terminer par un *backslash* ?
===========================================================================================================

Plus précisément, elles ne peuvent pas se terminer par un nombre
impair de *backslashes* : le *backslash* non appairé à la fin échappe
le caractère de guillemet final, laissant une chaîne non terminée.

Les chaînes brutes ont été conçues pour faciliter la création de
données pour les processeurs de texte (principalement les moteurs
d'expressions régulières) qui veulent faire leur propre traitement
d'échappement d'*antislashes*. Ces processeurs considèrent un
*antislash* de fin non-appairé comme une erreur, alors les chaînes
brutes ne le permettent pas. En retour, elles vous permettent de
transmettre le caractère de citation de la chaîne en l'échappant avec
un *antislash*. Ces règles fonctionnent bien lorsque les chaînes
brutes sont utilisées pour leur but premier.

Si vous essayez de construire des chemins d'accès Windows, notez que
tous les appels système Windows acceptent également les *slashes*
"classiques" :

   f = open("/mydir/file.txt")  # works fine!

Si vous essayez de construire un chemin d'accès pour une commande DOS,
essayez par exemple l'un de ceux-là

   dir = r"\this\is\my\dos\dir" "\\"
   dir = r"\this\is\my\dos\dir\ "[:-1]
   dir = "\\this\\is\\my\\dos\\dir\\"


Pourquoi la déclaration "with" pour les assignations d'attributs n'existe pas en Python ?
=========================================================================================

Python a une instruction "with" qui encapsule l'exécution d'un bloc,
en appelant le code sur l'entrée et la sortie du bloc.  Certains
langages possèdent une construction qui ressemble à ceci :

   with obj:
       a = 1               # equivalent to obj.a = 1
       total = total + 1   # obj.total = obj.total + 1

En Python, une telle construction serait ambiguë.

Les autres langages, tels que le Pascal, le Delphi et le C++ utilisent
des types statiques, il est donc possible de savoir d'une manière
claire et directe ce à quoi est attribué un membre. C'est le point
principal du typage statique --le compilateur connaît *toujours* la
portée de toutes les variables au moment de la compilation.

Python utilise le typage dynamique. Il est impossible de savoir à
l'avance quel attribut est utilisé comme référence lors de
l'exécution. Les attributs membres peuvent être ajoutés ou retirés des
objets à la volée. Il est donc impossible de savoir, d'une simple
lecture, quel attribut est référencé : s'il est local, global ou un
attribut membre?

Prenons par exemple l'extrait incomplet suivant :

   def foo(a):
       with a:
           print(x)

L'extrait suppose que "a" doit avoir un attribut membre appelé "x".
Néanmoins, il n'y a rien en Python qui en informe l'interpréteur. Que
se passe-t-il si "a" est, disons, un entier ? Si une variable globale
nommée "x" existe, sera-t-elle utilisée dans le bloc "with" ?  Comme
vous voyez, la nature dynamique du Python rend ces choix beaucoup plus
difficiles.

L'avantage principal de "with" et des fonctionnalités de langage
similaires (réduction du volume de code) peut, cependant, être
facilement réalisé en Python par assignation.  Au lieu de :

   function(args).mydict[index][index].a = 21
   function(args).mydict[index][index].b = 42
   function(args).mydict[index][index].c = 63

écrivez ceci :

   ref = function(args).mydict[index][index]
   ref.a = 21
   ref.b = 42
   ref.c = 63

Cela a également pour effet secondaire d'augmenter la vitesse
d'exécution car les liaisons de noms sont résolues au moment de
l'exécution en Python, et la deuxième version n'a besoin d'exécuter la
résolution qu'une seule fois.


Pourquoi les deux-points sont-ils nécessaires pour les déclarations "if/while/def/class" ?
==========================================================================================

Le deux-points est principalement nécessaires pour améliorer la
lisibilité (l'un des résultats du langage expérimental ABC).
Considérez ceci :

   if a == b
       print(a)

versus

   if a == b:
       print(a)

Remarquez comment le deuxième est un peu plus facile à lire.
Remarquez aussi comment un deux-points introduit l'exemple dans cette
réponse à la FAQ ; c'est un usage standard en anglais.

Une autre raison mineure est que les deux-points facilitent la tâche
des éditeurs avec coloration syntaxique ; ils peuvent rechercher les
deux-points pour décider quand l'indentation doit être augmentée au
lieu d'avoir à faire une analyse plus élaborée du texte du programme.


Pourquoi Python permet-il les virgules à la fin des listes et des tuples ?
==========================================================================

Python vous permet d'ajouter une virgule à la fin des listes, des
tuples et des dictionnaires :

   [1, 2, 3,]
   ('a', 'b', 'c',)
   d = {
       "A": [1, 5],
       "B": [6, 7],  # last trailing comma is optional but good style
   }

Il y a plusieurs raisons d'accepter cela.

Lorsque vous avez une valeur littérale pour une liste, un tuple ou un
dictionnaire réparti sur plusieurs lignes, il est plus facile
d'ajouter plus d'éléments parce que vous n'avez pas besoin de vous
rappeler d'ajouter une virgule à la ligne précédente.  Les lignes
peuvent aussi être réorganisées sans créer une erreur de syntaxe.

L'omission accidentelle de la virgule peut entraîner des erreurs
difficiles à diagnostiquer, par exemple :

   x = [
     "fee",
     "fie"
     "foo",
     "fum"
   ]

Cette liste a l'air d'avoir quatre éléments, mais elle en contient en
fait trois : "*fee*", "*fiefoo*" et "*fum*". Toujours ajouter la
virgule permet d'éviter cette source d'erreur.

Permettre la virgule de fin peut également faciliter la génération de
code.
