PEP 572 (Zuweisungsausdrücke in Python 3.8)

Hallo Habr. Dieses Mal werden wir uns PEP 572 ansehen, in dem es um Zuweisungsausdrücke geht. Wenn Sie dem Operator ": =" immer noch skeptisch gegenüberstehen oder die Regeln für seine Verwendung nicht vollständig verstehen, ist dieser Artikel genau das Richtige für Sie. Hier finden Sie viele Beispiele und Antworten auf die Frage: "Warum ist das so?" Dieser Artikel hat sich als so vollständig wie möglich herausgestellt. Wenn Sie wenig Zeit haben, schauen Sie sich den Abschnitt an, den ich geschrieben habe. Zu Beginn werden die wichtigsten „Thesen“ für eine bequeme Arbeit mit Zuordnungsausdrücken gesammelt. Verzeihen Sie mir im Voraus, wenn Sie Fehler finden (schreiben Sie mir darüber, ich werde es beheben). Lasst uns beginnen:

PEP 572 - Zuweisungsausdrücke

Pep572
Titel:Zuweisungsausdrücke
Autoren:Chris Angelico <rosuav at gmail.com>, Tim Peters <tim.peters at gmail.com>, Guido van Rossum <guido at python.org>
Diskussion:doc-sig bei python.org
Status:Akzeptiert
Eine Art:Standard
Erstellt:28.02.2018
Python-Version:3.8
Beitragsgeschichte:28-Feb-2018, 02-Mar-2018, 23-Mar-2018, 04-Apr-2018, 17-Apr-2018, 25-Apr-2018, 09-Jul-2018, 05-Aug-2019
Erlaubnis zur Übernahme des Standards:mail.python.org/pipermail/python-dev/2018-July/154601.html (mit VPN für eine lange Zeit, aber es wird geladen )
Inhalt


Anmerkung


Diese Konvention wird über die Möglichkeit der Zuweisung innerhalb von Ausdrücken unter Verwendung der neuen Notation NAME: = expr sprechen.

Im Rahmen der Innovationen wurde das Verfahren zur Berechnung der Wörterbuchgeneratoren (Wörterbuchverständnis) aktualisiert. Dadurch wird sichergestellt, dass der Schlüsselausdruck vor dem Wertausdruck ausgewertet wird (auf diese Weise können Sie den Schlüssel an eine Variable binden und die erstellte Variable dann für die Berechnung des dem Schlüssel entsprechenden Werts wiederverwenden).

Während einer Diskussion dieses PEP wurde dieser Operator inoffiziell als Walross-Operator bekannt. Der formale Name des Konstrukts lautet "Zuweisungsausdruck" (gemäß der Überschrift PEP: Zuweisungsausdrücke), kann jedoch als "Benannte Ausdrücke" bezeichnet werden. Beispielsweise verwendet die Referenzimplementierung in CPython genau diesen Namen.

Rechtfertigung


Die Benennung ist ein wichtiger Bestandteil der Programmierung, mit dem Sie anstelle eines längeren Ausdrucks einen „beschreibenden“ Namen verwenden und die Wiederverwendung von Werten vereinfachen können. Derzeit kann dies nur in Form von Anweisungen erfolgen, wodurch diese Operation beim Generieren von Listen (Listenverständnis) sowie in anderen Ausdrücken nicht verfügbar ist.

Darüber hinaus kann das Benennen von Teilen eines großen Ausdrucks beim interaktiven Debuggen hilfreich sein, indem Tools zum Anzeigen von Eingabeaufforderungen und Zwischenergebnissen bereitgestellt werden. Ohne die Möglichkeit, die Ergebnisse verschachtelter Ausdrücke zu erfassen, müssen Sie den Quellcode ändern. Mit den Zuweisungsausdrücken müssen Sie jedoch nur einige "Marker" der Form "name: = expression" einfügen. Dies eliminiert unnötiges Refactoring und verringert daher die Wahrscheinlichkeit unbeabsichtigter Codeänderungen während des Debuggens (eine häufige Ursache für Heisenbugs sind Fehler, die die Eigenschaften des Codes während des Debuggens ändern und möglicherweise unerwartet in der Produktion auftreten), und dieser Code ist für einen anderen verständlicher an den Programmierer.

Die Bedeutung von echtem Code


Während der Entwicklung dieses PEP konzentrierten sich viele Menschen (sowohl Befürworter als auch Kritiker) zu sehr auf Spielzeugbeispiele einerseits und zu komplexe Beispiele andererseits.

Die Gefahr von Spielzeugbeispielen besteht in zweierlei Hinsicht: Sie sind oft zu abstrakt, um jemanden sagen zu lassen: „Oh, das ist unwiderstehlich“, und sie werden auch leicht mit den Worten „Das würde ich niemals schreiben“ abgelehnt. Die Gefahr zu komplexer Beispiele besteht darin, dass sie Kritikern eine bequeme Umgebung bieten, in der vorgeschlagen wird, diese Funktionalität zu entfernen („Das ist zu verwirrend“, sagen solche Leute).

Solche Beispiele sind jedoch sinnvoll: Sie helfen, die beabsichtigte Semantik zu verdeutlichen. Daher werden wir im Folgenden einige davon nennen. Um jedoch zu überzeugen , müssen Beispiele auf basierenechter Code, der geschrieben wurde, ohne über diesen PEP nachzudenken. Das heißt, der Code, der Teil einer wirklich nützlichen Anwendung ist (kein Unterschied: ob groß oder klein). Tim Peters hat uns sehr geholfen, indem er sich seine persönlichen Repositories angesehen und Beispiele für den von ihm geschriebenen Code ausgewählt hat, der (seiner Meinung nach) verständlicher wäre, wenn sie (ohne Fanatismus) mit Zuweisungsausdrücken umgeschrieben würden. Sein Fazit lautet: Die aktuellen Änderungen würden eine bescheidene, aber offensichtliche Verbesserung einiger Teile seines Codes bewirken.

