Pouvons-nous avoir une affectation dans une condition?

92

Est-il possible d'avoir une affectation dans une condition?

Par ex.

if (a=some_func()):
    # Use a
Vishal
la source
Le fait que deux questions aient la même réponse ne signifie pas qu'il s'agit de doublons. Trivial pour vous répondre, mais pas trivial pour voir le raisonnement, ou s'il y a un moyen de contourner.
mehmet

Réponses:

111

Pourquoi ne pas l'essayer?

>>> def some_func():
...   return 2
... 
>>> a = 2
>>> if (a = some_func()):
  File "<stdin>", line 1
    if (a = some_func()):
          ^
SyntaxError: invalid syntax
>>> 

Donc non.

Mise à jour: c'est possible (avec une syntaxe différente) en Python 3.8

Jason Hall
la source
35
ceci est intentionnellement interdit car Guido, dictateur bienveillant de python, les trouve inutiles et plus déroutants qu'utiles. C'est la même raison pour laquelle il n'y a pas d'opérateurs de post-incrémentation ou de pré-incrémentation (++).
Matt Boehm
4
il a permis l'ajout de l' attribution augmentée dans la version 2.0 car il x = x + 1nécessite un temps de recherche supplémentaire alors que x += 1c'était un peu plus rapide, mais je suis sûr qu'il n'a même pas aimé faire ça . :-)
wescpy
espérons que cela deviendra bientôt possible. Python est juste lent à évoluer
Nik O'Lai
4
"pourquoi ne pas l'essayer" - Parce que qui sait quelle pourrait être la syntaxe? Peut-être que OP a essayé cela et cela n'a pas fonctionné, mais cela ne signifie pas que la syntaxe n'est pas différente, ou qu'il n'y a pas de moyen de le faire qui n'est pas prévu
Levi H
57

MISE À JOUR - La réponse d'origine est vers le bas

Python 3.8 apportera PEP572

Résumé
Il s'agit d'une proposition pour créer un moyen d'assigner des variables dans une expression en utilisant la notation NAME: = expr. Une nouvelle exception, TargetScopeError est ajoutée, et il y a une modification à l'ordre d'évaluation.

https://lwn.net/Articles/757713/

Le «désordre PEP 572» a été le sujet d'une session 2018 du Sommet du langage Python dirigée par le dictateur bienveillant pour la vie (BDFL) Guido van Rossum. PEP 572 cherche à ajouter des expressions d'affectation (ou "affectations en ligne") au langage, mais il a vu une discussion prolongée sur plusieurs énormes threads sur la liste de diffusion python-dev - même après plusieurs tours sur les idées python. Ces fils étaient souvent controversés et étaient clairement volumineux au point que beaucoup les ont probablement simplement exclus. Lors du sommet, Van Rossum a donné un aperçu de la proposition de fonctionnalité, qu'il semble enclin à accepter, mais il a également voulu discuter de la façon d'éviter ce type d'explosion de fils à l'avenir.

https://www.python.org/dev/peps/pep-0572/#examples-from-the-python-standard-library

Exemples de la bibliothèque standard Python

site.py env_base n'est utilisé que sur ces lignes, placer son affectation sur if le déplace comme "en-tête" du bloc.

Actuel:

env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
    return env_base

Amélioré:

if env_base := os.environ.get("PYTHONUSERBASE", None):
    return env_base
_pydecimal.py

Évitez si et supprimez un niveau d'indentation.

Actuel:

if self._is_special:
    ans = self._check_nans(context=context)
    if ans:
        return ans

Amélioré:

if self._is_special and (ans := self._check_nans(context=context)):
    return ans

copy.py Le code semble plus régulier et évite plusieurs if imbriqués. (Voir l'annexe A pour l'origine de cet exemple.)

Actuel:

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(deep)copyable object of type %s" % cls)

Amélioré:

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(deep)copyable object of type %s" % cls)
datetime.py

tz n'est utilisé que pour s + = tz, déplacer son affectation à l'intérieur du if aide à montrer sa portée.

Actuel:

s = _format_time(self._hour, self._minute,
                 self._second, self._microsecond,
                 timespec)
tz = self._tzstr()
if tz:
    s += tz
return s

Amélioré:

s = _format_time(self._hour, self._minute,
                 self._second, self._microsecond,
                 timespec)
if tz := self._tzstr():
    s += tz
return s

sysconfig.py L'appel de fp.readline () dans la condition while et l'appel de .match () sur les lignes if rendent le code plus compact sans

ce qui rend la compréhension plus difficile.

Actuel:

while True:
    line = fp.readline()
    if not line:
        break
    m = define_rx.match(line)
    if m:
        n, v = m.group(1, 2)
        try:
            v = int(v)
        except ValueError:
            pass
        vars[n] = v
    else:
        m = undef_rx.match(line)
        if m:
            vars[m.group(1)] = 0

