FAQ histoire et design

Sommaire

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.

Many coding styles place begin/end brackets on a line by themselves. This makes programs considerably longer and wastes valuable screen space, making it harder to get a good overview of a program. Ideally, a function should fit on one screen (say, 20–30 lines). 20 lines of Python can do a lot more work than 20 lines of C. This is not solely due to the lack of begin/end brackets – the lack of declarations and the high-level data types are also responsible – but the indentation-based syntax certainly helps.

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 rien à voir avec Python, mais avec la manière dont la plateforme C sous-jacente gère les nombres à virgule flottante et enfin, les imprécisions introduites lors de l’écriture des nombres en chaînes de caractères d’un nombre fixe de chiffres.

La représentation interne des nombres à virgule flottante utilise un nombre fixe de chiffres binaires pour représenter un nombre décimal. Certains nombres décimaux ne peuvent être représentés exactement en binaire, résultant ainsi à de petites erreurs d’arrondi.

En mathématiques, beaucoup de nombre ne peuvent être représentés par un nombre fixe de chiffres, par exemple 1/3 = 0.3333333333…….

En base 2, 1/2 = 0.1, 1/4 = 0.01, 1/8 = 0.001, etc. .2 est égale à 2/10 qui est égale à 1/5, ayant pour résultat le nombre fractionnel binaire 0.001100110011001…

Les nombres à virgule flottante ont une précision de seulement 32 ou 64 bits, donc les chiffres finissent par être tronqués, et le nombre résultant est 0.199999999999999996 en décimal, pas 0.2.

La fonction repr() d’un nombre décimal affiche autant de chiffres que nécessaire pour rendre l’expression eval(repr(f)) == f vraie pour tout nombre décimal f. La fonction str() affiche moins de chiffres et correspond généralement plus au nombre attendu

>>> 1.1 - 0.9
0.20000000000000007
>>> print 1.1 - 0.9
0.2

En conséquence comparer le résultat d’un calcul avec un nombre flottant avec l’opérateur “==” est propice à l’obtention d’erreurs. D’infimes imprécisions peuvent faire qu’un test d’égalité avec « == » échoue. Au lieu de cela, vous devez vérifier que la différence entre les deux chiffres est inférieure à un certain seuil

epsilon = 0.0000000000001  # Tiny allowed error
expected_result = 0.4

if expected_result-epsilon <= computation() <= expected_result+epsilon:
    ...

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 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 économisaient quelques touches mais utilisaient des mots clefs ou des syntaxes arbitraires ou cryptiques, et manquaient à la règle que toute proposition de changement de langage devrait respecter : elle doit intuitivement suggérer la bonne signification au lecteur qui n’a pas encore été introduit à cette syntaxe.

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 boucler au travers d’objets en utilisant la déclaration for. Par exemple, dans la version actuelle de Python, les fichiers objets supportent le protocole d’itérateur, vous pouvez alors 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)) ?

As Guido said:

(a) For some operations, prefix notation just reads better than postfix – prefix (and infix!) operations have a long tradition in mathematics which likes notations where the visuals help the mathematician thinking about a problem. Compare the easy with which we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of doing the same thing using a raw OO notation.

(b) When I read code that says len(x) I know that it is asking for the length of something. This tells me two things: the result is an integer, and the argument is some kind of container. To the contrary, when I read x.len(), I have to already know that x is some kind of container implementing an interface or inheriting from a class that has a standard len(). Witness the confusion we occasionally have when a class that is not implementing a mapping has a get() or keys() method, or something that isn’t a file has a write() method.

https://mail.python.org/pipermail/python-3000/2006-November/004643.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(", ")

is an instruction to a string literal to return the substrings delimited by the given separator (or, by default, arbitrary runs of white space). In this case a Unicode string returns a list of Unicode strings, an ASCII string returns a list of ASCII strings, and everyone is happy.

join() is a string method because in using it you are telling the separator string to iterate over a sequence of strings and insert itself between adjacent elements. This method can be used with any argument which obeys the rules for sequence objects, including any new classes you might define yourself.

Because this is a string method it can work for Unicode strings as well as plain ASCII strings. If join() were a method of the sequence types then the sequence types would have to decide which type of string to return depending on the type of the separator.

If none of these arguments persuade you, then for the moment you can continue to use the join() function from the string module, which allows you to write

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

À 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)

Note

In Python 2.0 and higher, you can code this as value = mydict.setdefault(key, getvalue(key)).

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 à 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 ?

The details of Python memory management depend on the implementation. The standard C implementation of Python uses reference counting to detect inaccessible objects, and another mechanism to collect reference cycles, periodically executing a cycle detection algorithm which looks for inaccessible cycles and deletes the objects involved. The gc module provides functions to perform a garbage collection, obtain debugging statistics, and tune the collector’s parameters.

Jython relies on the Java runtime so the JVM’s garbage collector is used. This difference can cause some subtle porting problems if your Python code depends on the behavior of the reference counting implementation.

Sometimes objects get stuck in tracebacks temporarily and hence are not deallocated when you might expect. Clear the tracebacks with:

import sys
sys.exc_clear()
sys.exc_traceback = sys.last_traceback = None

Tracebacks are used for reporting errors, implementing debuggers and related things. They contain a portion of the program state extracted during the handling of an exception (usually the most recent exception).

In the absence of circularities and tracebacks, Python programs do not need to manage memory explicitly.

Why doesn’t Python use a more traditional garbage collection scheme? For one thing, this is not a C standard feature and hence it’s not portable. (Yes, we know about the Boehm GC library. It has bits of assembler code for most common platforms, not for all of them, and although it is mostly transparent, it isn’t completely transparent; patches are required to get Python to work with it.)

Traditional GC also becomes a problem when Python is embedded into other applications. While in a standalone Python it’s fine to replace the standard malloc() and free() with versions provided by the GC library, an application embedding Python may want to have its own substitute for malloc() and free(), and may not want Python’s. Right now, Python works with anything that implements malloc() and free() properly.

In Jython, the following code (which is fine in CPython) will probably run out of file descriptors long before it runs out of memory:

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

Using the current reference counting and destructor scheme, each new assignment to f closes the previous file. Using GC, this is not guaranteed. If you want to write code that will work with any Python implementation, you should explicitly close the file or use the with statement; this will work regardless of GC:

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

Why isn’t all memory freed when Python exits?

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

How are lists implemented in CPython?

CPython’s lists are really variable-length arrays, not Lisp-style linked lists. The implementation uses a contiguous array of references to other objects, and keeps a pointer to this array and the array’s length in a list head structure.

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.

How are dictionaries implemented in CPython?

CPython’s dictionaries are implemented as resizable hash tables. Compared to B-trees, this gives better performance for lookup (the most common operation by far) under most circumstances, and the implementation is simpler.

Dictionaries work by computing a hash code for each key stored in the dictionary using the hash() built-in function. The hash code varies widely depending on the key; for example, « Python » hashes to -539294296 while « python », a string that differs by a single bit, hashes to 1142331976. The hash code is then used to calculate a location in an internal array where the value will be stored. Assuming that you’re storing keys that all have different hash values, this means that dictionaries take constant time – O(1), in computer science notation – to retrieve a key. It also means that no sorted order of the keys is maintained, and traversing the array as the .keys() and .items() do will output the dictionary’s content in some arbitrary jumbled order.

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 de type 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.

In Python 2.4 a new built-in function – sorted() – has been added. 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]...

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 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 module defines a set of useful ABCs such as Iterable, Container, and 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. Il existe aussi un outil, PyChecker, qui peut être utilisé pour trouver des problèmes d’héritage.

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 ?

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