
Salut, habrozhiteli! Apprendre toutes les possibilités de Python est une tâche difficile, et avec ce livre, vous pouvez vous concentrer sur des compétences pratiques qui sont vraiment importantes. Creusez de l'or caché dans la bibliothèque standard Python et commencez à écrire du code propre aujourd'hui.
Si vous avez de l'expérience avec les anciennes versions de Python, vous pouvez accélérer le travail avec les modèles et fonctions modernes introduits dans Python 3.
Si vous avez travaillé avec d'autres langages de programmation et que vous souhaitez passer à Python, vous trouverez les conseils pratiques nécessaires pour devenir un pythoniste efficace.
Si vous voulez apprendre à écrire du code propre, vous trouverez ici les exemples les plus intéressants et les astuces peu connues.
Extrait "L'expression la plus folle du dictionnaire en Occident"
Parfois, vous rencontrez un petit exemple de code d'une profondeur vraiment inattendue - une seule ligne de code qui peut vous apprendre beaucoup si vous y réfléchissez attentivement. Un tel morceau de code est comme un koan dans le bouddhisme zen: une question ou une déclaration utilisée dans la pratique zen pour soulever des doutes et tester les résultats des élèves.
Le petit morceau de code dont nous discutons dans cette section en est un exemple. À première vue, cela peut ressembler à une expression de vocabulaire simple, mais en y regardant de plus près, il vous envoie dans une croisière psychédélique à l'esprit élargi avec l'interpréteur Python.
De ce one-liner, je reçois un tel buzz qu'une fois que je l'ai même imprimé sur mon badge de participant à la conférence Python comme occasion de conversation. Cela a conduit à des dialogues constructifs avec les membres de ma liste de diffusion Python.
Donc, sans plus tarder, voici ce morceau de code. Faites une pause pour réfléchir à l'expression du vocabulaire ci-dessous et à quoi cela devrait conduire:
>>> {True: '', 1: '', 1.0: ''}
J'attendrai ici ...
OK, prêt?
Voici le résultat que nous obtenons lors de l'évaluation de l'expression de dictionnaire ci-dessus dans une session d'interpréteur Python:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
J'avoue, quand j'ai vu ce résultat pour la première fois, j'étais très abasourdi. Mais tout se mettra en place lorsque vous effectuerez une étude étape par étape de ce qui se passe ici. Voyons pourquoi nous obtenons cela, je dois dire, résultat pas très intuitif.
Lorsque Python traite notre expression de dictionnaire, il crée d'abord un nouvel objet de dictionnaire vide, puis lui attribue des clés et des valeurs dans l'ordre dans lequel elles sont passées à l'expression de dictionnaire.
Ensuite, lorsque nous le décomposons en parties, notre expression de dictionnaire sera équivalente à la séquence d'instructions suivante qui sont exécutées dans l'ordre:
>>> xs = dict() >>> xs[True] = '' >>> xs[1] = '' >>> xs[1.0] = ''
Curieusement, Python considère que toutes les clés utilisées dans cet exemple de dictionnaire sont équivalentes:
>>> True == 1 == 1.0 True
D'accord, mais attendez une minute. Je suis sûr que vous pouvez admettre intuitivement que 1.0 == 1, mais pourquoi True est-il également considéré comme équivalent à 1? La première fois que j'ai vu cette expression de dictionnaire, cela m'a vraiment intrigué.
En fouillant un peu dans la documentation Python, j'ai découvert que Python traite le type bool comme une sous-classe du type int. C'est le cas en Python 2 et Python 3:
Le type booléen est un sous-type du type entier et les valeurs booléennes se comportent respectivement comme les valeurs 0 et 1 dans presque tous les contextes, à l'exception que lors de la conversion en type de chaîne, les valeurs de chaîne 'False' ou 'True sont retournées, respectivement ".
Et bien sûr, cela signifie qu'en Python, les valeurs booléennes peuvent techniquement être utilisées comme index de liste ou de tuple:
>>> ['', ''][True] ''
Mais vous ne devriez probablement pas utiliser ce type de variable logique au nom de la clarté (et de la santé mentale de vos collègues).
D'une manière ou d'une autre, revenons à notre expression de dictionnaire.
Quant au langage Python, toutes ces valeurs - True, 1 et 1.0 - représentent la même clé de dictionnaire. Lorsque l'interpréteur évalue une expression de dictionnaire, il écrase à plusieurs reprises la valeur de la clé True. Cela explique pourquoi, à la toute fin, le dictionnaire résultant ne contient qu'une seule clé.
Avant d'aller plus loin, jetez un autre coup d'œil à l'expression du dictionnaire d'origine:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
Pourquoi obtenons-nous toujours True comme clé ici? La clé ne devrait-elle pas également passer à 1.0 en raison de tâches répétées à la toute fin?
Après quelques recherches dans le code source de l'interpréteur Python, j'ai découvert que lorsqu'une nouvelle valeur est associée à un objet clé, les dictionnaires Python eux-mêmes ne mettent pas à jour cet objet clé:
>>> ys = {1.0: ''} >>> ys[True] = '' >>> ys {1.0: ''}
Bien sûr, cela a du sens en tant qu'optimisation des performances: si les clés sont considérées comme identiques, alors pourquoi perdre du temps à mettre à jour l'original?
Dans le dernier exemple, vous avez vu que l'objet True d'origine sous forme de clé n'est jamais remplacé. Pour cette raison, la représentation sous forme de chaîne du dictionnaire imprime toujours la clé comme True (au lieu de 1 ou 1.0).
Avec ce que nous savons maintenant, apparemment, les valeurs du dictionnaire résultant ne sont réécrites que parce que la comparaison les montrera toujours équivalentes les unes aux autres. Cependant, il s'avère que cet effet n'est pas non plus une conséquence du test d'équivalence par la méthode __eq__.
Les dictionnaires Python reposent sur une structure de données de table de hachage. Quand j'ai vu pour la première fois cette incroyable expression de dictionnaire, ma première pensée a été qu'un tel comportement était en quelque sorte lié aux conflits de hachage.
Le fait est que la table de hachage dans la représentation interne stocke les clés qui y sont disponibles dans différents "paniers" en fonction de la valeur de hachage de chaque clé. La valeur de hachage est dérivée de la clé en tant que valeur numérique de longueur fixe qui identifie de manière unique la clé.
Ce fait vous permet d'effectuer des opérations de recherche rapide. La recherche de la valeur de hachage de clé dans la table de recherche est beaucoup plus rapide que la comparaison de l'objet clé complet avec toutes les autres clés et l'exécution d'une vérification d'équivalence.
Cependant, les méthodes de calcul des valeurs de hachage ne sont généralement pas idéales. Et finalement, deux clés ou plus qui sont réellement différentes auront la même valeur de hachage dérivée, et elles se retrouveront dans le même panier de table de recherche.
Lorsque deux clés ont la même valeur de hachage, cette situation est appelée conflit de hachage et est un cas particulier avec lequel les algorithmes pour insérer et rechercher des éléments dans une table de hachage doivent être traités.
Sur la base de cette évaluation, il est très probable que le hachage soit en quelque sorte lié au résultat inattendu que nous avons obtenu à partir de notre expression de dictionnaire. Par conséquent, découvrons si les valeurs de hachage de clé jouent également un certain rôle ici.
Je définis la classe ci-dessous comme un petit outil de détective:
class AlwaysEquals: def __eq__(self, other): return True def __hash__(self): return id(self)
Cette classe se caractérise par deux aspects.
Tout d'abord, étant donné que la méthode __eq__ Dunder renvoie toujours True, toutes les instances de cette classe prétendent être équivalentes à n'importe quel objet:
>>> AlwaysEquals() == AlwaysEquals() True >>> AlwaysEquals() == 42 True >>> AlwaysEquals() == '?' True
Et deuxièmement, chaque instance de AlwaysEquals retournera également une valeur de hachage unique générée par la fonction intégrée id ():
>>> objects = [AlwaysEquals(), AlwaysEquals(), AlwaysEquals()] >>> [hash(obj) for obj in objects] [4574298968, 4574287912, 4574287072]
En Python, la fonction id () renvoie l'adresse d'un objet en RAM, qui est garantie d'être unique.
En utilisant cette classe, vous pouvez désormais créer des objets qui prétendent être équivalents à tout autre objet, mais en même temps avoir une valeur de hachage unique qui leur est associée. Cela vous permettra de vérifier si les clés du dictionnaire sont réécrites, en se basant uniquement sur le résultat de leur comparaison sur l'équivalence.
Et, comme vous pouvez le voir, les clés de l'exemple suivant ne correspondent pas, bien que la comparaison les montre toujours comme équivalentes les unes aux autres:
>>> {AlwaysEquals(): '', AlwaysEquals(): ''} { <AlwaysEquals object at 0x110a3c588>: '', <AlwaysEquals object at 0x110a3cf98>: '' }
Nous pouvons également regarder cette idée de l'autre côté et vérifier si le retour de la même valeur de hachage est une raison suffisante pour forcer la réécriture des clés:
class SameHash: def __hash__(self): return 1
La comparaison des instances de la classe SameHash les montrera comme non équivalentes les unes aux autres, mais elles auront toutes la même valeur de hachage de 1:
>>> a = SameHash() >>> b = SameHash() >>> a == b False >>> hash(a), hash(b) (1, 1)
Voyons comment les dictionnaires Python réagissent lorsque nous essayons d'utiliser des instances de classe SameHash comme clés de dictionnaire:
>>> {a: 'a', b: 'b'} { <SameHash instance at 0x7f7159020cb0>: 'a', <SameHash instance at 0x7f7159020cf8>: 'b' }
Comme le montre cet exemple, l'effet de «l'écrasement des clés» n'est pas uniquement causé par des conflits de valeurs de hachage.
Les dictionnaires effectuent une vérification d'équivalence et comparent la valeur de hachage pour déterminer si les deux clés sont identiques. Essayons de résumer les résultats de notre étude.
L'expression du dictionnaire {True: 'oui', 1: 'non', 1.0: 'peut-être'} est calculée comme {True: 'possible'}, car la comparaison de toutes les clés de cet exemple, True, 1 et 1.0, les affichera comme équivalents les uns aux autres, et ils ont tous la même valeur de hachage:
>>> True == 1 == 1.0 True >>> (hash(True), hash(1), hash(1.0)) (1, 1, 1)
Il n'est peut-être pas si surprenant que nous ayons obtenu un résultat comme l'état final du dictionnaire:
>>> {True: '', 1: '', 1.0: ''} {True: ''}
Ici, nous avons couvert beaucoup de sujets, et cette astuce Python particulière peut ne pas rentrer dans la tête au début - c'est pourquoi au tout début de la section, je l'ai comparé avec un koan zen.
Si vous avez du mal à comprendre ce qui se passe dans cette section, essayez tour à tour de tester tous les exemples de code dans une session d'interpréteur Python. Vous serez récompensé en élargissant votre connaissance des mécanismes internes du langage Python.
»Plus d'informations sur le livre sont disponibles sur
le site Web de l'éditeur»
Contenu»
Extrait20% de réduction sur les colporteurs -
Python