6
votes

Comment réécrire cette boucle simple à l'aide d'expressions d'affectation introduites dans Python 3.8 alpha?

Il me semble que ce n'est pas si simple d'échanger des boucles while classiques avec expressions d’affectation -loops gardant le code en bon état.

Considérez example1:

>>> a = 0
>>> while a < 10:
...     print(a)
...     a += 1
... 
0
1
2
3
4
5
6
7
8
9

et example2:

>>> a = 0
>>> while (a := a+1) < 10:
...     print(a)
... 
1
2
3
4
5
6
7
8
9

Comment modifieriez-vous example1 afin d'avoir la même sortie (sans sauter le 0 ) de example2 ? (sans changer a = 0 , bien sûr)


12 commentaires

Je ne suis pas sûr que ce soit un bon cas d'utilisation pour les expressions d'affectation: Le danger des exemples de jouets est double: ils sont souvent trop abstraits pour faire dire à quiconque "ooh, c'est convaincant", et ils sont facilement réfutés avec "je le ferais de toute façon, ne l'écrivez jamais de cette façon ". python.org/dev / peps / pep-0572 / # justification


Changer a serait également mon choix. Mais je n'aime pas ça. Il doit y avoir un meilleur moyen!


Je ne vois pas pourquoi vous voudriez qu'il en soit autrement. Une boucle while évalue le code dans la condition de boucle avant chaque itération. Ainsi, une instruction qui incrémente la variable est exécutée avant la première itération et vous devez soit commencer par a = -1 soit imprimer a-1 . Il semble que vous aimeriez avoir une boucle do-while au lieu d'une boucle while pour ce que vous faites.


@allo Je voulais juste tester quelques cas d'utilisation pour la nouvelle expression d'affectation. En lisant PEP 572, j'ai compris que les boucles while sont de bons points de départ et je veux maintenant savoir s'il existe une meilleure façon de traiter l'exemple affiché. Changer la variable avant la boucle n'est pas ma solution préférée. Alors, y a-t-il une autre option ou pas?


Je ne sais pas, je suppose qu'il n'est pas conçu de la façon dont vous voulez l'utiliser, mais je suis également curieux d'obtenir d'autres réponses.


print (a-1) fonctionnera si vous n'avez pas à attribuer a = -1


@Arihant Ce ne sera pas le cas. Il imprimera le 0 mais pas le 9 final


À mon humble avis, votre exemple est un non-sens puisque, comme j'ai compris l'expression d'affectation, le but est d'éviter la pré-assignation d'une variable avant la boucle. Dans votre exemple, vous pré-attribuez en fait un de toute façon. Votre problème est le même que nous pouvons avoir dans d'autres langues entre a ++ et ++ a . Le premier cas correspond à un comportement ++ a , le second correspond à un comportement a ++ . Votre question équivaut à demander comment coder un comportement ++ a en utilisant a ++ ; non-sens à nouveau.


Le plus gros problème que je vois est que vous réinventez simplement une boucle for ici. Il existe des cas d'utilisation pour les expressions d'affectation, ce n'est simplement pas l'un d'eux .


@MartijnPieters qui pourrait être la réponse finale. Je me demandais simplement s'il y avait un moyen de contourner ou non. J'accepterais NON comme réponse.


Mettez-vous d'accord sur les critiques quant à savoir s'il s'agit d'un exemple sensé. Dans ce cas, nous avons déjà pour un in range (0, 10):


Cette fonctionnalité est destinée au calcul ; évaluer; utilisez les séquences tandis que l'exemple OP si évalue; utilisation; recalculer . Ainsi, il ne mappe pas nativement. Utilisez une autre construction de langage!


3 Réponses :


0
votes

Je suggérerais une boucle do-while, mais elle n'est pas prise en charge en Python. Cependant, vous pouvez émuler un moment en agissant comme un do-while. De cette manière, vous pouvez utiliser l'expression d'affectation

a=0
while True:
    print(a)
    if not((a:=a+1)<10):
        break


0 commentaires

1
votes

Le problème avec la question est que toute l'approche du problème semble venir d'un programmeur essayant d'utiliser Python comme d'autres langages.

Un programmeur Python expérimenté n'utiliserait pas une boucle while dans ce cas. Ils feraient soit ceci à la place:

for a in range(10):
    print (a)

... soit encore plus simple:

from itertools import takewhile, count

for a in takewhile(lambda x: x<10, count()):
    print (a)

