Zuweisungsausdrücke, auch bekannt als Walrus-Operator.

Einführung

walrus

Dieser Abschnitt in unserem Python-Kurs erkundet den "Zuweisungsoperator", den viele liebevoll als "den Walross-Operator" bezeichnen. Man kann ihn also entweder als den "Walross-Operator" oder als den "Zuweisungsoperator" bezeichnen, und die Menschen in der Python-Programmiergemeinschaft werden im Allgemeinen verstehen, was man meint. Obwohl er erst in Python-Version 3.8 im Oktober 2019 eingeführt wurde, können wir ihn als eine relativ neue Ergänzung zur Sprache betrachten. Die Zuweisungsausdrücke wurden in "PEP 572"1 diskutiert, und das ist das, was über die Benennung geschrieben wurde:

During discussion of this PEP, the operator became informally known as "the walrus operator". The construct's formal name is "Assignment Expressions" (as per the PEP title), but they may also be referred to as "Named Expressions" (e.g. the CPython reference implementation uses that name internally).(2)

Wir werden den Zuweisungsoperator einführen, indem wir ihn zunächst mit einer einfachen Zuweisungsanweisung vergleichen. Eine einfache Zuweisungsanweisung kann auch durch einen Zuweisungsausdruck ersetzt werden, obwohl er umständlich aussieht und definitiv in diesem Fall nicht der beabsichtigte Anwendungsfall ist.

In [14]:
x = 5
# wir können dies auch so schreiben:
(x := 5)  # möglich aber nicht empfohlen
# Die Klammern sind unbedingt notwendig
Out[14]:
5

Zunächst mag man sich über die Klammern wundern, wenn man sich den Zuweisungsausdruck ansieht. Er ist nicht als Ersatz für die einfache "Zuweisungsanweisung" gedacht. Seine Hauptrolle besteht darin, innerhalb von Ausdrücken verwendet zu werden.

Der folgende Code zeigt einen geeigneten Anwendungsfall:

In [15]:
x = 4.56
z = (square := x**2) - (6.6 / square)
print(z)
20.476194644506

Obwohl man argumentieren könnte, dass der folgende Code möglicherweise noch klarer ist:

In [16]:
x = 4.56
square = x**2
z = square - (6.6 / square)
print(z)
20.476194644506

Hier ist ein weiteres Python-Codebeispiel, das ein ähnliches Szenario demonstriert. Zuerst mit dem Walross-Operator:

In [3]:
# Mit dem Walross-Operator

n = int(input("Bitte gebe eine Zahl ein: "))
if (square := n ** 2) > 3:
    print("Die Zahl ist größer als 3.")
    print(f"Das Quadrat von {n} ist {square}.")
Die Zahl ist größer als 3.
Das Quadrat von 4 ist 16.

Nun ohne den Walross-Operator:

In [18]:
n = int(input("Bitte gebe eine Zahl ein: "))
square= n ** 2
if square > 3:
    print("Die Zahl ist größer als 3.")
    print(f"Das Quadrat von {n} ist {square}.")
The number is greater than 3.
The square of 42 is 1764.

Vorteilhafte Anwendungen

Wir wollen nun einige Beispiele erkunden, in denen die Verwendung des Zuweisungsausdrucks wirklich vorteilhaft ist im Vergleich zu Code, der ihn nicht verwendet.

In [4]:
def f(x):
    return x + 4

numbers = [3, 7, 2, 9, 12]

odd_numbers = [result for x in numbers if (result := f(x)) % 2]
odd_numbers
Out[4]:
[7, 11, 13]

Man kann beobachten, dass, wenn man sich entscheidet, den Zuweisungsausdruck in der Listenabstraktion zu vermeiden, man die Funktion zweimal aufrufen müsste. Daher macht die Verwendung des Zuweisungsausdrucks den Code in diesem Kontext effizienter.

In [5]:
def f(x):
    return x + 4

numbers = [3, 7, 2, 9, 12]

odd_numbers = [f(x) for x in numbers if  f(x) % 2]
odd_numbers
Out[5]:
[7, 11, 13]

Anwendungsfall: Reguläre Ausdrücke

Es gibt auch einen großen Vorteil, wenn man reguläre Ausdrücke verwendet. Wie in unseren vorherigen Beispielen präsentieren wir erneut zwei ähnliche Code-Snippets, das erste ohne und das zweite mit dem Walross-Operator:

In [6]:
import re

txt = """Der Python-Kurs begann am 2022-02-4
der andere Kurs am 2022-01-24
nur ein Datum pro Zeile, wenn überhaupt
die Daten können auch in diesem Format vorliegen 2020/10/15
oder 20-10-04"""