Amélioré:

while line := fp.readline():
    if m := define_rx.match(line):
        n, v = m.group(1, 2)
        try:
            v = int(v)
        except ValueError:
            pass
        vars[n] = v
    elif m := undef_rx.match(line):
        vars[m.group(1)] = 0

Simplification des compréhensions de liste Une compréhension de liste peut cartographier et filtrer efficacement en capturant la condition:

results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

De même, une sous-expression peut être réutilisée dans l'expression principale, en lui donnant un nom lors de la première utilisation:

stuff = [[y := f(x), x/y] for x in range(5)]

Notez que dans les deux cas, la variable y est liée dans la portée contenant (c'est-à-dire au même niveau que les résultats ou les trucs).

Capture des valeurs de condition Les expressions d'affectation peuvent être utilisées à bon escient dans l'en-tête d'une instruction if ou while:

# Loop-and-a-half
while (command := input("> ")) != "quit":
    print("You entered:", command)

# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
    print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

# Reading socket data until an empty string is returned
while data := sock.recv(8192):
    print("Received data:", data)

En particulier avec la boucle while, cela peut supprimer le besoin d'avoir une boucle infinie, une affectation et une condition. Il crée également un parallèle fluide entre une boucle qui utilise simplement un appel de fonction comme condition, et une qui l'utilise comme condition mais utilise également la valeur réelle.

Fork Un exemple du monde UNIX de bas niveau:

if pid := os.fork():
    # Parent code
else:
    # Child code

Réponse originale

http://docs.python.org/tutorial/datastructures.html

Notez qu'en Python, contrairement à C, l'affectation ne peut pas se produire à l'intérieur des expressions. Les programmeurs C peuvent se plaindre à ce sujet, mais cela évite une classe courante de problèmes rencontrés dans les programmes C: taper = dans une expression lorsque == était prévu.

regarde aussi:

http://effbot.org/pyfaq/why-can-ti-use-an-assignment-in-an-expression.htm

John La Rooy
la source
J'aime cette réponse car elle montre en fait pourquoi une telle "fonctionnalité" a pu être délibérément laissée en dehors de Python. Lors de l'enseignement de la programmation pour débutants, j'ai vu beaucoup de gens faire cette erreur if (foo = 'bar')en voulant tester la valeur defoo .
Jonathan Cross
2
@JonathanCross, Cette "fonctionnalité" va en fait être ajoutée en 3.8. Il est peu probable qu'il soit utilisé aussi modérément qu'il le devrait - mais au moins ce n'est pas une plaine=
John La Rooy
@JohnLaRooy: En regardant les exemples, je pense que "peu probable d'être utilisé avec parcimonie qu'il le devrait" était parfait; Sur les ~ 10 exemples, je trouve que seuls deux améliorent réellement le code. (À savoir, comme seule expression soit dans une condition while, pour éviter de dupliquer la ligne ou d'avoir la condition de boucle dans le corps, soit dans une chaîne elif pour éviter la nidification)
Aleksi Torhamo
39

Non, le BDFL n'aimait pas cette fonctionnalité.

D'où je suis assis, Guido van Rossum, "Dictateur bienveillant pour la vie", s'est battu pour garder Python aussi simple que possible. Nous pouvons chipoter avec certaines des décisions qu'il a prises - j'aurais préféré qu'il dise "Non Mais le fait qu'il n'y ait pas eu de comité de conception de Python, mais plutôt un "conseil consultatif" de confiance, basé en grande partie sur le mérite, filtrant à travers les sensibilités d' un concepteur, a produit un sacré langage sympa, à mon humble avis.

Kevin Little
la source
15
Facile? Cette fonctionnalité pourrait simplifier une partie de mon code car elle aurait pu le rendre plus compact et donc plus lisible. Maintenant, j'ai besoin de deux lignes là où j'en avais besoin d'une. Je n'ai jamais compris pourquoi Python avait rejeté les fonctionnalités d'autres langages de programmation pendant de nombreuses années (et souvent pour une très bonne raison). En particulier, cette fonctionnalité dont nous parlons ici est très, très utile.
Regis mai
6
Moins de code n'est pas toujours plus simple ou plus lisible. Prenons par exemple une fonction récursive. Son équivalent en boucle est souvent plus lisible.
FMF
1
Je n'aime pas la version C de celui-ci, mais il me manque vraiment quelque chose comme la rouille if letquand j'ai une chaîne if elif, mais j'ai besoin de stocker et d'utiliser la valeur de la condition dans chaque cas.
Thayne
1
Je dois dire que le code que j'écris maintenant (la raison pour laquelle j'ai recherché ce problème) est BEAUCOUP plus laid sans cette fonctionnalité. Au lieu d'utiliser if suivi de beaucoup d'autres ifs, je dois continuer à indenter le suivant if sous le dernier else.
MikeKulls
17

Pas directement, selon ma vieille recette - mais comme le dit la recette, il est facile de construire l'équivalent sémantique, par exemple si vous avez besoin de translittérer directement à partir d'un algorithme de référence codé C (avant de refactoriser vers Python plus idiomatique, bien sûr; -). C'est à dire:

class DataHolder(object):
    def __init__(self, value=None): self.value = value
    def set(self, value): self.value = value; return value
    def get(self): return self.value

data = DataHolder()

while data.set(somefunc()):
  a = data.get()
  # use a

BTW, une forme pythonique très idiomatique pour votre cas spécifique, si vous savez exactement quelle valeur falsifiée somefuncpeut renvoyer quand elle renvoie une valeur fausse (par exemple 0), est

for a in iter(somefunc, 0):
  # use a

donc dans ce cas précis, le refactoring serait assez simple ;-).

