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 clareté 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 bug 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 ambiguité 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 noms, et vous devez dire à Python quel espace de noms utiliser.

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

De nombreuses personnes habituées à C ou Perl se plaignent de vouloir utiliser cet idiome C :

while (line = readline(f)) {
    // do something with line
}

où en Python vous êtes forcé à écrire ceci :

while True:
    line = f.readline()
    if not line:
        break
    ...  # do something with line

La raison pour ne pas autoriser l’assignation dans les expressions en Python est un bug fréquent, et difficile à trouver dans ces autres langages, causé par cette construction :

if (x = 0) {
    // error handling
}
else {
    // code that only works for nonzero x
}

Cette erreur est une simple coquille : x = 0, qui assigne 0 à la variable x, a été écrit alors que la comparaison x == 0 est certainement ce qui était souhaité.

De nombreuses alternatives ont été proposées. La plupart des hacks économisaient de la frappe mais utilisaient d’arbitraires ou cryptiques syntaxes ou mot-clés et faillait le simple critère pour proposition de changement du langage : ça doit intuitivement suggérer la bonne signification au lecteur qui n’a pas encore été introduit à la construction.

Un phénomène intéressant est que la plupart des programmeurs Python expérimentés reconnaissent l’idiome while True et ne semblent pas manquer l’assignation dans la construction de l’expression; seuls les nouveaux-venus expriment un fort désir d’ajouter ceci au langage.

Il y a une manière alternative de faire ça qui semble attrayante mais elle est généralement moins robuste que la solution while True

line = f.readline()
while line:
    ...  # do something with line...
    line = f.readline()

Le problème avec ceci est que si vous changez d’avis sur la manière dont vous allez récupérer la prochaine ligne (ex : vous voulez changer en sys.stdin.readline()) vous devez vous souvenir de le changer à deux endroits dans votre programme – la deuxième occurrence est cachée en bas de la boucle.

La meilleur approche est d’utiliser les itérateurs, rendant possible de parcourir des objets en utilisant l’instruction for. Par exemple, les objets fichiers gèrent le protocole d’itération, donc vous pouvez simplement écrire :

for line in f:
    ...  # do something with line...

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

La raison principale est historique. Les fonctions étaient utilisées pour ces opérations qui étaient génériques pour un groupe de types et qui étaient censés fonctionner même pour les objets qui n’avaient pas de méthodes du tout (ex : tuples). C’est aussi pratique d’avoir une fonction qui s’apprête bien à une collection amorphe d’objets lorsque vous utiliser les outils fonctionnels de Python (map(), zip() et autres).

En fait, implémenter len(), max(), min() en tant que fonction intégrée produit moins de code que de les implémenter en tant que méthode pour chaque type. Certains peuvent rouspéter pour des cas individuels mais ça fait partie de Python et il est trop tard pour faire des changements si fondamentaux maintenant. Ces fonctions doivent rester pour éviter la casse massive de code.

Note

Pour les opérations de chaînes, Python a déplacé les fonctions externes (le module string) vers des méthodes. Cependant, len() est toujours une fonction.

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 threads 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 block 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 thread nécessiterai un support complet en C.

Réponse 2: Heureusement, il existe Stackless Python, qui à 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 compiles a modified version of Python with optional annotations into C extensions. Nuitka is an up-and-coming compiler of Python into C++ code, aiming to support the full Python language. For compiling to Java you can consider 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 implqué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 noms 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 sensemble. 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 est-ce que les listes sont implémentées ?