Comme c'est souvent le cas cas (pas toujours bien sûr), le code laid présenté dans la question est un symptôme de l'utilisation du langage d'une manière moins qu'optimale.


2 commentaires

J'ai fait juste un exemple de jouet pour illustrer le concept. Bien sûr, j'irais avec une boucle for dans ce cas. Ma question est plus théorique. "Y a-t-il un moyen?" La réponse pourrait aussi être: "NON, à cause de ceci et de cela ..."


Mais la question ne devient une question valable que si vous pouvez trouver un exemple qui illustre réellement ce que vous essayez de demander. L'exemple que vous avez fourni ne l'illustre pas. Trouvez-en un autre.



18
votes

Les boucles simples comme votre exemple ne doivent pas utiliser d'expressions d'attribution . Le PEP a un Recommandations du guide de style section à laquelle vous devez tenir compte:

  1. Si des instructions d'affectation ou des expressions d'affectation peuvent être utilisées, préférez les instructions; il s'agit d'une déclaration d'intention claire.
  2. Si l'utilisation d'expressions d'affectation entraîne une ambiguïté sur l'ordre d'exécution, restructurez-la pour utiliser des instructions à la place.

Les boucles simples doivent être implémentées en utilisant des itérables et pour , elles sont beaucoup plus clairement destinées à boucler jusqu'à ce que l'itérateur soit terminé. Pour votre exemple, l'itérable de choix serait range():

if any((found := ob).some_test(arg) for ob in iterable):
    # do something with 'found'

qui est beaucoup plus propre, concis et lisible em > que, dites

found = next((ob for ob in iterable if ob.some_test(arg)), None)
if found is not None:
    # do something with 'found'

Ce qui précède nécessite un examen plus approfondi pour déterminer que dans la boucle a commencera à 0 , pas à -1.

L'essentiel est que vous ne devriez pas être tenté de «trouver des moyens d'utiliser les instructions d'assignation». N'utilisez une instruction d'affectation que si elle simplifie le code, pas plus complexe. Il n'y a pas de bon moyen de rendre votre boucle while plus simple qu'une boucle for ici.

Vos tentatives de réécriture d'une boucle simple sont également reprises dans le Les conclusions de Tim Peters em> appendix , qui cite Tim Peters au sujet des expressions de style et d'affectation. Tim Peters est l'auteur du Zen of Python (parmi de nombreuses autres contributions à Python et au génie logiciel dans son ensemble), donc ses mots devraient avoir un poids supplémentaire:

Dans d'autres cas, la combinaison de la logique associée rendait la compréhension plus difficile, comme la réécriture:

if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(shallow)copyable object of type %s" % cls)

comme le plus bref:

reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error("un(shallow)copyable object of type %s" % cls)

Le test while y est trop subtil, reposant essentiellement sur une évaluation stricte de gauche à droite dans un contexte de non-court-circuit ou de chaînage de méthodes. Mon cerveau n'est pas câblé de cette façon.

Je m’insiste en gras.

Le modèle assigment-then-test est un bien meilleur cas d’utilisation des expressions d’affectation, en particulier lorsque plusieurs tests doivent être effectués. lieu qui essaie des objets successifs. L'essai de Tim cite un exemple donné par Kirill Balunov, de la bibliothèque standard, qui bénéficie en fait de la nouvelle syntaxe. La fonction copy.copy () a > doit trouver une méthode de hook appropriée pour créer une copie d'un objet personnalisé:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total

L'indentation ici est le résultat d'instructions if imbriquées car Python ne nous donne pas une syntaxe plus agréable pour tester différentes options jusqu'à ce qu'il en trouve une, et en même temps assigne l'option sélectionnée à une variable (vous ne pouvez pas utiliser proprement une boucle ici car tous les tests ne concernent pas les noms d'attributs).

Mais un expression d'affectation vous permet d'utiliser une structure plate if / elif / else :

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2

Ces 8 lignes sont beaucoup plus propres et plus facile à suivre (dans mon esprit) que le 13.

Un autre bon cas d'utilisation souvent cité est le s'il y a un objet correspondant après le filtrage, faites quelque chose avec cet objet em>, wh ich nécessite actuellement une fonction next () avec une expression de générateur, une valeur de repli par défaut et un test if :

a = -1
while (a := a + 1) < 10:
    # ...

que vous pouvez nettoyer beaucoup avec le any () function

for a in range(10):
    # ...


0 commentaires