Können wir eine Aufgabe in einem Zustand haben?

88

Ist es möglich, eine Zuordnung in einem Zustand zu haben?

Zum Beispiel.

if (a=some_func()):
    # Use a
Vishal
quelle
Dass zwei Fragen dieselbe Antwort haben, bedeutet nicht, dass es sich um Duplikate handelt. Trivial, um sich selbst zu antworten, aber nicht trivial, um die Argumentation zu sehen, oder ob es einen Ausweg gibt.
Mehmet

Antworten:

107

Warum probieren Sie es nicht aus?

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

Also nein.

Update: Dies ist (mit unterschiedlicher Syntax) in Python 3.8 möglich

Jason Hall
quelle
34
Dies ist absichtlich verboten, da Guido, der wohlwollende Python-Diktator, sie für unnötig und eher verwirrend als nützlich hält. Es ist der gleiche Grund, warum es keine Post-Inkrement- oder Pre-Inkrement-Operatoren (++) gibt.
Matt Boehm
4
er hat die Zugabe von erlauben Augmented assigment in 2,0 weil x = x + 1erfordert zusätzliche Lookup - Zeit , während x += 1etwas schneller war, aber ich bin sicher , hat er nicht einmal , wie zu tun , dass viel. :-)
wescpy
hoffe, das wird bald möglich. Python entwickelt sich nur langsam
Nik O'Lai
54

UPDATE - Die ursprüngliche Antwort befindet sich ganz unten

Python 3.8 bringt PEP572

Zusammenfassung
Dies ist ein Vorschlag zum Erstellen einer Möglichkeit zum Zuweisen von Variablen innerhalb eines Ausdrucks unter Verwendung der Notation NAME: = Ausdruck. Eine neue Ausnahme, TargetScopeError, wurde hinzugefügt, und die Auswertungsreihenfolge wurde geändert.

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

Das "PEP 572-Durcheinander" war das Thema einer Sitzung des Python Language Summit 2018, die von dem wohlwollenden Diktator für das Leben (BDFL) Guido van Rossum geleitet wurde. PEP 572 versucht, der Sprache Zuweisungsausdrücke (oder "Inline-Zuweisungen") hinzuzufügen, hat jedoch eine längere Diskussion über mehrere große Threads auf der Python-Dev-Mailingliste gesehen - selbst nach mehreren Runden mit Python-Ideen. Diese Threads waren oft umstritten und eindeutig so umfangreich, dass viele sie wahrscheinlich gerade ausgeschaltet haben. Auf dem Gipfel gab Van Rossum einen Überblick über den Feature-Vorschlag, den er offenbar gerne annimmt, wollte aber auch diskutieren, wie diese Art von Fadenexplosion in Zukunft vermieden werden kann.

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

Beispiele aus der Python-Standardbibliothek

site.py env_base wird nur in diesen Zeilen verwendet und setzt seine Zuweisung auf das if, wenn es als "Header" des Blocks verschoben wird.

Aktuell:

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

Verbessert:

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

Vermeiden Sie verschachtelte if und entfernen Sie eine Einrückungsstufe.

Aktuell:

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

Verbessert:

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

copy.py Code sieht regelmäßiger aus und vermeidet mehrfach verschachtelte if. (Die Herkunft dieses Beispiels finden Sie in Anhang A.)

Aktuell:

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)

Verbessert:

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 wird nur für s + = tz verwendet, wobei die Zuordnung innerhalb des if verschoben wird, um den Umfang anzuzeigen.

Aktuell:

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

Verbessert:

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

sysconfig.py Wenn Sie fp.readline () in der while-Bedingung aufrufen und .match () in den if-Zeilen aufrufen, wird der Code ohne kompakter

macht es schwieriger zu verstehen.

Aktuell:

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

Verbessert:

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

Vereinfachen des Listenverständnisses Ein Listenverständnis kann effizient erfasst und gefiltert werden, indem die Bedingung erfasst wird:

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

Ebenso kann ein Unterausdruck innerhalb des Hauptausdrucks wiederverwendet werden, indem ihm bei der ersten Verwendung ein Name gegeben wird:

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