for line in txt.split('\n'):
    date = re.search(r'(\d{2,4})[-/](\d{2})[-/](\d{2})', line)
    if date:
        year, month, day = date.groups()
        print(f"Found date: {year}-{month}-{day}")
Found date: 2022-01-24
Found date: 2020-10-15
Found date: 20-10-04

Now, the code using assignment expressions:

In [7]:
import re

txt = """Der Python-Kurs begann am 2022-02-4
der andere Kurs am 2022-01-24
nur ein Datum pro Zeile, wenn überhaupt
die Daten können auch in diesem Format vorliegen 2020/10/15
oder 20-10-04"""


# Split the text into lines and search for dates in each line
for line in txt.split('\n'):
    # Using the assignment expression to find and store the date match
    if (date := re.search(r'(\d{2,4})[-/](\d{2})[-/](\d{2})', line)):
        year, month, day = date.groups()
        print(f"Found date: {year}-{month}-{day}")
Found date: 2022-01-24
Found date: 2020-10-15
Found date: 20-10-04

Zuweisungsausdruck beim Dateilesen

Dateien mit fester Breite

Dateien, in denen jede Zeile die gleiche Länge hat, werden oft als "Dateien mit fester Breite" bezeichnet. In diesen Dateien ist jede Zeile (oder Datensatz) strukturiert, so dass sie eine vordefinierte Anzahl von Zeichen enthält, und die Felder innerhalb der Zeilen haben feste Positionen. Die Dateien können mit der read-Methode und dem Walross-Operator gelesen werden:

In [19]:
with open('python-kurse-info.txt') as fh:
    while ((data := fh.read(53)) != ''):
        print(data.rstrip())
Python Python-Kurs, Basics             Berlin     08
Python Kurs für Fortgeschrittene       Hamburg    06
Python OOP-Kurs                        Frankfurt  08
Python Python-Kurs, Basics             Freiburg   08
Python Kurs für Fortgeschrittene       München    97
Python OOP-Kurs                        Freiburg   12
Python Maschinelles Lernen             Stuttgart  06
Python OOP-Kurs	                       Zürich     08
Python Python-Kurs, Basics             Basel      08

Jetzt dasselbe im traditionellen Codierungsstil:

In [21]:
with open('python-kurse-info.txt') as fh:
    data = fh.read(53)
    while data:
        print(data.rstrip())
        data = fh.read(53)
Python Python-Kurs, Basics             Berlin     08
Python Kurs für Fortgeschrittene       Hamburg    06
Python OOP-Kurs                        Frankfurt  08
Python Python-Kurs, Basics             Freiburg   08
Python Kurs für Fortgeschrittene       München    97
Python OOP-Kurs                        Freiburg   12
Python Maschinelles Lernen             Stuttgart  06
Python OOP-Kurs	                       Zürich     08
Python Python-Kurs, Basics             Basel      08

Die Vorteile für das neueste Code-Snippet sind:

  • Es verwendet einen traditionelleren Codierungsstil, der für einige Entwickler möglicherweise vertrauter ist.
  • Es weist die Variable data explizit zu und verwendet sie, was den Code für diejenigen, die nicht mit Zuweisungsausdrücken vertraut sind, einfacher verständlich macht.

Ein bedeutender Nachteil dieses Ansatzes ist jedoch, dass er die explizite Zuweisung und Neuzuweisung der Variable data erfordert, was als weniger prägnant und etwas weniger elegant angesehen werden kann.

Verwendung von readline

Die meisten Menschen verwenden eine for-Schleife, um über eine Textdatei zeilenweise zu iterieren. Mit dem Walross-Operator können wir jedoch auch elegant durch einen Text mit der Methode readline gehen:

In [25]:
word_to_find = "Maschinelles"
with open('python-kurse-info.txt') as file:
    while (line := file.readline()):
        if word_to_find in line:
            print(line)
Python Maschinelles Lernen             Stuttgart  06

Code snippet using readline but not the assignment expression:

In [27]:
word_to_find = "Maschinelles"
with open('python-kurse-info.txt') as file:
    line = "darf nur nicht leer sein, deshalb dieser Text"
    while line:
        line = file.readline()
        if word_to_find in line:
            print(line)
Python Maschinelles Lernen             Stuttgart  06

Nun mit dem Walross-Operator:

In [31]:
word_to_find = "OOP-Kurs"
with open('python-kurse-info.txt') as file:
    while (line := file.readline()):
        if word_to_find in line:
            print(line.rstrip())
Python OOP-Kurs                        Frankfurt  08
Python OOP-Kurs                        Freiburg   12
Python OOP-Kurs	                       Zürich     08

