Zuweisungsausdrücks alias Walross-Operator

Einführung

walrus

Die Python-Version 3.8 brachte eine neue Funktion mit sich, auf die einige Python-Programmierer schon lange gewartet hatten. Eingeführt wurde eine neue Möglichkeit Objekten Variablen zuzuweisen, nämlich der := Operator. Er bietet Programmierenden eine bequeme Möglichkeit, Variablen in der Mitte von Ausdrücken zuzuweisen. Wenn man sich die Zeichen mit ein wenig Phantasie ansieht, kann man eine Ähnlichkeit mit den Augen und Stoßzähnen eines Walrosses erkennen, weshalb dieser Operator auch liebevoll der Walross-Operator genannt wird.

Die Zuweisungsausdrücke wurden in [PEP 572] (https://www.python.org/dev/peps/pep-0572/) diskutiert, und dies ist, was über die Benennung geschrieben wurde:

Während der Diskussion über dieses PEP wurde der Operator informell als "der Walross-Operator" bekannt. Der formale Name des Konstrukts ist Zuweisungsausdrücke (englisch "Assignment Expressions") (gemäß dem PEP-Titel), aber sie können auch als benamte Ausdrücke (englisch "Named Expressions" ) bezeichnet werden (z.B. verwendet die CPython-Referenzimplementierung intern diesen Namen)

Englischer Originaltext: Wir werden diese Art der Zuweisung in diesem Teil unseres Python-Tutorials vorstellen.

Eine einfache Zuweisung kann auch durch einen Zuweisungsausdruck ersetzt werden, auch wenn dies unbeholfen aussieht und definitiv nicht der beabsichtigte Anwendungsfall ist: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).

Wir werden diese Art der Zuweisung in diesem Teil von unserem Python-Kurse vorstellen.

Eine einfache Zuweisung kann auch durch einen Zuweisungsausdruck ersetzt werden, auch wenn dies unbeholfen aussieht und definitiv nicht der beabsichtigte Anwendungsfall ist. Man sollte unbedingt beachten, dass die Klammern in unten stehendem Beispiel unbedingt notwendig sind:

In [1]:
x = 5
# kann auch wie folgt geschrieben werden:
(x := 5)  # gültig, aber nicht empfohlen
Out[1]:
5

Schauen wir uns nun ein kleines Code-Beispiel an, das nur traditionelle Zuweisungen verwendet:

In [2]:
txt = 'Python-lernen macht Spaß!'
ideal_length = 22

n = len(txt)
if n == ideal_length:
    print(f'Die Länge {n} ist ideal!')
else:
    print(f'Die Länge {n} ist nicht ideal!')
Die Länge 25 ist nicht ideal!

Wie schreiben obiges Python-Beispiel um, indem wir den neuen Walross-Operator verwenden:

In [3]:
txt = 'Python-lernen macht Spaß!'
ideal_length = 22

if (n := len(txt)) == ideal_length:
    print(f'Die Länge {n} ist ideal!')
else:
    print(f'Die Länge {n} ist nicht ideal!')
Die Länge 25 ist nicht ideal!

Okay, einige werden vielleicht sagen, dass dies nicht sehr beeindruckend ist und die erste Version sogar noch lesbarer sei. Deshalb wollen wir nun einen Blick auf einen nützlicheren Anwendungsfall werfen.

Nützliche Anwendungen der Zuweisungsausdrücke

Listen-Abstraktion (list comprehension)

Im Folgenden benutzen wir den Walross-Operator in einer Listenabstraktion:

In [59]:
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[59]:
[7, 11, 13]

Die obige Implementierung ist effizienter als eine Listenabstraktion ohne den Zuweisungsausdruck, da wir die Funktion nämlich zweimal aufrufen müssen:

In [4]:
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[4]:
[7, 11, 13]

Reguläre Ausdrücke

Bei der Verwendung regulärer Ausdrücke birgt der Walross-Operator ebenfalls einen großen Vorteil. Der Textstring macht keinen großen Sinn. Es geht um Anfangs- und Enddaten von Python-Kursen. Diese Datumsangaben kommen in verschiedenen Formaten vor:

In [5]:
import re

txt = """Die Python-Schulung begann am 2023-03-20 
sie endete am 2023-03-24
nur ein Datum pro Zeile, wenn überhaupt
die Daten können auch in diesem Format sein 2023/03/15
oder 23-02-04"""

for line in txt.split('\n'):
    if (date := re.search(r'(\d{2,4})[-/](\d{2})[-/](\d{2})', line)):
        year, month, day = date.groups()
        print(year, month, day)
2023 03 20
2023 03 24
2023 03 15
23 02 04

Lesen von Dateien mit fester Zeilenlänge

Die Datei 'python_kurse_daten.txt' enthält Zeilen fester Länge, d.h. jede Zeile ist 57 Zeichen lang. Jede Zeile enthält einen Kurstitel und Daten für den Beginn und das Ende des Kurses. Die Datei sieht wie folgt aus:

Python-Kurs: Basics                2023-06-19 2023-06-23
Java-Kurs: Basics                  2023-06-26 2023-06-30
Python-Kurs: Machine Learning      2023-07-03 2023-07-07
Python-Kurs: OOP                   2023-07-24 2023-07-28

Der folgende Python-Code schält diese INformationen aus den Zeilen. Wir lesen jeweils 57 Zeichen ein und weisen das Ergebnis mittels Walross-Operator der Variablen data zu:

In [28]:
with open('python_kurse_daten.txt') as fh:
    while ((data := fh.read(57)) != ''):
        kurs = data[:35].rstrip()
        anfang = data[35:46]
        ende = data[46:].rstrip()
        print(kurs)
        print(anfang)
        print(ende)
Python-Kurs: Basics
2023-06-19 
2023-06-23
Java-Kurs: Basics
2023-06-26 
2023-06-30
Python-Kurs: Machine Learning
2023-07-03 
2023-07-07
Python-Kurs: OOP
2023-07-24 
2023-07-28

Weiteres Beispie mit while-Schleife

Im Kapitel über while-Schleifen unseres Python-Tutorials haben wir ein kleines Zahlen-Ratespiel vorgestellt:

In [ ]:
import random

lower_bound, upper_bound = 1, 20
to_be_guessed = random.randint(lower_bound, upper_bound)
guess = 0
while guess != to_be_guessed:
    guess = int(input("Neue Zahl: "))
    if guess > to_be_guessed:
        print("Zahl zu groß")
    elif guess < to_be_guessed:
        print("Zahl zu klein")
else:
    print("Glückwunsch! Du hast es geschafft!")

Wie man sehen kann, mussten wir 'guess' auf Null initialisieren, um in die Schleife einsteigen zu können. Wir können die Initialisierung direkt in der Schleifenbedingung mit einem Zuweisungsausdruck durchführen und so den gesamten Code vereinfachen:

In [29]:
import random

lower_bound, upper_bound = 1, 20
to_be_guessed = random.randint(lower_bound, upper_bound)

while (guess := int(input("New number: "))) != to_be_guessed:
    if guess > to_be_guessed:
        print("Number too large")
    elif guess < to_be_guessed:
        print("Number too small")
else:
    print("Congratulations. You made it!")
Number too small
Congratulations. You made it!

Auf die Spitze getrieben:

Wir haben am Anfang dieser Seite gesagt, dass einige Python-Programmierer dieses Konstrukt schon lange herbeigesehnt haben. Ein Grund, warum es nicht früher eingeführt wurde, war die Tatsache, dass man damit auch Code schreiben kann, der weniger lesbar ist, wenn man dies Möglichkeit zu extensiv verwendet. Der folgende Codeschnipsel zeigt ein solches Extrembeispiel, dessen Verwendung nicht zu empfehlen ist:

In [30]:
a, b, c = 1, 2, 3
x = 4
y = (c := (a := x*2.3) + (b := x*4.5 -3)) 
In [31]:
y
Out[31]:
24.2
In [32]:
c
Out[32]:
24.2
In [25]:
a
Out[25]:
9.2