Beachten Sie, dass in beiden Fällen die Variable y im enthaltenden Bereich gebunden ist (dh auf derselben Ebene wie Ergebnisse oder Material).

Erfassen von Bedingungswerten Zuweisungsausdrücke können im Header einer if- oder while-Anweisung effektiv verwendet werden:

# 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)

Insbesondere bei der while-Schleife kann dies die Notwendigkeit einer Endlosschleife, einer Zuweisung und einer Bedingung beseitigen. Es wird auch eine glatte Parallele zwischen einer Schleife erstellt, die einfach einen Funktionsaufruf als Bedingung verwendet, und einer Schleife, die diesen als Bedingung verwendet, aber auch den tatsächlichen Wert verwendet.

Gabel Ein Beispiel aus der UNIX-Welt auf niedriger Ebene:

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

Ursprüngliche Antwort

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

Beachten Sie, dass in Python im Gegensatz zu C die Zuweisung nicht innerhalb von Ausdrücken erfolgen kann. C-Programmierer mögen darüber meckern, aber es vermeidet eine häufige Klasse von Problemen, die in C-Programmen auftreten: Geben Sie = in einen Ausdruck ein, wenn == beabsichtigt war.

siehe auch:

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

John La Rooy
quelle
Ich mag diese Antwort, weil sie tatsächlich darauf hinweist, warum ein solches "Feature" in Python absichtlich weggelassen wurde. Beim Unterrichten von Anfängerprogrammen habe ich viele gesehen, die diesen Fehler gemacht haben, if (foo = 'bar')als sie beabsichtigten, den Wert von zu testen foo.
Jonathan Cross
2
@ JonathanCross, Diese "Funktion" wird tatsächlich in 3.8 hinzugefügt. Es ist unwahrscheinlich, dass es so sparsam eingesetzt wird, wie es sollte - aber zumindest ist es keine Ebene=
John La Rooy
@JohnLaRooy: Wenn ich mir die Beispiele anschaue, denke ich, dass "es unwahrscheinlich ist, dass sie so sparsam eingesetzt werden, wie es sollte", genau richtig war. Von den ~ 10 Beispielen finde ich, dass nur zwei den Code tatsächlich verbessern. (Als einziger Ausdruck entweder in einer Weile, um zu vermeiden, dass die Linie dupliziert wird oder die Schleifenbedingung im Körper vorliegt, oder in einer Elifkette, um ein Verschachteln zu vermeiden)
Aleksi Torhamo,
39

Nein, die BDFL mochte diese Funktion nicht.

Von meinem Platz aus hat Guido van Rossum, "Wohlwollender Diktator fürs Leben", hart dafür gekämpft, Python so einfach wie möglich zu halten. Wir können uns mit einigen seiner Entscheidungen streiten - ich hätte es vorgezogen, wenn er "Nein" gesagt hätte Aber die Tatsache, dass es kein Komitee gab, das Python entwarf, sondern einen vertrauenswürdigen "Beirat", der größtenteils auf Verdiensten basiert und die Sensibilität eines Designers filtert, hat meiner Meinung nach eine verdammt schöne Sprache hervorgebracht, IMHO.

Kevin Little
quelle
15
Einfach? Diese Funktion könnte einen Teil meines Codes vereinfachen, da sie ihn kompakter und daher lesbarer hätte machen können. Jetzt brauche ich zwei Zeilen, wo ich früher eine brauchte. Ich habe nie verstanden, warum Python Funktionen abgelehnt hat, die andere Programmiersprachen seit vielen Jahren haben (und oft aus einem sehr guten Grund). Besonders diese Funktion, über die wir hier sprechen, ist sehr, sehr nützlich.
Regis
6
Weniger Code ist nicht immer einfacher oder besser lesbar. Nehmen Sie zum Beispiel eine rekursive Funktion. Das Loop-Äquivalent ist oft besser lesbar.
FMF
1
Ich mag die C-Version nicht, aber ich vermisse es wirklich, etwas wie Rost zu haben, if letwenn ich eine If-Elif-Kette habe, aber ich muss den Wert der Bedingung in jedem Fall speichern und verwenden.
Thayne
1
Ich muss sagen, dass der Code, den ich gerade schreibe (der Grund, warum ich dieses Problem gesucht habe), ohne diese Funktion VIEL hässlicher ist. Anstatt if zu verwenden, gefolgt von vielen anderen ifs, muss ich das nächste if unter dem letzten else einrücken.
MikeKulls
16