Ein weiteres Beispiel für echten Code ist die indirekte Beobachtung, wie Programmierer Kompaktheit schätzen. Guido van Rossum überprüfte die Dropbox-Codebasis und fand Hinweise darauf, dass Programmierer lieber weniger Codezeilen schreiben als ein paar kleine Ausdrücke.

Beispiel: Guido hat mehrere veranschaulichende Punkte gefunden, wenn ein Programmierer einen Unterausdruck wiederholt (wodurch das Programm verlangsamt wird), aber eine zusätzliche Codezeile speichert. Zum Beispiel anstatt zu schreiben:

match = re.match(data)
group = match.group(1) if match else None

Programmierer bevorzugten diese Option:

group = re.match(data).group(1) if re.match(data) else None

Hier ist ein weiteres Beispiel, das zeigt, dass Programmierer manchmal bereit sind, mehr Arbeit zu leisten, um die „vorherige Ebene“ der Einrückung beizubehalten:

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

Dieser Code berechnet Muster2, auch wenn Muster1 bereits übereinstimmt (in diesem Fall wird die zweite Unterbedingung niemals erfüllt). Daher ist die folgende Lösung effektiver, aber weniger attraktiv:

match1 = pattern1.match(data)
if match1:
    result = match1.group(1)
else:
    match2 = pattern2.match(data)
    if match2:
        result = match2.group(2)
    else:
        result = None

Syntax und Semantik


In den meisten Fällen, in denen Python beliebige Ausdrücke verwendet, können Sie jetzt Zuweisungsausdrücke verwenden. Sie haben die Form NAME: = Ausdruck, wobei Ausdruck ein beliebiger gültiger Python-Ausdruck ist, mit Ausnahme des nicht parenthesierten Tupels, und NAME der Bezeichner ist. Der Wert eines solchen Ausdrucks stimmt mit dem Original überein, ein zusätzlicher Effekt ist jedoch die Zuweisung eines Werts zum Zielobjekt:

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

Ausnahmefällen


Es gibt mehrere Stellen, an denen Zuweisungsausdrücke nicht zulässig sind, um Mehrdeutigkeiten oder Verwirrung unter den Benutzern zu vermeiden:

  • Zuweisungsausdrücke, die nicht in Klammern stehen, sind auf der „oberen“ Ebene verboten:

    y := f(x)  # 
    (y := f(x))  # ,   

    Diese Regel erleichtert dem Programmierer die Auswahl zwischen einem Zuweisungsoperator und einem Zuweisungsausdruck. Es gibt keine syntaktische Situation, in der beide Optionen gleichwertig sind.
  • . :

    y0 = y1 := f(x)  # 
    y0 = (y1 := f(x))  # ,   

    . :

    foo(x = y := f(x))  # 
    foo(x=(y := f(x)))  # ,     

    , .
  • . :

    def foo(answer = p := 42):  # 
        ...
    def foo(answer=(p := 42)):  # Valid, though not great style
        ...

    , (. , «» ).
  • , . :

    def foo(answer: p := 42 = 5):  # 
        ...
    def foo(answer: (p := 42) = 5):  # ,  
        ...

    : , "=" ":=" .
  • -. :

    (lambda: x := 1) # 
    lambda: (x := 1) # ,  
    (x := lambda: 1) # 
    lambda line: (m := re.match(pattern, line)) and m.group(1) # Valid

    - , ":=". . , , () , .
  • f- . :

    >>> f'{(x:=10)}'  # ,  
    '10'
    >>> x = 10
    >>> f'{x:=10}'    # ,  ,  '=10'
    '        10'

    , , f-, . f- ":" . , f- . , .


Ein Zuweisungsausdruck führt keinen neuen Bereich ein. In den meisten Fällen muss der Bereich, in dem die Variable erstellt wird, nicht erläutert werden: Sie ist aktuell. Wenn die Variable zuvor die nichtlokalen oder globalen Schlüsselwörter verwendet hat, berücksichtigt der Zuweisungsausdruck dies. Nur Lambda (eine anonyme Definition einer Funktion) wird für diese Zwecke als separater Bereich betrachtet.

Es gibt einen Sonderfall: Ein Zuweisungsausdruck, der in Generatoren von Listen, Mengen, Wörterbüchern oder in den „Ausdrücken von Generatoren“ selbst (im Folgenden zusammenfassend als „Generatoren“ (Verständnis) bezeichnet) vorkommt, bindet die Variable unter Berücksichtigung des Globab-Modifikators an den Bereich, den der Generator enthält oder nicht global, falls vorhanden.

Für diesen Sonderfall gibt es zwei Gründe. Erstens können wir das „Mitglied“ bequem in den Ausdrücken any () und all () erfassen, zum Beispiel:

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

if all((nonblank := line).strip() == '' for line in lines):
    print("All lines are blank")
else:
    print("First non-blank line:", nonblank)

Zweitens bietet es eine kompakte Möglichkeit, eine Variable von einem Generator zu aktualisieren, zum Beispiel:

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

Der Name der Variablen aus dem Zuweisungsausdruck kann jedoch nicht mit dem Namen übereinstimmen, der bereits in Generatoren von der for-Schleife zum Iterieren verwendet wird. Die Nachnamen sind lokal für den Generator, in dem sie erscheinen. Es wäre inkonsistent, wenn sich die Zuweisungsausdrücke auch auf den Bereich innerhalb des Generators beziehen würden.

Zum Beispiel ist [i: = i + 1 für i in Bereich (5)] nicht gültig: Die for-Schleife bestimmt, dass i lokal für den Generator ist, aber der Teil „i: = i + 1“ besteht darauf, dass i eine Variable von außen ist Umfang Aus dem gleichen Grund funktionieren die folgenden Beispiele nicht:


[[(j := j) for i in range(5)] for j in range(5)] # 
[i := 0 for i, j in stuff]                       # 
[i+1 for i in (i := stuff)]                      # 

