FAQ sur l'histoire et la conception¶
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é d'un programme Python ordinaire. 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 et 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++
est exécutée si la condition est vraie, mais l'indentation conduit beaucoup de gens à penser le contraire. Mêmes des développeurs C expérimentés peuvent parfois rester 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 et fin, Python est moins sujet aux conflits de style de code. En C, on peut placer les accolades de nombreuses façons. Après s'être habitué à lire et écrire selon un style particulier, il est normal de se sentir perturbé en lisant (ou en devant écrire) avec un autre style.
Nombre de styles de programmation utilisent des accolades de début et 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 et 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 n'en est pas un. 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.
Many numbers that can be written easily in decimal notation cannot be expressed exactly in binary floating point. For example, after:
>>> 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.
For a fuller explanation, please see the floating-point arithmetic chapter in the Python tutorial.
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 n-uplets 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 ne changera la chaîne de caractères "huit" en autre chose.
Pourquoi self doit-il être explicitement utilisé dans les définitions et les appels de méthodes ?¶
L'idée a été empruntée à Modula-3. Cela s'avère 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 reconnaître 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 devriez 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.
Second, it means that no special syntax is necessary if you want to explicitly
reference or call the method from a particular class. In C++, if you want to
use a method from a base class which is overridden in a derived class, you have
to use the ::
operator -- in Python you can write
baseclass.methodname(self, <argument list>)
. This is particularly useful
for __init__()
methods, and in general in cases where a derived class
method wants to extend the base class method of the same name and thus has to
call the base class method somehow.
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 lisx.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 unlen()
standard. Voyez la confusion qui arrive parfois quand une classe qui n'implémente pas une interface de dictionnaire a une méthodeget()
oukey()
, ou quand un objet qui n'est pas un fichier implémente une méthodewrite()
.—https://mail.python.org/pipermail/python-3000/2006-November/004643.html
Pourquoi join()
est une méthode de chaîne plutôt qu'une méthode de liste ou de n-uplet ?¶
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 mal à l'aise 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 argument peut se résumer à : « c'est vraiment moche d'utiliser une méthode de chaîne littérale (c.-à-d. constante) ». La réponse est « certes, mais une chaîne littérale est une valeur comme une autre, juste fixe ». Si on utilise les méthodes sur des noms de variables, il n'y a pas de raison à les interdire sur des chaînes littérales.
Le second consiste à se demander : « suis-je réellement en train de dire à une séquence de joindre ses membres avec une constante de chaîne ? ». Malheureusement, ce n'est pas ça. Allez savoir, il semble être bien moins difficile de comprendre ce qui se passe avec 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 fourni (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 ?¶
A try
/except
block is extremely efficient if no exceptions
are raised. Actually
catching an exception is expensive. In versions of Python prior to 2.0 it was
common to use this idiom:
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 ?¶
In general, structured switch statements execute one block of code
when an expression has a particular value or set of values.
Since Python 3.10 one can easily match literal values, or constants
within a namespace, with a match ... case
statement.
An older alternative is a sequence of if... elif... elif... else
.
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 :
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é :
class MyVisitor:
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.
Imitating switch with fallthrough, as with C's switch-case-default, is possible, much harder, and less needed.
Est-il possible d'émuler des fils d'exécution dans l'interpréteur plutôt que se baser sur les implémentations spécifiques aux systèmes d'exploitation ?¶
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écessiterait une gestion complète pour le 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 que celui renvoyé par 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.
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.
Other implementations (such as Jython or PyPy), however, can rely on a different mechanism such as a full-blown garbage collector. This difference can cause some subtle porting problems if your Python code depends on the behavior of the reference counting implementation.
Dans certaines implémentations de Python, le code suivant (qui fonctionne 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)
Indeed, using CPython's reference counting and destructor scheme, each new
assignment to f
closes the previous file. With a traditional GC, however,
those file objects will only get collected (and closed) at varying and possibly
long intervals.
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 fonctionne 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-t-il pas un modèle de ramasse-miettes plus traditionnel ?¶
D'une part, ce n'est pas une caractéristique normalisée 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 plateformes classiques, mais pas toutes, et bien qu'elle soit le plus souvent transparente, elle ne l'est pas complètement ; des correctifs sont nécessaires afin que Python fonctionne correctement avec.)
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, CPython works with anything that
implements malloc()
and free()
properly.
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ées 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 atexit
pour exécuter une fonction qui va forcer ces destructions.
Pourquoi les n-uplets et les list sont deux types de données séparés ?¶
Lists and tuples, while similar in many respects, are generally used in
fundamentally different ways. Tuples can be thought of as being similar to
Pascal records
or C structs
; they're small collections of related data which may
be of different types which are operated on as a group. For example, a
Cartesian coordinate is appropriately represented as a tuple of two or three
numbers.
Lists, on the other hand, are more like arrays in other languages. They tend to
hold a varying number of objects all of which have the same type and which are
operated on one-by-one. For example, os.listdir('.')
returns a list of
strings representing the files in the current directory. Functions which
operate on this output would generally not break if you added another file or
two to the directory.
Les n-uplets sont immuables, ce qui signifie que lorsqu'un n-uplet a été créé, vous ne pouvez remplacer aucun de ses éléments par une nouvelle valeur. Les listes sont mutables, 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 n-uplets 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.
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 and a per-process seed; for example, 'Python'
could hash to
-539294296
while 'python'
, a string that differs by a single bit, could hash
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 Big-O notation -- to retrieve a key.
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 mutable, 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 n-uplet ; la fonction tuple(L)
crée un n-uplet avec les mêmes entrées que la liste L
. Les n-uplets sont immuables et peuvent donc être utilisés comme clés du dictionnaire.
Certaines solutions insatisfaisantes ont été proposées :
Hacher les listes 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]])
lèverait une exception
KeyError
car l'ID de[1, 2]
utilisé dans la deuxième ligne diffère de celui 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 mutable, 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 n-uplet 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.
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
Notez que le calcul de hachage peut être compliqué car il est possible que certains membres de la liste soient impossibles à 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.
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.
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 classer serait un gaspillage. Par conséquent, list.sort()
classe la liste en place. Afin de vous le rappeler, elle ne renvoie pas la liste classé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 tout en gardant sous la main la version non triée.
Si vous souhaitez qu'une nouvelle liste soit renvoyée, utilisez plutôt la fonction native sorted()
. Cette fonction crée une nouvelle liste à partir d’un itérable fourni, la classe et la renvoie. Par exemple, voici comment itérer dans l’ordre sur les clés d’un dictionnaire :
for key in sorted(mydict):
... # do whatever with mydict[key]...
Comment spécifier une interface et appliquer 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 facilite la construction de grands programmes.
Python 2.6 ajoute un module abc
qui vous permet de définir des classes mères abstraites (ABC pour abstract base classes en anglais). 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'ABC 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 cadriciels de test tiers peuvent être utilisés pour construire des suites de tests exhaustives qui éprouvent chaque ligne de code dans un 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 list.append()
method is expected to add new elements
to the end of some internal list; an interface specification cannot test that
your list.append()
implementation will actually do this correctly, but it's
trivial to check this property in a test suite.
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 ?¶
In the 1970s people realized that unrestricted goto could lead
to messy "spaghetti" code that was hard to understand and revise.
In a high-level language, it is also unneeded as long as there
are ways to branch (in Python, with if
statements and or
,
and
, and if
/else
expressions) and loop (with while
and for
statements, possibly containing continue
and break
).
One can also use exceptions to provide a "structured goto"
that works even across
function calls. Many feel that exceptions can conveniently emulate all
reasonable uses of the go
or goto
constructs of C, Fortran, and other
languages. For example:
class label(Exception): pass # declare a label
try:
...
if condition: raise label() # goto label
...
except label: # where to goto
pass
...
This doesn't allow you to jump into the middle of a loop, but that's usually
considered an abuse of goto
anyway. Use sparingly.
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 la 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-ci
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 has a with
statement that wraps the execution of a block, calling code
on the entrance and exit from the block. Some languages have a construct that
looks like this:
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 Pascal, Delphi et 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 sera référencé 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)
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
é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.
Similar proposals that would introduce syntax to further reduce code volume, such as using a 'leading dot', have been rejected in favour of explicitness (see https://mail.python.org/pipermail/python-ideas/2016-May/040070.html).
Pourquoi l'instruction with
ne prend-elle pas en charge les générateurs ?¶
For technical reasons, a generator used directly as a context manager
would not work correctly. When, as is most common, a generator is used as
an iterator run to completion, no closing is needed. When it is, wrap
it as contextlib.closing(generator)
in the with
statement.
Pourquoi les deux-points sont-ils nécessaires pour les déclarations if/while/def/class
?¶
Le deux-points est principalement nécessaire pour améliorer la lisibilité (l'un des résultats du langage expérimental ABC). Considérez ceci :
if a == b
print(a)
et cela
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 français (et 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 n-uplets ?¶
Python vous permet d'ajouter une virgule à la fin des listes, des n-uplets 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 n-uplet 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.