Les listes en Python sont de vrais tableaux de longueur variable contrairement à des listes orientées Lisp (i.e 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 ?

Les dictionnaires Python 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 les dictionnaires parcourt le dictionnaire en un temps constant – O(1), en notation scientifique informatique - - pour récupérer une clé. Cela signifie également qu’aucun ordre de tri des clés n’est maintenu, et lors du parcours du tableau, les .keys() et .items() afficheront le contenu du dictionnaire dans un certain ordre arbitraire qui peut changer à chaque appel d’un programme.

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 soulèverait une exception de type KeyError car l’id de [1, 2] utilisée 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 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é.

  • Allow lists as keys but tell the user not to modify them. This would allow a class of hard-to-track bugs in programs when you forgot or modified a list by accident. It also invalidates an important invariant of dictionaries: every value in d.keys() is usable as a key of the dictionary.

  • Mark lists as read-only once they are used as a dictionary key. The problem is that it’s not just the top-level object that could change its value; you could use a tuple containing a list as a key. Entering anything as a key into a dictionary would require marking all objects reachable from there as read-only – and again, self-referential objects could cause an infinite loop.

There is a trick to get around this if you need to, but use it at your own risk: You can wrap a mutable structure inside a class instance which has both a __eq__() and a __hash__() method. You must then make sure that the hash value for all such wrapper objects that reside in a dictionary (or other hash based structure), remain fixed while the object is in the dictionary (or other 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

Note that the hash computation is complicated by the possibility that some members of the list may be unhashable and also by the possibility of arithmetic overflow.

Furthermore it must always be the case that if o1 == o2 (ie o1.__eq__(o2) is True) then hash(o1) == hash(o2) (ie, o1.__hash__() == o2.__hash__()), regardless of whether the object is in a dictionary or not. If you fail to meet these restrictions dictionaries and other hash based structures will misbehave.

In the case of ListWrapper, whenever the wrapper object is in a dictionary the wrapped list must not change to avoid anomalies. Don’t do this unless you are prepared to think hard about the requirements and the consequences of not meeting them correctly. Consider yourself warned.

Why doesn’t list.sort() return the sorted list?

In situations where performance matters, making a copy of the list just to sort it would be wasteful. Therefore, list.sort() sorts the list in place. In order to remind you of that fact, it does not return the sorted list. This way, you won’t be fooled into accidentally overwriting a list when you need a sorted copy but also need to keep the unsorted version around.

If you want to return a new list, use the built-in sorted() function instead. This function creates a new list from a provided iterable, sorts it and returns it. For example, here’s how to iterate over the keys of a dictionary in sorted order:

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

How do you specify and enforce an interface spec in Python?

An interface specification for a module as provided by languages such as C++ and Java describes the prototypes for the methods and functions of the module. Many feel that compile-time enforcement of interface specifications helps in the construction of large programs.

Python 2.6 adds an abc module that lets you define Abstract Base Classes (ABCs). You can then use isinstance() and issubclass() to check whether an instance or a class implements a particular ABC. The collections.abc module defines a set of useful ABCs such as Iterable, Container, and MutableMapping.

For Python, many of the advantages of interface specifications can be obtained by an appropriate test discipline for components. There is also a tool, PyChecker, which can be used to find problems due to subclassing.

A good test suite for a module can both provide a regression test and serve as a module interface specification and a set of examples. Many Python modules can be run as a script to provide a simple « self test. » Even modules which use complex external interfaces can often be tested in isolation using trivial « stub » emulations of the external interface. The doctest and unittest modules or third-party test frameworks can be used to construct exhaustive test suites that exercise every line of code in a module.

An appropriate testing discipline can help build large complex applications in Python as well as having interface specifications would. In fact, it can be better because an interface specification cannot test certain properties of a program. For example, the append() method is expected to add new elements to the end of some internal list; an interface specification cannot test that your append() implementation will actually do this correctly, but it’s trivial to check this property in a test suite.

Writing test suites is very helpful, and you might want to design your code with an eye to making it easily tested. One increasingly popular technique, test-directed development, calls for writing parts of the test suite first, before you write any of the actual code. Of course Python allows you to be sloppy and not write test cases at all.

Pourquoi n’y a-t-il pas de goto en Python ?

Vous pouvez utiliser les exceptions afin de mettre en place un « goto structuré » qui fonctionne même avec les appels de fonctions. Beaucoup de personnes estiment que les exceptions peuvent émuler idéalement tout utilisation raisonnable des constructions « go » ou « goto » en C, en Fortran ou 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.

Why can’t raw strings (r-strings) end with a backslash?

More precisely, they can’t end with an odd number of backslashes: the unpaired backslash at the end escapes the closing quote character, leaving an unterminated string.

Raw strings were designed to ease creating input for processors (chiefly regular expression engines) that want to do their own backslash escape processing. Such processors consider an unmatched trailing backslash to be an error anyway, so raw strings disallow that. In return, they allow you to pass on the string quote character by escaping it with a backslash. These rules work well when r-strings are used for their intended purpose.

If you’re trying to build Windows pathnames, note that all Windows system calls accept forward slashes too:

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

If you’re trying to build a pathname for a DOS command, try e.g. one of

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?

For instance, take the following incomplete snippet:

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

The snippet assumes that « a » must have a member attribute called « x ». However, there is nothing in Python that tells the interpreter this. What should happen if « a » is, let us say, an integer? If there is a global variable named « x », will it be used inside the with block? As you see, the dynamic nature of Python makes such choices much harder.

The primary benefit of « with » and similar language features (reduction of code volume) can, however, easily be achieved in Python by assignment. Instead of:

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

write this:

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

This also has the side-effect of increasing execution speed because name bindings are resolved at run-time in Python, and the second version only needs to perform the resolution once.

Why are colons required for the if/while/def/class statements?

The colon is required primarily to enhance readability (one of the results of the experimental ABC language). Consider this:

if a == b
    print(a)

versus

if a == b:
    print(a)

Notice how the second one is slightly easier to read. Notice further how a colon sets off the example in this FAQ answer; it’s a standard usage in English.

Another minor reason is that the colon makes it easier for editors with syntax highlighting; they can look for colons to decide when indentation needs to be increased instead of having to do a more elaborate parsing of the program text.

Why does Python allow commas at the end of lists and tuples?

Python lets you add a trailing comma at the end of lists, tuples, and dictionaries:

[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.

When you have a literal value for a list, tuple, or dictionary spread across multiple lines, it’s easier to add more elements because you don’t have to remember to add a comma to the previous line. The lines can also be reordered without creating a syntax error.

Accidentally omitting the comma can lead to errors that are hard to diagnose. For example:

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

This list looks like it has four elements, but it actually contains three: « fee », « fiefoo » and « fum ». Always adding the comma avoids this source of error.

Allowing the trailing comma may also make programmatic code generation easier.