Obwohl es technisch möglich ist, für solche Fälle eine konsistente Semantik zuzuweisen, ist es schwierig zu bestimmen, ob die Art und Weise, wie wir diese Semantik verstehen, in Ihrem realen Code funktioniert. Aus diesem Grund stellt die Referenzimplementierung sicher, dass solche Fälle SyntaxError auslösen und nicht mit undefiniertem Verhalten ausgeführt werden, abhängig von der jeweiligen Hardwareimplementierung. Diese Einschränkung gilt auch dann, wenn ein Zuweisungsausdruck niemals ausgeführt wird:

[False and (i := 0) for i, j in stuff]     # 
[i for i, j in stuff if True or (j := 1)]  # 

# [.  . - ""   
# ,       
# ,    ,   ]

Für den Generatorkörper (den Teil vor dem ersten Schlüsselwort "für") und den Filterausdruck (den Teil nach dem "wenn" und vor einem verschachtelten "für") gilt diese Einschränkung ausschließlich für Variablennamen, die gleichzeitig als iterative Variablen verwendet werden. Wie bereits erwähnt, führen Lambda-Ausdrücke einen neuen expliziten Funktionsumfang ein und können daher ohne zusätzliche Einschränkungen in Ausdrücken von Generatoren verwendet werden. [ca. wieder, außer in solchen Fällen: [i für i im Bereich (2, (Lambda: (s: = 2) ())]]

Aufgrund von Designbeschränkungen in der Referenzimplementierung (der Symboltabellenanalysator kann nicht erkennen, ob die Namen aus dem linken Teil des Generators in dem verbleibenden Teil verwendet werden, in dem sich der iterierbare Ausdruck befindet), sind Zuweisungsausdrücke als Teil von iterable (in dem Teil nach jedem "in" und "vollständig verboten) vor jedem nachfolgenden Schlüsselwort "if" oder "for"). Das heißt, alle diese Fälle sind inakzeptabel:

[i+1 for i in (j := stuff)]                    # 
[i+1 for i in range(2) for j in (k := stuff)]  # 
[i+1 for i in [j for j in (k := stuff)]]       # 
[i+1 for i in (lambda: (j := stuff))()]        # 

Eine weitere Ausnahme tritt auf, wenn ein Zuweisungsausdruck in Generatoren verwendet wird, die sich im Bereich einer Klasse befinden. Wenn bei Verwendung der oben genannten Regeln eine im Bereich neu gemessene Klasse erstellt werden soll, ist ein solcher Zuweisungsausdruck ungültig und führt zu einem SyntaxError:

class Example:
    [(j := i) for i in range(5)]  # 

(Der Grund für die letzte Ausnahme ist der implizite Umfang der vom Generator erstellten Funktion. Derzeit gibt es keinen Laufzeitmechanismus für Funktionen, die auf eine Variable im Bereich der Klasse verweisen, und wir möchten keinen solchen Mechanismus hinzufügen. Wenn dieses Problem jemals gelöst wird, In diesem Fall wird dieser Sonderfall (möglicherweise) aus der Spezifikation der Zuweisungsausdrücke entfernt. Beachten Sie, dass dieses Problem auch dann auftritt, wenn Sie eine Variable früher im Bereich der Klasse erstellt haben und versuchen, sie mit einem Zuweisungsausdruck aus dem Generator zu ändern.) Beispiele dazu finden

Sie in Anhang B. In Generatoren gefundene Zuweisungsausdrücke werden in äquivalenten Code konvertiert.

Relative Priorität: =


Der Operator: = wird nach Möglichkeit in allen syntaktischen Positionen stärker als das Komma gruppiert, aber schwächer als alle anderen Operatoren, einschließlich oder und und nicht, und bedingte Ausdrücke (A wenn C sonst B). Wie aus dem obigen Abschnitt „Ausnahmefälle“ hervorgeht, funktionieren Zuweisungsausdrücke niemals auf derselben „Ebene“ wie die klassische Zuweisung =. Wenn eine andere Reihenfolge der Operationen erforderlich ist, verwenden Sie Klammern.

Der Operator: = kann direkt beim Aufrufen des Positionsarguments einer Funktion verwendet werden. Dies funktioniert jedoch nicht direkt im Argument. Einige Beispiele, die verdeutlichen, was technisch zulässig und was nicht möglich ist:

x := 0 # 

(x := 0) #  

x = y := 0 # 

x = (y := 0) #  

len(lines := f.readlines()) # 

foo(x := 3, cat='vector') # 

foo(cat=category := 'vector') # 

foo(cat=(category := 'vector')) #  

Die meisten der oben genannten „gültigen“ Beispiele werden für die Verwendung in der Praxis nicht empfohlen, da Personen, die Ihren Quellcode schnell scannen, dessen Bedeutung möglicherweise nicht richtig verstehen. In einfachen Fällen ist dies jedoch zulässig:

# Valid
if any(len(longline := line) >= 100 for line in lines):
    print("Extremely long line:", longline)

In diesem PEP wird empfohlen, unbedingt immer Leerzeichen um: = zu setzen, ähnlich der Empfehlung von PEP 8 für = für die klassische Zuweisung. (Der Unterschied zur letzten Empfehlung besteht darin, dass Leerzeichen um = verboten werden, mit denen Schlüsselargumente an die Funktion übergeben werden.)

Ändern Sie die Reihenfolge der Berechnungen.


Um eine genau definierte Semantik zu haben, erfordert diese Vereinbarung, dass das Bewertungsverfahren klar definiert ist. Technisch ist dies keine neue Anforderung. Python hat bereits die Regel, dass Unterausdrücke normalerweise von links nach rechts ausgewertet werden. Zuweisungsausdrücke machen diese „Nebenwirkungen“ jedoch deutlicher, und wir schlagen eine Änderung in der aktuellen Berechnungsreihenfolge vor:

  • In Wörterbuchgeneratoren {X: Y für ...} wird Y derzeit vor X ausgewertet. Wir empfehlen, dies so zu ändern, dass X vor Y berechnet wird. (In einem klassischen Dikt wie {X: Y} sowie in dikt ((X, Y) für ...) Dies wurde bereits implementiert. Daher müssen Wörterbuchgeneratoren diesen Mechanismus einhalten.)


Unterschiede zwischen Zuweisungsausdrücken und Zuweisungsanweisungen.


Am wichtigsten ist, dass ": =" ein Ausdruck ist , was bedeutet, dass er in Fällen verwendet werden kann, in denen Anweisungen ungültig sind, einschließlich Lambda-Funktionen und Generatoren. Im Gegensatz dazu Zuweisung Ausdrücke nicht unterstützen die erweiterte Funktionalität , die in verwendet werden kann Zuordnung Anweisungen :

  • Die kaskadierende Zuweisung wird nicht direkt unterstützt

    x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • Separate "Ziele" werden mit Ausnahme des einfachen Variablennamens NAME nicht unterstützt:

    # No equivalent
    a[i] = x
    self.rest = []
  • Funktionalität und Priorität "um" Kommas unterscheiden sich:

    x = 1, 2  # Sets x to (1, 2)
    (x := 1, 2)  # Sets x to 1
  • Auspacken und Verpackungswerte haben keine „reine“ Äquivalenz oder werden überhaupt nicht unterstützt

    # Equivalent needs extra parentheses
    loc = x, y  # Use (loc := (x, y))
    info = name, phone, *rest  # Use (info := (name, phone, *rest))
    
    # No equivalent
    px, py, pz = position
    name, phone, email, *other_info = contact
  • Inline-Typanmerkungen werden nicht unterstützt:

    # Closest equivalent is "p: Optional[int]" as a separate declaration
    p: Optional[int] = None
  • Es gibt keine verkürzte Form von Operationen:

    total += tax  # Equivalent: (total := total + tax)

Spezifikationsänderungen während der Implementierung


Die folgenden Änderungen wurden basierend auf unseren Erfahrungen und zusätzlichen Analysen nach dem ersten Schreiben dieses PEP und vor der Veröffentlichung von Python 3.8 vorgenommen:

  • Um die Konsistenz mit anderen ähnlichen Ausnahmen zu gewährleisten und keinen neuen Namen einzuführen, der für Endbenutzer möglicherweise nicht geeignet ist, wurde die ursprünglich vorgeschlagene Unterklasse von TargetScopeError for SyntaxError entfernt und auf den üblichen SyntaxError reduziert. [3]
  • Aufgrund von Einschränkungen beim Parsen der CPython-Zeichentabelle löst die Referenzimplementierung des Zuweisungsausdrucks einen SyntaxError für alle Verwendungen innerhalb von Iteratoren aus. Bisher trat diese Ausnahme nur auf, wenn der Name der zu erstellenden Variablen mit dem bereits im iterativen Ausdruck verwendeten übereinstimmte. Dies kann überarbeitet werden, wenn ausreichend überzeugende Beispiele vorliegen, die zusätzliche Komplexität jedoch für rein „hypothetische“ Anwendungsfälle ungeeignet erscheint.

Beispiele


Beispiele für Python-Standardbibliotheken


site.py


env_base wird nur in einer Bedingung verwendet, daher kann die Zuweisung in if als "Header" eines logischen Blocks platziert werden.

  • Aktueller Code:
    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
  • Verbesserter Code:
    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base

_pydecimal.py


Sie können verschachtelte ifs vermeiden und dadurch eine Einrückungsstufe entfernen.

  • Aktueller Code:
    if self._is_special:
        ans = self._check_nans(context=context)
        if ans:
            return ans
  • Verbesserter Code:
    if self._is_special and (ans := self._check_nans(context=context)):
        return ans

copy.py


Der Code sieht klassischer aus und vermeidet auch das mehrfache Verschachteln von bedingten Anweisungen. (Weitere Informationen zum Ursprung dieses Beispiels finden Sie in Anhang A.)

  • Aktueller Code:
    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)
  • Verbesserter Code:

    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. Bewegen Sie es nach innen, wenn dies dazu beiträgt, den logischen Verwendungsbereich aufzuzeigen.

  • Aktueller Code:

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

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

sysconfig.py


Das Aufrufen von fp.readline () als "Bedingung" in der while-Schleife (sowie das Aufrufen der .match () -Methode) in der if-Bedingung macht den Code kompakter, ohne sein Verständnis zu erschweren.

  • Aktueller Code:

    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
  • Verbesserter Code:

    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 Sie Listengeneratoren


Jetzt kann der Listengenerator effektiv gefiltert werden, indem die Bedingung "erfasst" wird:

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

Danach kann die Variable in einem anderen Ausdruck wiederverwendet werden:

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

Bitte beachten Sie erneut, dass sich die Variable y in beiden Fällen im selben Bereich befindet wie das Ergebnis und die Daten der Variablen.

Werte in Bedingungen erfassen


Zuweisungsausdrücke können effektiv unter den Bedingungen einer if- oder while-Anweisung 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 kann durch diesen Ansatz die Notwendigkeit beseitigt werden, eine Endlosschleife, Zuweisung und Bedingungsprüfung zu erstellen. Außerdem können Sie eine glatte Parallele zwischen einem Zyklus zeichnen, der einen Funktionsaufruf als Bedingung verwendet, und einem Zyklus, der nicht nur die Bedingung überprüft, sondern auch den tatsächlichen Wert verwendet, den die Funktion in Zukunft zurückgibt.

Gabel


Ein Beispiel aus der Low-Level-Welt von UNIX: [ca. Fork () ist ein Systemaufruf unter Unix-ähnlichen Betriebssystemen, der einen neuen Unterprozess relativ zum übergeordneten Betriebssystem erstellt.]

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

Abgelehnte Alternativen


Im Allgemeinen sind ähnliche Vorschläge in der Python-Community weit verbreitet. Im Folgenden finden Sie eine Reihe alternativer Syntaxen für Zuweisungsausdrücke, die zu spezifisch sind, um sie zu verstehen, und die zugunsten der oben genannten abgelehnt wurden.

Ändern des Bereichs für Generatoren


In einer früheren Version dieses PEP wurde vorgeschlagen, die Bereichsregeln für Generatoren geringfügig zu ändern, um sie für die Verwendung im Klassenbereich besser geeignet zu machen. Diese Vorschläge würden jedoch zu einer Inkompatibilität führen und wurden daher abgelehnt. Daher konnte sich dieser PEP nur auf Zuweisungsausdrücke konzentrieren.

Alternative Schreibweisen


Im Allgemeinen haben die vorgeschlagenen Zuweisungsausdrücke dieselbe Semantik, sind jedoch unterschiedlich geschrieben.

  1. EXPR als NAME:

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

    EXPR as NAME import, except with, (, ).

    ( , «with EXPR as VAR» EXPR VAR, EXPR.__enter__() VAR.)

    , ":=" :
    • , if f(x) as y , ​​ if f x blah-blah, if f(x) and y.
    • , as , , :
      • import foo as bar
      • except Exc as var
      • with ctxmgr() as var

      , as if while , as « » .
    • «»
      • NAME = EXPR
      • if NAME := EXPR

      .
  2. EXPR -> NAME

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

    , R Haskell, . ( , - y < — f (x) Python, - .) «as» , import, except with, . Python ( ), ":=" ( Algol-58) .
  3. «»

    stuff = [[(f(x) as .y), x/.y] for x in range(5)] # with "as"
    stuff = [[(.y := f(x)), x/.y] for x in range(5)] # with ":="

    . Python, , .
  4. where: :

    value = x**2 + 2*x where:
        x = spam(1, 4, 7, q)

    ( , «»). , «» ( with:). . PEP 3150, ( given: ).
  5. TARGET from EXPR:

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

    Es ist weniger wahrscheinlich, dass diese Syntax mit anderen in Konflikt steht als as (es sei denn, Sie zählen die Erhöhung von Exc aus Exc-Konstrukten), sie ist jedoch ansonsten mit diesen vergleichbar. Anstelle einer Parallele mit mit expr als Ziel: (was nützlich sein kann, aber auch verwirrend sein kann), weist diese Option überhaupt keine Parallelen zu irgendetwas auf, wird aber überraschenderweise besser in Erinnerung behalten.


Sonderfälle in bedingten Aussagen


Einer der häufigsten Anwendungsfälle für Zuweisungsausdrücke sind die if- und while-Anweisungen. Anstelle einer allgemeineren Lösung verbessert die Verwendung von as die Syntax dieser beiden Anweisungen, indem ein Mittel zum Erfassen des zu vergleichenden Werts hinzugefügt wird:

if re.search(pat, text) as match:
    print("Found:", match.group(0))

Dies funktioniert gut, aber NUR, wenn die gewünschte Bedingung auf der "Richtigkeit" des Rückgabewerts basiert. Daher ist diese Methode in bestimmten Fällen wirksam (Überprüfung auf reguläre Ausdrücke, Lesen von Sockets, Rückgabe einer leeren Zeichenfolge am Ende der Ausführung) und in komplexeren Fällen (z. B. wenn die Bedingung f (x) <0 ist und Sie möchten) völlig nutzlos Speichern Sie den Wert von f (x)). Dies ist auch bei Listengeneratoren nicht sinnvoll.