Si le retour pourrait être une sorte de valeur falsish (0, None, '', ...), une possibilité est:

import itertools

for a in itertools.takewhile(lambda x: x, iter(somefunc, object())):
    # use a

mais vous préférerez peut-être un simple générateur personnalisé:

def getwhile(func, *a, **k):
    while True:
      x = func(*a, **k)
      if not x: break
      yield x

for a in getwhile(somefunc):
    # use a
Alex Martelli
la source
Je voterais deux fois si je pouvais. C'est une excellente solution pour les moments où quelque chose comme ça est vraiment nécessaire. J'ai adapté votre solution à une classe regex Matcher, qui est instanciée une fois, puis .check () est utilisé dans l'instruction if et .result () utilisé dans son corps pour récupérer la correspondance, s'il y en avait une. Merci! :)
Teekin
16

Oui, mais uniquement à partir de Python 3.8 et au-delà.

La PEP 572 propose des expressions d'affectation et a déjà été acceptée.

Citant la partie syntaxe et sémantique du PEP:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

Dans votre cas particulier, vous pourrez écrire

if a := some_func():
    # Use a
Timgeb
la source
5

Non. L'affectation en Python est une instruction, pas une expression.

Ignacio Vazquez-Abrams
la source
Et Guido ne l'aurait pas fait autrement.
Mark Ransom
1
@MarkRansom Salut à tous Guido. Bien .. soupir.
StephenBoesch
@javadba le gars a eu raison beaucoup plus souvent qu'il a eu tort. J'apprécie qu'avoir une seule personne en charge de la vision aboutisse à une stratégie beaucoup plus cohérente que la conception en comité; Je peux comparer et contraster avec C ++ qui est mon pain et beurre principal.
Mark Ransom
Je sens que ruby ​​et scala (v langues différentes) font les choses bien plus que python: mais en tout cas, ce n'est pas le lieu ..
StephenBoesch
4

Grâce à la nouvelle fonctionnalité de Python 3.8, il sera possible de faire une telle chose à partir de cette version, bien que n'utilisant pas un =opérateur d'assignation de type Ada :=. Exemple tiré de la documentation:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match
Jean-François Fabre
la source
2

Vous pouvez définir une fonction pour effectuer l'affectation à votre place:

def assign(name, value):
    import inspect
    frame = inspect.currentframe()
    try:
        locals_ = frame.f_back.f_locals
    finally:
        del frame 
    locals_[name] = value
    return value

if assign('test', 0):
    print("first", test)
elif assign('xyz', 123):
    print("second", xyz)
Willem Hengeveld
la source
1

L'une des raisons pour lesquelles les affectations sont illégales dans les conditions est qu'il est plus facile de faire une erreur et d'attribuer Vrai ou Faux:

some_variable = 5

# This does not work
# if True = some_variable:
#   do_something()

# This only works in Python 2.x
True = some_variable

print True  # returns 5

En Python 3, True et False sont des mots-clés, donc plus de risque.

user2979916
la source
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: True, je ne pense pas que ce soit la raison
volcan
À peu près sûr que la plupart des gens mettent == Truedu bon côté de toute façon.
numbermaniac
1

L'opérateur d'affectation - également connu de manière informelle sous le nom d'opérateur de morse - a été créé le 28 février 2018 dans PEP572 .

Par souci d'exhaustivité, je posterai les parties pertinentes afin que vous puissiez comparer les différences entre 3.7 et 3.8:

3.7
---
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
test: or_test ['if' or_test 'else' test] | lambdef
test_nocond: or_test | lambdef_nocond
lambdef: 'lambda' [varargslist] ':' test
lambdef_nocond: 'lambda' [varargslist] ':' test_nocond
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*

3.8
---
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
namedexpr_test: test [':=' test]                         <---- WALRUS OPERATOR!!!
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
BPL
la source