Ein weiterer Anwendungsfall

Im Kapitel über while-Schleifen unseres Python-Tutorials hatten wir ein kleines Zahlenratespiel:

In [32]:
import random

untere_grenze, obere_grenze = 1, 20
zu_ratende_zahl = random.randint(untere_grenze, obere_grenze)
vermutung = 0
while vermutung != zu_ratende_zahl:
    vermutung = int(input("Neue Zahl: "))
    if vermutung > zu_ratende_zahl:
        print("Zahl zu groß")
    elif vermutung < zu_ratende_zahl:
        print("Zahl zu klein")
else:
    print("Herzlichen Glückwunsch. Du hast es geschafft!")
Zahl zu groß
Zahl zu groß
Zahl zu klein
Herzlichen Glückwunsch. Du hast es geschafft!

Wie Sie sehen können, mussten wir vermutung auf null initialisieren, um die Schleife betreten zu können. Wir können die Initialisierung direkt in der Schleifenbedingung mit einem Zuweisungsausdruck durchführen und den gesamten Code dadurch vereinfachen:

In [33]:
import random

untere_grenze, obere_grenze = 1, 20
zu_ratende_zahl = random.randint(untere_grenze, obere_grenze)

while (vermutung := int(input("Neue Zahl: "))) != zu_ratende_zahl:
    if vermutung > zu_ratende_zahl:
        print("Zahl zu groß")
    elif vermutung < zu_ratende_zahl:
        print("Zahl zu klein")
else:
    print("Herzlichen Glückwunsch. Du hast es geschafft!")
Zahl zu groß
Zahl zu klein
Zahl zu klein
Herzlichen Glückwunsch. Du hast es geschafft!

Kritik an dem Walrus-Operator in Python

Python-Walross

Wir haben zu Beginn dieser Seite erwähnt, dass einige Python-Programmierer schon lange auf dieses Konstrukt gewartet haben. Ein Grund, warum es nicht früher eingeführt wurde, war die Tatsache, dass es auch verwendet werden kann, um weniger lesbaren Code zu schreiben. Tatsächlich widerspricht die Anwendung des Walross-Operators mehreren Prinzipien, die im Zen von Python hervorgehoben werden. Um diese Verstöße zu erfassen, wollen wir uns den folgenden Szenarien zuwenden.

Im Zen von Python heißt es: "Explizit ist besser als implizit". Der Walross-Operator verstößt gegen diese Anforderung des Zen von Python. Dies bedeutet, dass explizite Operationen immer besser sind als implizite Operationen. Der Walross-Operator weist einer Variablen einen Wert zu und gibt den Wert implizit zurück. Daher steht er im Widerspruch zum Konzept von "Explizit ist besser als implizit".

Der folgende Python-Code-Schnipsel zeigt ein extremes Beispiel, das nicht empfohlen wird:

In [34]:
a, b, c = 1, 2, 3
x = 4
y = (c := (a := x*2.3) + (b := x*4.5 -3)) 

Was denken Sie über den folgenden Code, der den Python-Zuweisungsoperator innerhalb eines print-Funktionsaufrufs verwendet?

In [35]:
print((a := x*2.3) + (b := x*4.5 -3) + (x := 4))
28.2

Diese Python-Code-Schnipsel verstoßen sicherlich gegen zwei weitere Forderungen des Zen von Python:

  1. Schönheit ist besser als Hässlichkeit (im Original: Beautiful is Better Than Ugly)
  2. Komplexität ist besser als Kompliziertheit (original: Complex is Better Than Complicated)

Das Prinzip "Es sollte eine und vorzugsweise nur eine offensichtliche Art geben, es zu tun"3 aus dem Zen von Python betont die Bedeutung eines klaren und eindeutigen Ansatzes. Wenn wir die Möglichkeit haben, Zuweisungs- und Rückgabeoperationen in separate Anweisungen aufzuteilen, widerspricht die Wahl des weniger lesbaren und komplexeren Walross-Operators diesem Prinzip.

In [11]:
z = (x:=3) + (y:=4)

This is the preferred way to do itDies ist definitiv besser:

In [36]:
x, y = 3, 4
z = x + y

Footnotes

1 [PEP 572](https://peps.python.org/pep-0572/)

2 The purpose of this feature is not a new way to assign objects to variables, but it gives programmers a convenient way to assign variables in the middle of expressions. You might still be curious about the origin of the term "walrus operator." It's affectionately named this way because, with a touch of imagination, the operator's characters resemble the eyes and tusks of a walrus.

3 im Original: "There should be one and preferably only one obvious way to do it"