Vorteile : Keine syntaktischen Mehrdeutigkeiten. Nachteile : Auch wenn Sie es nur in if / while-Anweisungen verwenden, funktioniert es nur in einigen Fällen gut.

Sonderfälle bei Generatoren


Ein weiterer häufiger Anwendungsfall für Zuweisungsausdrücke sind Generatoren (list / set / dict und genexps). Wie oben wurden Vorschläge für spezifische Lösungen gemacht.

  1. wo, lassen oder gegeben:

    stuff = [(y, x/y) where y = f(x) for x in range(5)]
    stuff = [(y, x/y) let y = f(x) for x in range(5)]
    stuff = [(y, x/y) given y = f(x) for x in range(5)]

    Diese Methode führt zu einem Unterausdruck zwischen der for-Schleife und dem Hauptausdruck. Außerdem wird ein zusätzliches Sprachschlüsselwort eingeführt, das zu Konflikten führen kann. Von den drei Optionen, wo ist die sauberste und lesbar, aber potenzielle Konflikte existieren nach wie vor (zum Beispiel SQLAlchemy und numpy ihre wo Methoden sowie tkinter.dnd.Icon in der Standardbibliothek).
  2. mit NAME = EXPR:

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

    , , with. . , «» for. C, , . : « «with NAME = EXPR:» , ?»
  3. with EXPR as NAME:

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

    , as, . , for. with