Nicht direkt nach diesem alten Rezept von mir - aber wie das Rezept sagt, ist es einfach, das semantische Äquivalent zu erstellen, z. B. wenn Sie direkt von einem C-codierten Referenzalgorithmus transliterieren müssen (natürlich vor dem Refactoring zu mehr idiomatischem Python; -). Dh:

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

Übrigens, eine sehr idiomatische pythonische Form für Ihren speziellen Fall, wenn Sie genau wissen, welcher falsche Wert zurückkehren somefunckann, wenn er einen falschen Wert zurückgibt (z. B. 0)

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

in diesem speziellen Fall wäre das Refactoring also ziemlich einfach ;-).

Wenn die Rückkehr könnte jede Art von falsish Wert (0, None, '', ...), eine Möglichkeit ist:

import itertools

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

Möglicherweise bevorzugen Sie jedoch einen einfachen benutzerdefinierten Generator:

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
quelle
Ich würde das zweimal abstimmen, wenn ich könnte. Dies ist eine großartige Lösung für Zeiten, in denen so etwas wirklich benötigt wird. Ich habe Ihre Lösung an eine Regex-Matcher-Klasse angepasst, die einmal instanziiert wird. Anschließend wird .check () in der if-Anweisung und .result () in ihrem Text verwendet, um die Übereinstimmung abzurufen, falls vorhanden. Vielen Dank! :)
Teekin
15

Ja, aber nur ab Python 3.8.

PEP 572 schlägt Zuweisungsausdrücke vor und wurde bereits akzeptiert.

Zitieren des Syntax- und Semantikteils des 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]

In Ihrem speziellen Fall können Sie schreiben

if a := some_func():
    # Use a
timgeb
quelle
5

Die Zuweisung in Python ist eine Anweisung, kein Ausdruck.

Ignacio Vazquez-Abrams
quelle
Und Guido würde es nicht anders haben.
Mark Ransom
1
@ MarkRansom Alle begrüßen Guido. Richtig ... seufz.
Javadba
@javadba der Typ hat viel öfter recht gehabt als er sich geirrt hat. Ich schätze, dass eine einzige Person, die für die Vision verantwortlich ist, zu einer viel kohärenteren Strategie führt als die Gestaltung durch das Komitee. Ich kann mit C ++ vergleichen und kontrastieren, das mein Hauptbrot und Butter ist.
Mark Ransom
Ich fühle, dass sowohl Ruby als auch Scala (in verschiedenen Sprachen) es deutlich besser machen als Python: aber auf jeden Fall ist hier nicht der
richtige
4

Dank der neuen Funktion von Python 3.8 wird es möglich sein, so etwas ab dieser Version zu tun, obwohl nicht der =Ada-ähnliche Zuweisungsoperator verwendet wird :=. Beispiel aus den Dokumenten:

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

Sie können eine Funktion definieren, die die Zuweisung für Sie übernimmt:

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
quelle
1

Einer der Gründe, warum Zuweisungen unter bestimmten Bedingungen illegal sind, besteht darin, dass es einfacher ist, einen Fehler zu machen und Richtig oder Falsch zuzuweisen:

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

In Python 3 sind Wahr und Falsch Schlüsselwörter, also kein Risiko mehr.

user2979916
quelle
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: True Ich glaube nicht, dass dies der Grund ist
Vulkan
Ich bin mir ziemlich sicher, dass die meisten Leute == Truesowieso auf die richtige Seite gehen.
Numbermaniac
1

Der Zuweisungsoperator - auch informell als Walrossoperator bezeichnet - wurde am 28. Februar 2018 in PEP572 erstellt .

Der Vollständigkeit halber werde ich die relevanten Teile veröffentlichen, damit Sie die Unterschiede zwischen 3.7 und 3.8 vergleichen können:

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
quelle