trouver un expert Python
Chaque nouvelle version de Python ajoute de nouvelles fonctionnalités au langage. Pour Python 3.8, le plus gros changement est l'ajout de expressions d'affectation. Plus précisément, le :=
L'opérateur vous donne une nouvelle syntaxe pour affecter des variables au milieu des expressions. Cet opérateur est familièrement connu sous le nom de opérateur morse.
Ce didacticiel est une introduction approfondie à l'opérateur morse. Vous apprendrez certaines des motivations de la mise à jour de la syntaxe et explorerez quelques exemples où les expressions d'affectation peuvent être utiles.
Notez que tous les exemples d'opérateurs morses de ce didacticiel nécessitent Python 3.8 ou une version ultérieure pour fonctionner.
Principes de base de l'opérateur morse
Commençons par quelques termes différents que les programmeurs utilisent pour faire référence à cette nouvelle syntaxe. Vous en avez déjà vu quelques-uns dans ce tutoriel.
Le :=
l'opérateur est officiellement connu sous le nom de opérateur d'expression d'affectation. Au cours des premières discussions, il a été surnommé le opérateur morse parce que le :=
la syntaxe ressemble aux yeux et aux défenses d'un morse de côté. Vous pouvez également voir le :=
opérateur dénommé opérateur deux points est égal à. Un autre terme utilisé pour les expressions d'affectation est expressions nommées.
Salut Morse !
Pour avoir une première impression de ce que sont les expressions d'affectation, démarrez votre REPL et jouez avec le code suivant :
1>>> morse = Faux
2>>> morse
3Faux
4
5>>> (morse := Vrai)
6Vrai
7>>> morse
8Vrai
La ligne 1 montre une instruction d'affectation traditionnelle où la valeur Faux
est attribué à morse
. Ensuite, à la ligne 5, vous utilisez une expression d'affectation pour affecter la valeur Vrai
à morse
. Après les deux lignes 1 et 5, vous pouvez vous référer aux valeurs attribuées en utilisant le nom de la variable morse
.
Vous vous demandez peut-être pourquoi vous utilisez des parenthèses à la ligne 5, et vous apprendrez pourquoi les parenthèses sont nécessaires plus tard dans ce didacticiel.
Noter: UNE déclaration en Python est une unité de code. Une expression est une instruction spéciale qui peut être évaluée à une certaine valeur.
Par exemple, 1 + 2
est une expression qui évalue à la valeur 3
, tandis que nombre = 1 + 2
est une instruction d'affectation qui n'évalue pas à une valeur. Bien que l'exécution de la déclaration nombre = 1 + 2
n'évalue pas à 3
, Cela fait attribuer la valeur 3
à numéro
.
En Python, vous voyez souvent des instructions simples comme revenir
déclarations et importer
déclarations, ainsi que des déclarations composées comme si
instructions et définitions de fonctions. Ce sont toutes des déclarations, pas des expressions.
Il existe une différence subtile, mais importante, entre les deux types d'affectations vues précédemment avec le morse
variable. Une expression d'affectation renvoie la valeur, contrairement à une affectation traditionnelle. Vous pouvez le voir en action lorsque le REPL n'imprime aucune valeur après morse = Faux
à la ligne 1, pendant qu'il s'imprime Vrai
après l'expression d'affectation à la ligne 5.
Vous pouvez voir un autre aspect important des opérateurs morses dans cet exemple. Bien qu'il puisse sembler neuf, le :=
l'opérateur fait ne pas faire tout ce qui n'est pas possible sans cela. Cela ne fait que rendre certaines constructions plus pratiques et peut parfois communiquer plus clairement l'intention de votre code.
Vous avez maintenant une idée de base de ce que :=
l'opérateur est et ce qu'il peut faire. C'est un opérateur utilisé dans les expressions d'affectation, qui peut renvoyer la valeur affectée, contrairement aux instructions d'affectation traditionnelles. Pour approfondir et vraiment en savoir plus sur l'opérateur morse, continuez à lire pour voir où vous devriez et ne devriez pas l'utiliser.
Mise en œuvre
Comme la plupart des nouvelles fonctionnalités de Python, les expressions d'affectation ont été introduites via un Proposition d'amélioration Python (DYNAMISME). PEP 572 décrit la motivation pour introduire l'opérateur morse, les détails de la syntaxe, ainsi que des exemples où le :=
peut être utilisé pour améliorer votre code.
Ce PEP a été initialement écrit par Chris Angelico en février 2018. Après de vives discussions, le PEP 572 a été accepté par Guido van Rossum en juillet 2018. Depuis lors, Guido a annoncé qu'il se retirait de son rôle de dictateur bienveillant à vie (BDFL). À partir du début de 2019, Python est plutôt gouverné par un conseil de direction élu.
L'opérateur morse a été implémenté par Emily Morehouse et disponible dans la première version alpha de Python 3.8.
Motivation
Dans de nombreux langages, y compris C et ses dérivés, les instructions d'affectation fonctionnent comme des expressions. Cela peut être à la fois très puissant et aussi une source de bugs déroutants. Par exemple, le code suivant est du C valide mais ne s'exécute pas comme prévu :
entier X = 3, oui = 8;
si (X = oui)
imprimer("x et y sont égaux (x = %d, y = %d)", X, oui);
Ici, si (x = y)
évaluera à vrai et l'extrait de code s'imprimera x et y sont égaux (x = 8, y = 8)
. Est-ce le résultat que vous attendiez ? Tu essayais de comparer X
et oui
. Comment la valeur de X
changer de 3
à 8
?
Le problème est que vous utilisez l'opérateur d'affectation (=
) au lieu de l'opérateur de comparaison d'égalité (==
). En C, x = y
est une expression qui évalue à la valeur de oui
. Dans cet exemple, x = y
est évalué comme 8
, ce qui est considéré comme véridique dans le contexte de la si
déclaration.
Jetez un œil à un exemple correspondant en Python. Ce code soulève un Erreur de syntaxe
:
X, oui = 3, 8
si X = oui:
imprimer(F"x et y sont égaux (X = , oui = )")
Contrairement à l'exemple C, ce code Python vous donne une erreur explicite au lieu d'un bogue.
La distinction entre les instructions d'affectation et les expressions d'affectation en Python est utile afin d'éviter ce genre de bogues difficiles à trouver. Le PEP 572 soutient que Python est mieux adapté pour avoir une syntaxe différente pour les instructions d'affectation et les expressions au lieu de transformer les instructions d'affectation existantes en expressions.
Un principe de conception qui sous-tend l'opérateur morse est qu'il n'y a pas de contextes de code identiques où à la fois une instruction d'affectation utilisant le =
opérateur et une expression d'affectation à l'aide de l'opérateur :=
l'opérateur serait valide. Par exemple, vous ne pouvez pas faire une affectation simple avec l'opérateur morse :
>>> morse := Vrai
Fichier "" , ligne 1
morse := Vrai
^
Erreur de syntaxe: Syntaxe invalide
Dans de nombreux cas, vous pouvez ajouter des parenthèses (()
) autour de l'expression d'affectation pour la rendre valide Python :
>>> (morse := Vrai) # Valide, mais les déclarations régulières sont préférées
Vrai
Rédaction d'une déclaration de mission traditionnelle avec =
n'est pas autorisé à l'intérieur de ces parenthèses. Cela vous aide à détecter les bogues potentiels.
Plus loin dans ce didacticiel, vous en apprendrez plus sur les situations où l'opérateur morse n'est pas autorisé, mais vous découvrirez d'abord les situations dans lesquelles vous pourriez vouloir les utiliser.
Cas d'utilisation de l'opérateur Walrus
Dans cette section, vous verrez plusieurs exemples où l'opérateur morse peut simplifier votre code. Un thème général dans tous ces exemples est que vous éviterez différents types de répétition :
- Appels de fonction répétés peut rendre votre code plus lent que nécessaire.
- Déclarations répétées peut rendre votre code difficile à maintenir.
- Appels répétés qui épuisent les itérateurs peut rendre votre code trop complexe.
Vous verrez comment l'opérateur morse peut vous aider dans chacune de ces situations.
Débogage
L'un des meilleurs cas d'utilisation de l'opérateur morse est sans doute le débogage d'expressions complexes. Supposons que vous souhaitiez trouver la distance entre deux emplacements le long de la surface de la Terre. Une façon de le faire est d'utiliser la formule haversine :
φ représente la latitude et λ représente la longitude de chaque emplacement. Pour illustrer cette formule, vous pouvez calculer la distance entre Oslo (59,9°N 10,8°E) et Vancouver (49,3°N 123,1°W) comme suit :
>>> de math importer un péché, car, radians, péché, carré
>>> # Rayon approximatif de la Terre en kilomètres
>>> rad = 6371
>>> # Emplacements d'Oslo et de Vancouver
>>> 1, 1 = radians(59,9), radians(10.8)
>>> 2, 2 = radians(49,3), radians(-123,1)
>>> # Distance entre Oslo et Vancouver
>>> 2 * rad * un péché(
... carré(
... péché((2 - 1) / 2) ** 2
... + car(1) * car(2) * péché((2 - 1) / 2) ** 2
... )
... )
...
7181.7841229421165
Comme vous pouvez le voir, la distance d'Oslo à Vancouver est d'un peu moins de 7200 kilomètres.
Noter: Le code source Python est généralement écrit en utilisant UTF-8 Unicode. Cela vous permet d'utiliser des lettres grecques comme φ
et λ
dans votre code, ce qui peut être utile lors de la traduction de formules mathématiques. Wikipedia montre quelques alternatives pour utiliser Unicode sur votre système.
Alors que UTF-8 est pris en charge (dans les chaînes littérales, par exemple), les noms de variables de Python utilisent un jeu de caractères plus limité. Par exemple, vous ne pouvez pas utiliser d'émojis lorsque vous nommez vos variables. C'est une bonne restriction !
Maintenant, disons que vous avez besoin de revérifier votre implémentation et que vous voulez voir dans quelle mesure les termes haversine contribuent au résultat final. Vous pouvez copier et coller le terme de votre code principal pour l'évaluer séparément. Cependant, vous pouvez également utiliser le :=
pour donner un nom à la sous-expression qui vous intéresse :
>>> 2 * rad * un péché(
... carré(
... (_hav := péché((2 - 1) / 2) ** 2)
... + car(1) * car(2) * péché((2 - 1) / 2) ** 2
... )
... )
...
7181.7841229421165
>>> _hav
0,008532325425222883
L'avantage d'utiliser l'opérateur morse ici est que vous calculez la valeur de l'expression complète et gardez une trace de la valeur de _hav
en même temps. Cela vous permet de confirmer que vous n'avez introduit aucune erreur lors du débogage.
Listes et dictionnaires
Les listes sont de puissantes structures de données en Python qui représentent souvent une série d'attributs liés. De même, les dictionnaires sont utilisés partout dans Python et sont parfaits pour structurer les informations.
Parfois, lors de la configuration de ces structures de données, vous finissez par effectuer plusieurs fois la même opération. Comme premier exemple, calculez quelques statistiques descriptives de base d'une liste de nombres et stockez-les dans un dictionnaire :
>>> Nombres = [[[[2, 8, 0, 1, 1, 9, 7, 7]
>>> la description =
... "longueur": longueur(Nombres),
... "somme": somme(Nombres),
... "signifier": somme(Nombres) / longueur(Nombres),
...
>>> la description
'longueur' : 8, 'somme' : 35, 'moyenne' : 4,375
Notez que la somme et la longueur des Nombres
liste sont calculés deux fois. Les conséquences ne sont pas trop graves dans cet exemple simple, mais si la liste était plus longue ou si les calculs étaient plus compliqués, vous voudrez peut-être optimiser le code. Pour ce faire, vous pouvez d'abord déplacer les appels de fonction hors de la définition du dictionnaire :
>>> Nombres = [[[[2, 8, 0, 1, 1, 9, 7, 7]
>>> nombre_longueur = longueur(Nombres)
>>> somme_num = somme(Nombres)
>>> la description =
... "longueur": nombre_longueur,
... "somme": somme_num,
... "signifier": somme_num / nombre_longueur,
...
>>> la description
'longueur' : 8, 'somme' : 35, 'moyenne' : 4,375
Les variables nombre_longueur
et somme_num
ne sont utilisées que pour optimiser les calculs à l'intérieur du dictionnaire. En utilisant l'opérateur morse, ce rôle peut être rendu plus clair :
>>> Nombres = [[[[2, 8, 0, 1, 1, 9, 7, 7]
>>> la description =
... "longueur": (nombre_longueur := longueur(Nombres)),
... "somme": (somme_num := somme(Nombres)),
... "signifier": somme_num / nombre_longueur,
...
>>> la description
'longueur' : 8, 'somme' : 35, 'moyenne' : 4,375
nombre_longueur
et somme_num
sont maintenant définis à l'intérieur de la définition de la description
. C'est un indice clair pour quiconque lit ce code que ces variables sont juste utilisées pour optimiser ces calculs et ne sont pas réutilisées plus tard.
Noter: La portée de la nombre_longueur
et somme_num
variables est la même dans l'exemple avec l'opérateur morse et dans l'exemple sans. Cela signifie que dans les deux exemples, les variables sont disponibles après la définition de la description
.
Même si les deux exemples sont très similaires sur le plan fonctionnel, l'un des avantages de l'utilisation des expressions d'affectation est que le :=
l'opérateur communique le intention de ces variables en tant qu'optimisations jetables.
Dans l'exemple suivant, vous travaillerez avec une implémentation simple du toilettes
utilitaire pour compter les lignes, les mots et les caractères dans un fichier texte :
1# wc.py
2
3importer pathlib
4importer système
5
6pour nom de fichier dans système.argv[[[[1:]:
7 chemin = pathlib.Chemin(nom de fichier)
8 compte = (
9 chemin.read_text().compter("n"), # Nombre de lignes
dix longueur(chemin.read_text().diviser()), # Nombre de mots
11 longueur(chemin.read_text()), # Nombre de caractères
12 )
13 imprimer(*compte, chemin)
Ce script peut lire un ou plusieurs fichiers texte et indiquer le nombre de lignes, de mots et de caractères que chacun d'eux contient. Voici une ventilation de ce qui se passe dans le code :
- Ligne 6 boucle sur chaque nom de fichier fourni par l'utilisateur.
sys.argv
est une liste contenant chaque argument donné sur la ligne de commande, commençant par le nom de votre script. Pour plus d'informations sursys.argv
, vous pouvez consulter les arguments de ligne de commande Python. - Ligne 7 traduit chaque chaîne de nom de fichier en un
pathlib.Chemin
objet. Stockage d'un nom de fichier dans unChemin
object vous permet de lire facilement le fichier texte dans les lignes suivantes. - Lignes 8 à 12 construire un tuple de comptes pour représenter le nombre de lignes, de mots et de caractères dans un fichier texte.
- Ligne 9 lit un fichier texte et calcule le nombre de lignes en comptant les retours à la ligne.
- Ligne 10 lit un fichier texte et calcule le nombre de mots en les divisant sur des espaces.
- Ligne 11 lit un fichier texte et calcule le nombre de caractères en trouvant la longueur de la chaîne.
- Ligne 13 imprime les trois comptes avec le nom du fichier sur la console. Le
*compte
la syntaxe décompresse lecompte
tuple. Dans ce cas, leimprimer()
l'énoncé équivaut àimprimer (compte[0], compte[1], compte[2], chemin)
.
Voir wc.py
en action, vous pouvez utiliser le script sur lui-même comme suit :
$ python wc.py wc.py
13 34 316 wc.py
En d'autres termes, le wc.py
Le fichier se compose de 13 lignes, 34 mots et 316 caractères.
Si vous regardez attentivement cette implémentation, vous remarquerez qu'elle est loin d'être optimale. En particulier, l'appel à chemin.read_text()
est répété trois fois. Cela signifie que chaque fichier texte est lu trois fois. Vous pouvez utiliser l'opérateur morse pour éviter la répétition :
# wc.py
importer pathlib
importer système
pour nom de fichier dans système.argv[[[[1:]:
chemin = pathlib.Chemin(nom de fichier)
compte = [[[[
(texte := chemin.read_text()).compter("n"), # Nombre de lignes
longueur(texte.diviser()), # Nombre de mots
longueur(texte), # Nombre de caractères
]
imprimer(*compte, chemin)
Le contenu du fichier est affecté à texte
, qui est réutilisé dans les deux prochains calculs. Le programme fonctionne toujours de la même manière :
$ python wc.py wc.py
13 36 302 wc.py
Comme dans les exemples précédents, une approche alternative consiste à définir texte
avant la définition de compte
:
# wc.py
importer pathlib
importer système
pour nom de fichier dans système.argv[[[[1:]:
chemin = pathlib.Chemin(nom de fichier)
texte = chemin.read_text()
compte = [[[[
texte.compter("n"), # Nombre de lignes
longueur(texte.diviser()), # Nombre de mots
longueur(texte), # Nombre de caractères
]
imprimer(*compte, chemin)
Bien qu'il s'agisse d'une ligne de plus que l'implémentation précédente, il offre probablement le meilleur équilibre entre lisibilité et efficacité. Le :=
L'opérateur d'expression d'affectation n'est pas toujours la solution la plus lisible même lorsqu'il rend votre code plus concis.
Compréhensions de liste
Les compréhensions de liste sont idéales pour construire et filtrer des listes. Ils indiquent clairement l'intention du code et s'exécutent généralement assez rapidement.
Il existe un cas d'utilisation de la compréhension de liste où l'opérateur morse peut être particulièrement utile. Supposons que vous vouliez appliquer une fonction coûteuse en calcul, ralentir()
, aux éléments de votre liste et filtrez sur les valeurs résultantes. Vous pouvez faire quelque chose comme ce qui suit :
Nombres = [[[[7, 6, 1, 4, 1, 8, 0, 6]
résultats = [[[[ralentir(nombre) pour nombre dans Nombres si ralentir(nombre) > 0]
Ici, vous filtrez les Nombres
liste et laisser les résultats positifs de l'application ralentir()
. Le problème avec ce code est que cette fonction coûteuse est appelée deux fois.
Une solution très courante pour ce type de situation consiste à réécrire votre code pour utiliser un pour
boucle:
résultats = []
pour nombre dans Nombres:
valeur = ralentir(nombre)
si valeur > 0:
résultats.ajouter(valeur)
Cela n'appellera que ralentir()
une fois. Malheureusement, le code est maintenant plus détaillé et l'intention du code est plus difficile à comprendre. La compréhension de la liste avait clairement signalé que vous étiez en train de créer une nouvelle liste, alors que cela est plus caché dans l'explicite pour
boucle puisque plusieurs lignes de code séparent la création de la liste et l'utilisation de .ajouter()
. De plus, la compréhension de la liste est plus rapide que les appels répétés à .ajouter()
.
Vous pouvez coder d'autres solutions en utilisant un filtre()
expression ou une sorte de compréhension en double liste :
# Utilisation du filtre
résultats = filtre(lambda valeur: valeur > 0, (ralentir(nombre) pour nombre dans Nombres))
# Utiliser une compréhension en double liste
résultats = [[[[valeur pour nombre dans Nombres pour valeur dans [[[[ralentir(nombre)] si valeur > 0]
La bonne nouvelle est qu'il n'y a qu'un seul appel à ralentir()
pour chaque numéro. La mauvaise nouvelle est que la lisibilité du code a souffert dans les deux expressions.
Comprendre ce qui se passe réellement dans la compréhension de la double liste prend beaucoup de temps. Essentiellement, le deuxième pour
l'instruction est utilisée uniquement pour donner le nom valeur
à la valeur de retour de lent(nombre)
. Heureusement, cela ressemble à quelque chose qui peut être exécuté à la place avec une expression d'affectation !
Vous pouvez réécrire la compréhension de liste à l'aide de l'opérateur morse comme suit :
résultats = [[[[valeur pour nombre dans Nombres si (valeur := ralentir(nombre)) > 0]
Notez que les parenthèses autour value := slow(num)
sont requises. Cette version est efficace, lisible et communique bien l'intention du code.
Noter: Vous devez ajouter l'expression d'affectation sur le si
clause de la liste de compréhension. Si vous essayez de définir valeur
avec l'autre appel à ralentir()
, alors cela ne fonctionnera pas:
>>> résultats = [([([([(valeur := ralentir(nombre)) pour nombre dans Nombres si valeur > 0]
NameError : le nom 'valeur' n'est pas défini
Cela soulèvera un NameError
parce que le si
clause est évaluée avant l'expression au début de la compréhension.
Regardons un exemple un peu plus impliqué et pratique. Dites que vous voulez utiliser le Python réel pour retrouver les titres des derniers épisodes du Real Python Podcast.
Vous pouvez utiliser Real Python Feed Reader pour télécharger des informations sur les dernières Python réel parutions. Pour trouver les titres des épisodes de podcast, vous utiliserez le package Parse. Commencez par les installer dans votre environnement virtuel :
$ python -m pip install realpython-reader parse
Vous pouvez désormais lire les derniers titres publiés par Python réel:
>>> de lecteur importer alimentation
>>> alimentation.obtenir_titres()
['TheWalrusOperator:Python38AssignmentExpressions'['TheWalrusOperator:Python38AssignmentExpressions'['TheWalrusOperator:Python38AssignmentExpressions'['TheWalrusOperator:Python38AssignmentExpressions'
'The Real Python Podcast - Episode #63: Créer des applications Web à l'aide d'Anvil',
« Gestionnaires de contexte et Python avec instruction »,
...]
Les titres de podcast commencent par "Le vrai podcast Python"
, vous pouvez donc créer ici un modèle que Parse peut utiliser pour les identifier :
>>> importer analyser
>>> modèle = analyser.compiler(
... "Le vrai podcast Python - Épisode #num:d: Nom"
... )
La compilation préalable du modèle accélère les comparaisons ultérieures, en particulier lorsque vous souhaitez faire correspondre le même modèle encore et encore. Vous pouvez vérifier si une chaîne correspond à votre modèle en utilisant soit pattern.parse()
ou alors modèle.search()
:
>>> modèle.analyser(
... « Le vrai podcast Python - Épisode #63 : »
... "Créer des applications Web à l'aide d'Anvil"
... )
...
Notez que Parse est capable de choisir le numéro de l'épisode du podcast et le nom de l'épisode. Le numéro d'épisode est converti en un type de données entier car vous avez utilisé le :ré
spécificateur de format.
Revenons à la tâche à accomplir. Afin de répertorier tous les titres de podcast récents, vous devez vérifier si chaque chaîne correspond à votre modèle, puis analyser le titre de l'épisode. Une première tentative peut ressembler à ceci :
>>> importer analyser
>>> de lecteur importer alimentation
>>> modèle = analyser.compiler(
... "Le vrai podcast Python - Épisode #num:d: Nom"
... )
>>> balados = [[[[
... modèle.analyser(Titre)[[[["Nom"]
... pour Titre dans alimentation.obtenir_titres()
... si modèle.analyser(Titre)
... ]
>>> balados[:[:[:[:3]
['CreateWebApplicationsUsingOnlyPythonWithAnvil'['CreateWebApplicationsUsingOnlyPythonWithAnvil'['CreateWebApplicationsUsingOnlyPythonWithAnvil'['CreateWebApplicationsUsingOnlyPythonWithAnvil'
'Sélectionner la structure de données idéale et démêler les "passes" et "avec" de Python',
« Mettre à l'échelle une infrastructure de science des données et d'apprentissage automatique comme Netflix »]
Bien que cela fonctionne, vous remarquerez peut-être le même problème que vous avez vu plus tôt. Vous analysez chaque titre deux fois parce que vous filtrez les titres qui correspondent à votre modèle, puis utilisez ce même modèle pour choisir le titre de l'épisode.
Comme vous l'avez fait plus tôt, vous pouvez éviter le double travail en réécrivant la compréhension de la liste en utilisant soit un pour
boucle ou une double liste de compréhension. Cependant, l'utilisation de l'opérateur morse est encore plus simple :
>>> balados = [[[[
... Podcast[[[["Nom"]
... pour Titre dans alimentation.obtenir_titres()
... si (Podcast := modèle.analyser(Titre))
... ]
Les expressions d'affectation fonctionnent bien pour simplifier ces types de compréhension de liste. Ils vous aident à garder votre code lisible tout en évitant de faire deux fois une opération potentiellement coûteuse.
Noter: Le Real Python Podcast possède son propre flux RSS séparé, que vous devez utiliser si vous souhaitez jouer avec des informations uniquement sur le podcast. Vous pouvez obtenir tous les titres des épisodes avec le code suivant :
de lecteur importer alimentation
balados = alimentation.obtenir_titres("https://realpython.com/podcasts/rpp/feed")
Voir The Real Python Podcast pour les options pour l'écouter à l'aide de votre lecteur de podcast.
Dans cette section, vous vous êtes concentré sur des exemples où les compréhensions de liste peuvent être réécrites à l'aide de l'opérateur morse. Les mêmes principes s'appliquent également si vous constatez que vous devez répéter une opération dans une compréhension de dictionnaire, une compréhension d'ensemble ou une expression génératrice.
L'exemple suivant utilise une expression génératrice pour calculer la durée moyenne des titres d'épisodes qui sont terminés 50
Longs caractères:
>>> importer statistiques
>>> statistiques.signifier(
... title_length
... pour Titre dans balados
... si (title_length := longueur(Titre)) > 50
... )
65.425
L'expression du générateur utilise une expression d'affectation pour éviter de calculer deux fois la longueur de chaque titre d'épisode.
Tandis que les boucles
Python a deux constructions de boucle différentes : pour
boucles et tandis que
boucles. Vous utilisez généralement un pour
boucle lorsque vous devez parcourir une séquence connue d'éléments. UNE tandis que
boucle, d'autre part, est utilisé lorsque vous ne savez pas à l'avance combien de fois vous aurez besoin de boucler.
Dans tandis que
boucles, vous devez définir et vérifier la condition de fin en haut de la boucle. Cela conduit parfois à un code maladroit lorsque vous devez effectuer une configuration avant d'effectuer la vérification. Voici un extrait d'un programme de quiz à choix multiples qui demande à l'utilisateur de répondre à une question avec l'une des nombreuses réponses valides :
question = « Utiliserez-vous l'opérateur morse ? »
réponses_valides = "Oui", "Oui", "ou", "O", "non", "Non", "n", "N"
réponse de l'utilisateur = saisir(F"nquestion ")
tandis que réponse de l'utilisateur ne pas dans réponses_valides:
imprimer(F"Veuillez répondre à l'une des ', '.rejoindre(réponses_valides)")
réponse de l'utilisateur = saisir(F"nquestion ")
Cela fonctionne mais a une répétition malheureuse d'identiques saisir()
lignes. Il est nécessaire d'obtenir au moins une réponse de l'utilisateur avant de vérifier si elle est valide ou non. Vous avez alors un deuxième appel à saisir()
à l'intérieur de tandis que
boucle pour demander une deuxième réponse au cas où l'original réponse de l'utilisateur
n'était pas valide.
Si vous voulez rendre votre code plus maintenable, il est assez courant de réécrire ce genre de logique avec un tandis que vrai
boucle. Au lieu de faire du chèque une partie du tandis que
instruction, la vérification est effectuée plus tard dans la boucle avec une instruction explicite Pause
:
tandis que Vrai:
réponse de l'utilisateur = saisir(F"nquestion ")
si réponse de l'utilisateur dans réponses_valides:
Pause
imprimer(F"Veuillez répondre à l'une des ', '.rejoindre(réponses_valides)")
Cela a l'avantage d'éviter la répétition. Cependant, le contrôle réel est maintenant plus difficile à repérer.
Les expressions d'affectation peuvent souvent être utilisées pour simplifier ces types de boucles. Dans cet exemple, vous pouvez maintenant remettre le chèque avec tandis que
où cela a plus de sens :
tandis que (réponse de l'utilisateur := saisir(F"nquestion ")) ne pas dans réponses_valides:
imprimer(F"Veuillez répondre à l'une des ', '.rejoindre(réponses_valides)")
Le tandis que
L'instruction est un peu plus dense, mais le code communique maintenant l'intention plus clairement sans lignes répétées ou boucles apparemment infinies.
Vous pouvez développer la boîte ci-dessous pour voir le code complet du programme de quiz à choix multiples et essayer vous-même quelques questions sur l'opérateur morse.
Ce script exécute un quiz à choix multiples. On vous posera chacune des questions dans l'ordre, mais l'ordre des réponses sera modifié à chaque fois :
# morse_quiz.py
importer Aléatoire
importer chaîne de caractères
DES QUESTIONS =
« Quel est le nom du PEP 572 ? »: [[[[
"Expressions d'affectation",
« Expressions nommées »,
"L'Opérateur Morse",
« L'opérateur du côlon est égal à »,
],
« Lequel de ceux-ci est une utilisation invalide de l'opérateur morse ? »: [[[[
"[y**2 for x in range(10) if y := f(x) > 0]",
"imprimer(y := f(x))",
"(y := f(x))",
"tout((y := f(x)) pour x dans la plage (10))",
],
nombre_correct = 0
pour question, answers dans QUESTIONS.items():
correct = answers[[[[0]
random.shuffle(answers)
coded_answers = dict(zip(chaîne de caractères.ascii_lowercase, answers))
valid_answers = sorted(coded_answers.keys())
pour code, answer dans coded_answers.items():
imprimer(f" code) answer")
while (user_answer := input(f"nquestion ")) ne pas dans valid_answers:
imprimer(f"Please answer one of ', '.join(valid_answers)")
if coded_answers[[[[user_answer] == correct:
imprimer(f"Correct, the answer is user_answer!rn")
num_correct += 1
else:
imprimer(f"No, the answer is correct!rn")
imprimer(f"You got num_correct correct out of len(QUESTIONS) questions")
Note that the first answer is assumed to be the correct one. You can add more questions to the quiz yourself. Feel free to share your questions with the community in the comments section below the tutorial!
You can often simplify while
loops by using assignment expressions. The original PEP shows an example from the standard library that makes the same point.
Witnesses and Counterexamples
In the examples you’ve seen so far, the :=
assignment expression operator does essentially the same job as the =
assignment operator in your old code. You’ve seen how to simplify code, and now you’ll learn about a different type of use case that’s made possible by this new operator.
In this section, you’ll learn how you can find witnesses when calling any()
by using a clever trick that isn’t possible without using the walrus operator. A witness, in this context, is an element that satisfies the check and causes any()
to return Vrai
.
By applying similar logic, you’ll also learn how you can find counterexamples when working with all()
. A counterexample, in this context, is an element that doesn’t satisfy the check and causes all()
to return Faux
.
In order to have some data to work with, define the following list of city names:
>>> cities = [[[["Vancouver", "Oslo", "Houston", "Warsaw", "Graz", "Holguín"]
You can use any()
et all()
to answer questions about your data:
>>> # Does ANY city name start with "H"?
>>> any(ville.startswith("H") pour ville dans cities)
Vrai
>>> # Does ANY city name have at least 10 characters?
>>> any(len(ville) >= dix pour ville dans cities)
Faux
>>> # Do ALL city names contain "a" or "o"?
>>> tous(set(ville) & set("ao") pour ville dans cities)
Vrai
>>> # Do ALL city names start with "H"?
>>> tous(ville.startswith("H") pour ville dans cities)
Faux
In each of these cases, any()
et all()
give you plain Vrai
ou alors Faux
answers. What if you’re also interested in seeing an example or a counterexample of the city names? It could be nice to see what’s causing your Vrai
ou alors Faux
result:
-
Fait any city name start with
"H"
?Yes, because
"Houston"
starts with"H"
. -
Do tous city names start with
"H"
?No, because
"Oslo"
doesn’t start with"H"
.
In other words, you want a witness or a counterexample to justify the answer.
Capturing a witness to an any()
expression has not been intuitive in earlier versions of Python. If you were calling any()
on a list and then realized you also wanted a witness, you’d typically need to rewrite your code:
>>> witnesses = [[[[ville pour ville dans cities if ville.startswith("H")]
>>> if witnesses:
... imprimer(f"witnesses[[[[0] starts with H")
... else:
... imprimer("No city name starts with H")
...
Houston starts with H
Here, you first capture all city names that start with "H"
. Then, if there’s at least one such city name, you print out the first city name starting with "H"
. Note that here you’re actually not using any()
even though you’re doing a similar operation with the list comprehension.
By using the :=
operator, you can find witnesses directly in your any()
expressions:
>>> if any((witness := ville).startswith("H") pour ville dans cities):
... imprimer(f"witness starts with H")
... else:
... imprimer("No city name starts with H")
...
Houston starts with H
You can capture a witness inside the any()
expression. The reason this works is a bit subtle and relies on any()
et all()
using short-circuit evaluation: they only check as many items as necessary to determine the result.
Note: If you want to check whether tous city names start with the letter "H"
, then you can look for a counterexample by replacing any()
with all()
and updating the print()
functions to report the first item that doesn’t pass the check.
You can see what’s happening more clearly by wrapping .startswith("H")
in a function that also prints out which item is being checked:
>>> def starts_with_h(Nom):
... imprimer(f"Checking Nom: Nom.startswith('H')")
... return Nom.startswith("H")
...
>>> any(starts_with_h(ville) pour ville dans cities)
Checking Vancouver: False
Checking Oslo: False
Checking Houston: True
Vrai
Note that any()
doesn’t actually check all items in cities
. It only checks items until it finds one that satisfies the condition. Combining the :=
operator and any()
works by iteratively assigning each item that is being checked to witness
. However, only the last such item survives and shows which item was last checked by any()
.
Even when any()
returns Faux
, a witness is found:
>>> any(len(witness := ville) >= dix pour ville dans cities)
Faux
>>> witness
'Holguín'
However, in this case, witness
doesn’t give any insight. 'Holguín'
doesn’t contain 10 or more characters. The witness only shows which item happened to be evaluated last.
Walrus Operator Syntax
One of the main reasons assignments were not expressions in Python from the beginning is that the visual likeness of the assignment operator (=
) and the equality comparison operator (==
) could potentially lead to bugs. When introducing assignment expressions, a lot of thought was put into how to avoid similar bugs with the walrus operator. As mentioned earlier, one important feature is that the :=
operator is never allowed as a direct replacement for the =
operator, and vice versa.
As you saw at the beginning of this tutorial, you can’t use a plain assignment expression to assign a value:
>>> walrus := Vrai
File "" , line 1
walrus := Vrai
^
SyntaxError: invalid syntax
It’s syntactically legal to use an assignment expression to only assign a value, but only if you add parentheses:
>>> (walrus := Vrai)
Vrai
Even though it’s possible, however, this really is a prime example of where you should stay away from the walrus operator and use a traditional assignment statement instead.
PEP 572 shows several other examples where the :=
operator is either illegal or discouraged. The following examples all raise a SyntaxError
:
>>> lat = lon := 0
SyntaxError: invalid syntax
>>> angle(phi = lat := 59.9)
SyntaxError: invalid syntax
>>> def distance(phi = lat := 0, lam = lon := 0):
SyntaxError: invalid syntax
In all these cases, you’re better served using =
instead. The next examples are similar and are all legal code. However, the walrus operator doesn’t improve your code in any of these cases:
>>> lat = (lon := 0) # Discouraged
>>> angle(phi = (lat := 59.9)) # Discouraged
>>> def distance(phi = (lat := 0), lam = (lon := 0)): # Discouraged
... pass
...
None of these examples make your code more readable. You should instead do the extra assignment separately by using a traditional assignment statement. See PEP 572 for more details about the reasoning.
There’s one use case where the :=
character sequence is already valid Python. In f-strings, a colon (:
) is used to separate values from their format specification. Par exemple:
>>> x = 3
>>> f"x:=8"
' 3'
Le :=
in this case does look like a walrus operator, but the effect is quite different. To interpret x:=8
inside the f-string, the expression is broken into three parts: x
, :
, et =8
.
Here, x
is the value, :
acts as a separator, and =8
is a format specification. According to Python’s Format Specification Mini-Language, in this context =
specifies an alignment option. In this case, the value is padded with spaces in a field of width 8
.
To use assignment expressions inside f-strings, you need to add parentheses:
>>> x = 3
>>> f"(x := 8)"
'8'
>>> x
8
This updates the value of x
as expected. However, you’re probably better off using traditional assignments outside of your f-strings instead.
Let’s look at some other situations where assignment expressions are illegal:
-
Attribute and item assignment: You can only assign to simple names, not dotted or indexed names:
>>>>>> (mapping[[[["hearts"] := "♥") SyntaxError: cannot use assignment expressions with subscript >>> (number.answer := 42) SyntaxError: cannot use assignment expressions with attribute
This fails with a descriptive error message. There’s no straightforward workaround.
-
Iterable unpacking: You can’t unpack when using the walrus operator:
>>>>>> lat, lon := 59.9, 10.8 SyntaxError: invalid syntax
If you add parentheses around the whole expression, it will be interpreted as a 3-tuple with the three elements
lat
,59.9
, et10.8
. -
Augmented assignment: You can’t use the walrus operator combined with augmented assignment operators like
+=
. This raises aSyntaxError
:>>>>>> compter +:= 1 SyntaxError: invalid syntax
The easiest workaround would be to do the augmentation explicitly. You could, for example, do
(count := count + 1)
. PEP 577 originally described how to add augmented assignment expressions to Python, but the proposal was withdrawn.
When you’re using the walrus operator, it will behave similarly to traditional assignment statements in many respects:
-
Le scope of the assignment target is the same as for assignments. It will follow the LEGB rule. Typically, the assignment will happen in the local scope, but if the target name is already declared
global
ou alorsnonlocal
, that is honored. -
Le precedence of the walrus operator can cause some confusion. It binds less tightly than all other operators except the comma, so you might need parentheses to delimit the expression that is assigned. As an example, note what happens when you don’t use parentheses:
>>>>>> number = 3 >>> if square := number ** 2 > 5: ... imprimer(square) ... Vrai
square
is bound to the whole expressionnumber ** 2 > 5
. In other words,square
gets the valueVrai
and not the value ofnumber ** 2
, which was the intention. In this case, you can delimit the expression with parentheses:>>>>>> number = 3 >>> if (square := number ** 2) > 5: ... imprimer(square) ... 9
The parentheses make the
if
statement both clearer and actually correct.There’s one final gotcha. When assigning a tuple using the walrus operator, you always need to use parentheses around the tuple. Compare the following assignments:
>>>>>> walrus = 3.7, Faux >>> walrus (3.7, False) >>> (walrus := 3.8, Vrai) (3.8, True) >>> walrus 3.8 >>> (walrus := (3.8, Vrai)) (3.8, True) >>> walrus (3.8, True)
Note that in the second example,
walrus
takes the value3.8
and not the whole tuple3.8, True
. That’s because the:=
operator binds more tightly than the comma. This may seem a bit annoying. However, if the:=
operator bound less tightly than the comma, it would not be possible to use the walrus operator in function calls with more than one argument. -
Le style recommendations for the walrus operator are mostly the same as for the
=
operator used for assignment. First, always add spaces around the:=
operator in your code. Second, use parentheses around the expression as necessary, but avoid adding extra parentheses that are not needed.
The general design of assignment expressions is to make them easy to use when they are helpful but to avoid overusing them when they might clutter up your code.
Walrus Operator Pitfalls
The walrus operator is a new syntax that is only available in Python 3.8 and later. This means that any code you write that uses the :=
syntax will only work on the most recent versions of Python.
If you need to support older versions of Python, you can’t ship code that uses assignment expressions. There are some projects, like walrus
, that can automatically translate walrus operators into code that is compatible with older versions of Python. This allows you to take advantage of assignment expressions when writing your code and still distribute code that is compatible with more Python versions.
Experience with the walrus operator indicates that :=
will not revolutionize Python. Instead, using assignment expressions where they are useful can help you make several small improvements to your code that could benefit your work overall.
There are many times it’s possible for you to use the walrus operator, but where it won’t necessarily improve the readability or efficiency of your code. In those cases, you’re better off writing your code in a more traditional manner.
Conclusion
You now know how the new walrus operator works and how you can use it in your own code. By using the :=
syntax, you can avoid different kinds of repetition in your code and make your code both more efficient and easier to read and maintain. At the same time, you shouldn’t use assignment expressions everywhere. They will only help you in some use cases.
In this tutorial, you learned how to:
- Identify the walrus operator and understand its meaning
- Understand use cases for the walrus operator
- Avoid repetitive code by using the walrus operator
- Convert between code using the walrus operator and code using other assignment methods
- Understand the impacts on backward compatibility when using the walrus operator
- Use appropriate style in your assignment expressions
To learn more about the details of assignment expressions, see PEP 572. You can also check out the PyCon 2019 talk PEP 572: The Walrus Operator, where Dustin Ingram gives an overview of both the walrus operator and the discussion around the new PEP.
[ad_2]