Unabhängig von der gewählten Methode wird durch eine for-Schleife ein scharfer semantischer Unterschied zwischen Generatoren und ihren bereitgestellten Versionen eingeführt. Es wäre unmöglich, den Zyklus in einen Generator zu packen, ohne die Phase der Erstellung der Variablen zu verarbeiten. Das einzige Schlüsselwort, das für diese Aufgabe neu ausgerichtet werden könnte, ist das Wort mit . Dies führt jedoch zu einer unterschiedlichen Semantik in verschiedenen Teilen des Codes. Dies bedeutet, dass Sie ein neues Schlüsselwort erstellen müssen. Dies ist jedoch mit hohen Kosten verbunden.

Niedrigere Bedienerpriorität


Der Operator: = hat zwei logische Prioritäten. Oder es sollte eine möglichst niedrige Priorität haben (auf dem Niveau des Zuweisungsoperators). Oder es sollte Vorrang vor Vergleichsoperatoren haben. Wenn Sie die Priorität zwischen Vergleichsoperatoren und arithmetischen Operationen platzieren (um genau zu sein: etwas niedriger als bitweises ODER), können Sie in den meisten Fällen während und während der Verwendung auf Klammern verzichten, da es wahrscheinlicher ist, dass Sie den Wert von etwas vorher beibehalten möchten wie der Vergleich darauf durchgeführt wird:

pos = -1
while pos := buffer.find(search_term, pos + 1) >= 0:
    ...

Sobald find () -1 zurückgibt, endet die Schleife. Wenn: = die Operanden so frei wie = bindet, wird das Ergebnis von find () zuerst im Vergleichsoperator "erfasst" und gibt normalerweise True oder False zurück, was weniger nützlich ist.

Obwohl dieses Verhalten in der Praxis in vielen Situationen praktisch wäre, wäre es schwieriger zu erklären. Und so können wir sagen, dass "der Operator: = sich genauso verhält wie der übliche Zuweisungsoperator". Das heißt, die Priorität für: = wurde so nah wie möglich am Operator = gewählt (außer dass: = eine höhere Priorität als das Komma hat).

Sie geben rechts Kommas


Einige Kritiker argumentieren, dass Zuweisungsausdrücke Tupel ohne zusätzliche Klammern erkennen sollten, damit die beiden Einträge gleichwertig sind:

(point := (x, y))
(point := x, y)

(In der aktuellen Version des Standards entspricht der letzte Datensatz dem Ausdruck ((Punkt: = x), y).)

Es ist jedoch logisch, dass in dieser Situation der Zuweisungsausdruck im Funktionsaufruf auch eine niedrigere Priorität als das Komma hat wäre die folgende verwirrende Äquivalenz:

foo (x: = 1, y)
foo (x: = (1, y))

Und wir bekommen den einzigen weniger verwirrenden Ausweg: Machen Sie den Operator: = zu einer niedrigeren Priorität als das Komma.

Immer Klammern erforderlich


Es wurde immer vorgeschlagen, die Zuweisungsausdrücke in Klammern zu setzen. Dies würde uns viele Unklarheiten ersparen. In der Tat werden häufig Klammern benötigt, um den gewünschten Wert zu extrahieren. In den folgenden Fällen erschien uns das Vorhandensein von Klammern jedoch eindeutig überflüssig:

# Top level in if
if match := pattern.match(line):
    return match.group(1)

# Short call
len(lines := f.readlines())

Häufige Einwände


Warum nicht einfach die Zuweisungsanweisungen in Ausdrücke verwandeln?


C und ähnliche Sprachen definieren den Operator = als Ausdruck und nicht als Anweisung, wie dies Python tut. Dies ermöglicht die Zuweisung in vielen Situationen, einschließlich an Orten, an denen Variablen verglichen werden. Die syntaktischen Ähnlichkeiten zwischen if (x == y) und if (x = y) widersprechen ihrer stark unterschiedlichen Semantik. Daher führt dieser PEP den Operator ein: =, um ihre Unterschiede zu verdeutlichen.

Warum mit Mühe Zuweisung Ausdrücke , wenn Zuordnung Anweisungen existieren ?


Diese beiden Formen haben unterschiedliche Flexibilität. Der Operator: = kann in einem größeren Ausdruck verwendet werden, und im Operator = kann er von der "Familie der Mini-Operatoren" vom Typ "+ =" verwendet werden. Mit = können Sie Werte nach Attributen und Indizes zuweisen.

Warum nicht den lokalen Bereich nutzen und die Verschmutzung von Namespaces verhindern?


Frühere Versionen dieses Standards enthielten einen realen lokalen Bereich (auf eine Anweisung beschränkt) für Zuweisungsausdrücke, um Namensverlust und Verschmutzung des Namespace zu verhindern. Trotz der Tatsache, dass dies in einigen Situationen einen gewissen Vorteil brachte, erschwert es in vielen anderen die Aufgabe, und die Vorteile sind nicht durch die Vorteile des bestehenden Ansatzes gerechtfertigt. Dies geschieht im Interesse der Einfachheit der Sprache. Sie brauchen diese Variable nicht mehr? Es gibt eine Lösung: Löschen Sie die Variable mit dem Schlüsselwort del oder fügen Sie ihrem Namen einen unteren Unterstrich hinzu.

(Der Autor möchte Guido van Rossum und Christophe Groth für ihre Vorschläge danken, den PEP-Standard in diese Richtung voranzutreiben. [2])

Stilempfehlungen


Da Zuweisungsausdrücke manchmal mit einem Zuweisungsoperator gleichwertig verwendet werden können, stellt sich die Frage, was noch bevorzugt wird. In Übereinstimmung mit anderen Stilkonventionen (wie PEP 8) gibt es zwei Empfehlungen:

  1. Wenn Sie beide Zuweisungsoptionen verwenden können, bevorzugen Sie Operatoren. Sie drücken Ihre Absichten am deutlichsten aus.
  2. Wenn die Verwendung von Zuweisungsausdrücken zu Mehrdeutigkeiten in der Ausführungsreihenfolge führt, schreiben Sie den Code mit dem klassischen Operator neu.

Vielen Dank


Die Autoren dieses Standards möchten Nick Coghlan und Steven D'Aprano für ihre bedeutenden Beiträge zu diesem PEP sowie den Mitgliedern der Python Core Mentorship für ihre Hilfe bei der Implementierung danken.

Anhang A: Schlussfolgerungen von Tim Peters


Hier ist ein kurzer Aufsatz, den Tim Peters zu diesem Thema geschrieben hat.

Ich mag den "verwirrten" Code nicht und mag es auch nicht, konzeptionell nicht verwandte Logik in eine Zeile zu setzen. Also zum Beispiel anstelle von:

i = j = count = nerrors = 0

Ich schreibe lieber:

i = j = 0
count = 0
nerrors = 0

Daher denke ich, dass ich mehrere Stellen finden werde, an denen ich Zuweisungsausdrücke verwenden möchte. Ich möchte nicht einmal über ihre Verwendung in Ausdrücken sprechen, die bereits auf die Hälfte des Bildschirms ausgedehnt sind. In anderen Fällen kann Folgendes auftreten:

mylast = mylast[1]
yield mylast[0]

Deutlich besser als das:

yield (mylast := mylast[1])[0]

Diese beiden Codes haben völlig unterschiedliche Konzepte und es wäre verrückt, sie zu mischen. In anderen Fällen erschwert das Kombinieren logischer Ausdrücke das Verständnis des Codes. Beispiel: Umschreiben:

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

In einer kürzeren Form haben wir die „Logik“ verloren. Sie müssen verstehen, wie dieser Code funktioniert. Mein Gehirn will das nicht:

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

Solche Fälle sind jedoch selten. Die Aufgabe, das Ergebnis zu erhalten, ist sehr verbreitet, und „dünn ist besser als dicht“ bedeutet nicht, dass „fast leer ist besser als dünn“ [ca. ein Verweis auf Zen Python]. Zum Beispiel habe ich viele Funktionen, die None oder 0 zurückgeben, um zu sagen: "Ich habe nichts Nützliches, aber da dies häufig vorkommt, möchte ich Sie nicht mit Ausnahmen belästigen." Tatsächlich wird dieser Mechanismus auch in regulären Ausdrücken verwendet, die None zurückgeben, wenn keine Übereinstimmungen vorhanden sind. Daher in diesem Beispiel viel Code:

result = solution(xs, n)
if result:
    # use result

Ich finde die folgende Option verständlicher und natürlich lesbarer:

if result := solution(xs, n):
    # use result

Anfangs legte ich nicht viel Wert darauf, aber eine so kurze Konstruktion erschien so oft, dass es mich bald ärgerte, dass ich sie nicht verwenden konnte. Es überrascht mich! [ca. Anscheinend wurde dies geschrieben, bevor Python 3.8 offiziell veröffentlicht wurde.]

Es gibt andere Fälle, in denen Zuweisungsausdrücke wirklich "schießen". Anstatt noch einmal in meinem Code zu stöbern, gab Kirill Balunov ein gutes Beispiel für die Funktion copy () aus der Standardbibliothek copy.py:

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)

Die immer größer werdende Einrückung ist irreführend: Immerhin ist die Logik flach: Der erste erfolgreiche Test „gewinnt“:

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)

Die einfache Verwendung von Zuweisungsausdrücken ermöglicht es der visuellen Struktur des Codes, die „Ebene“ der Logik hervorzuheben. Aber die immer größer werdende Einrückung macht es implizit.

Hier ist ein weiteres kleines Beispiel aus meinem Code, das mich sehr gefreut hat, weil ich die intern verknüpfte Logik in eine Zeile setzen und die lästige „künstliche“ Einrückungsstufe entfernen konnte. Dies ist genau das, was ich von der if-Anweisung will und es erleichtert das Lesen. Der folgende Code:

diff = x - x_base
if diff:
    g = gcd(diff, n)
    if g > 1:
        return g

Wurde zu:

if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
    return g

In den meisten Zeilen, in denen Variablen zugewiesen werden, würde ich keine Zuweisungsausdrücke verwenden. Aber dieses Design ist so häufig, dass es immer noch viele Orte gibt, an denen ich diese Gelegenheit nutzen würde. In den letzten Fällen habe ich ein wenig gewonnen, wie sie oft erschienen sind. Im verbleibenden Teil führte dies zu mittleren oder großen Verbesserungen. Daher würde ich Zuweisungsausdrücke viel häufiger als ein Dreifach verwenden, aber viel seltener als eine erweiterte Zuweisung [ca. kurze Optionen: * =, / =, + = usw.].

Numerisches Beispiel


Ich habe ein anderes Beispiel, das mich früher beeindruckt hat.

Wenn alle Variablen positive ganze Zahlen sind und die Variable a größer als die n-te Wurzel von x ist, gibt dieser Algorithmus die "niedrigere" Rundung der n-ten Wurzel von x zurück (und verdoppelt ungefähr die Anzahl der exakten Bits pro Iteration):

while a > (d := x // a**(n-1)):
    a = ((n-1)*a + d) // n
return a

Es ist nicht klar warum, aber eine solche Variante des Algorithmus ist weniger offensichtlich als eine Endlosschleife mit einer bedingten Verzweigungsunterbrechung (eineinhalb Schleifen). Es ist auch schwierig, die Richtigkeit dieser Implementierung zu beweisen, ohne sich auf eine mathematische Aussage („arithmetisches Mittel - geometrische Mittelwertungleichung“) zu stützen und einige nicht triviale Dinge darüber zu wissen, wie sich die verschachtelten Rundungsfunktionen nach unten verhalten. Aber hier liegt das Problem bereits in der Mathematik und nicht in der Programmierung.

Und wenn Sie das alles wissen, ist die Option mit Zuweisungsausdrücken sehr einfach zu lesen, wie ein einfacher Satz: "Überprüfen Sie die aktuelle" Vermutung "und wenn sie zu groß ist, reduzieren Sie sie", und die Bedingung ermöglicht es Ihnen, den Zwischenwert sofort aus der Schleifenbedingung zu speichern. Meiner Meinung nach ist die klassische Form schwerer zu verstehen:

while True:
    d = x // a**(n-1)
    if a <= d:
        break
    a = ((n-1)*a + d) // n
return a

Anhang B: Ein Grobcode-Interpreter für Generatoren


In diesem Anhang wird versucht, die Regeln zu klären (obwohl nicht angegeben), nach denen eine Variable in Generatorausdrücken erstellt werden soll. Für einige anschauliche Beispiele zeigen wir den Quellcode, bei dem der Generator durch eine äquivalente Funktion in Kombination mit einem „Gerüst“ ersetzt wird.

Da [x für ...] der Liste entspricht (x für ...), verlieren die Beispiele nicht ihre Allgemeingültigkeit. Und da diese Beispiele nur zur Verdeutlichung der allgemeinen Regeln dienen sollen, erheben sie keinen Anspruch auf Realität.

Hinweis: Generatoren werden jetzt durch die Erstellung verschachtelter Generatorfunktionen implementiert (ähnlich den in diesem Anhang angegebenen). Die Beispiele zeigen den neuen Teil, der die entsprechende Funktionalität für die Arbeit mit dem Bereich der Zuweisungsausdrücke hinzufügt (z. B., wenn die Zuweisung in einem Block ausgeführt wurde, der den externesten Generator enthält). Um die „Typinferenz“ zu vereinfachen, berücksichtigen diese veranschaulichenden Beispiele nicht, dass Zuweisungsausdrücke optional sind (sie berücksichtigen jedoch den Umfang der im Generator erstellten Variablen).

Erinnern wir uns zunächst daran, welcher Code für Generatoren ohne Zuweisungsausdrücke „unter der Haube“ generiert wird:

  • Quellcode (EXPR verwendet am häufigsten die VAR-Variable):

    def f():
        a = [EXPR for VAR in ITERABLE]
  • Der konvertierte Code (machen wir uns keine Sorgen um Namenskonflikte):

    def f():
        def genexpr(iterator):
            for VAR in iterator:
                yield EXPR
        a = list(genexpr(iter(ITERABLE)))


Fügen wir einen einfachen Zuweisungsausdruck hinzu.

  • Quelle:

    def f():
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Konvertierter Code:

    def f():
        if False:
            TARGET = None  # Dead code to ensure TARGET is a local variable
        def genexpr(iterator):
            nonlocal TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Fügen wir nun die globale TARGET-Anweisung zur Deklaration der Funktion f () hinzu.

  • Quelle:

    def f():
        global TARGET
        a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Konvertierter Code:

    def f():
        global TARGET
        def genexpr(iterator):
            global TARGET
            for VAR in iterator:
                TARGET = EXPR
                yield TARGET
        a = list(genexpr(iter(ITERABLE)))

Oder umgekehrt, fügen wir der Deklaration der Funktion f () nichtlokales ZIEL hinzu.

  • Quelle:

    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            a = [TARGET := EXPR for VAR in ITERABLE]
    
  • Konvertierter Code:

    def g():
        TARGET = ...
        def f():
            nonlocal TARGET
            def genexpr(iterator):
                nonlocal TARGET
                for VAR in iterator:
                    TARGET = EXPR
                    yield TARGET
            a = list(genexpr(iter(ITERABLE)))

Lassen Sie uns zum Schluss zwei Generatoren einsetzen.

  • Quelle:

    def f():
        a = [[TARGET := i for i in range(3)] for j in range(2)]
        # I.e., a = [[0, 1, 2], [0, 1, 2]]
        print(TARGET)  # prints 2
    
  • Konvertierter Code:

    def f():
        if False:
            TARGET = None
        def outer_genexpr(outer_iterator):
            nonlocal TARGET
            def inner_generator(inner_iterator):
                nonlocal TARGET
                for i in inner_iterator:
                    TARGET = i
                    yield i
            for j in outer_iterator:
                yield list(inner_generator(range(3)))
        a = list(outer_genexpr(range(2)))
        print(TARGET)

Anhang C: Keine Änderungen in der Umfangssemantik


Beachten Sie, dass sich die Umfangssemantik in Python nicht geändert hat. Der Umfang lokaler Funktionen wird zur Kompilierungszeit noch festgelegt und hat zur Laufzeit einen unbegrenzten Zeitumfang (Closure). Beispiel:

a = 42
def f():
    # `a` is local to `f`, but remains unbound
    # until the caller executes this genexp:
    yield ((a := i) for i in range(3))
    yield lambda: a + 100
    print("done")
    try:
        print(f"`a` is bound to {a}")
        assert False
    except UnboundLocalError:
        print("`a` is not yet bound")

Dann:

>>> results = list(f()) # [genexp, lambda]
done
`a` is not yet bound
# The execution frame for f no longer exists in CPython,
# but f's locals live so long as they can still be referenced.
>>> list(map(type, results))
[<class 'generator'>, <class 'function'>]
>>> list(results[0])
[0, 1, 2]
>>> results[1]()
102
>>> a
42

Verweise


  1. Nachweis der Konzeptumsetzung
  2. Diskussion der Semantik von Zuweisungsausdrücken (VPN ist eng aber geladen)
  3. Diskussion von TargetScopeError in PEP 572 (ähnlich wie im vorherigen geladen)

Urheberrechte ©


Dieses Dokument wurde öffentlich zugänglich gemacht.

Quelle: github.com/python/peps/blob/master/pep-0572.rst

Mein Teil


Lassen Sie uns zunächst zusammenfassen:
  • Damit die Menschen nicht versuchen , die semantische Dualität zu entfernen, gibt es an vielen „klassischen“ Stellen, an denen sowohl „=“ als auch „: =“ verwendet werden können, Einschränkungen. Daher sollte der Operator :: = häufig in Klammern gesetzt werden. Diese Fälle müssen im Abschnitt über die grundlegende Verwendung überprüft werden .
  • Die Priorität von Zuweisungsausdrücken ist etwas höher als die eines Kommas. Aus diesem Grund werden während der Zuweisung keine Tupel gebildet. Es ermöglicht auch die Verwendung des Operators: =, wenn Argumente an eine Funktion übergeben werden.
  • , , , . . lambda , «» .
  • : ,
  • , .
  • / .
  • , .

Am Ende möchte ich sagen, dass mir der neue Operator gefallen hat. Es ermöglicht Ihnen, flacheren Code unter Bedingungen zu schreiben, Listen zu filtern und (endlich) die gleiche, einsame Zeile zu entfernen, bevor if. Wenn Benutzer Zuweisungsausdrücke für ihren beabsichtigten Zweck verwenden, ist dies ein sehr praktisches Werkzeug, das die Lesbarkeit und Schönheit des Codes verbessert (obwohl dies für jede funktionale Sprache gesagt werden kann ...).

All Articles