Ga naar hoofdinhoud

Pythons walrus-operator: de toewijzingsexpressie (tutorial)

Pythons walrus-operator := laat je binnen een expressie een waarde aan een variabele toewijzen, waardoor toewijzing en gebruik in één stap worden gecombineerd.
· 8 min lezen

Verken met AI

Python 3.8 introduceerde een nieuwe operator aan de taal. De officiële naam is de toewijzingsexpressie en hij gebruikt de operator :=.

Toch wordt hij vaker de walrus-operator genoemd, omdat hij lijkt op de slagtanden van een walrus!

Bij het toevoegen van deze operator ontstond er, voor Python-begrippen, best wat controverse, en het gebruik ervan is nog steeds relatief beperkt. Toch is de toewijzingsexpressie nu onderdeel van Python en kan die in sommige situaties handig zijn.

In deze tutorial bekijken we hoe Pythons walrus-operator werkt en in welke situaties je hem kunt gebruiken.

Wat is Pythons walrus-operator

De toewijzingsexpressie, die de operator := gebruikt, lijkt op de meer gebruikelijke toewijzingsinstructie, die de operator = gebruikt. Er is echter een belangrijk verschil tussen beide.

Een statement is een eenheid code die een actie uitvoert. Een expressie is een statement dat geëvalueerd kan worden tot een waarde. Een expressie retourneert dus een waarde, terwijl statements die geen expressie zijn geen waarde teruggeven.

De REPL van Python (Read, Evaluate, Print, Loop) toont de waarde van een expressie zodra die geëvalueerd is. Bij het uitvoeren van een statement dat geen expressie is, wordt echter geen waarde getoond:

>>> import random  # Not an expression
>>> 10 * 2  # An expression
20

De import-instructie evalueert niet tot een waarde. 10 * 2 is daarentegen een expressie die wel tot een waarde evalueert.

De toewijzingsinstructie, die de operator = gebruikt, wijst een object toe aan een variabele of andere referentie, maar retourneert de waarde niet. De toewijzingsexpressie, oftewel de walrus-operator, doet hetzelfde als de toewijzingsinstructie maar retourneert ook het object:

>>> value = 10  # Assignment statement
>>> value
10
>>> (value := 20)  # Assignment expression
20

Let op dat de toewijzingsinstructie de waarde niet retourneert, in tegenstelling tot de toewijzingsexpressie. Een toewijzingsexpressie combineert dus meerdere bewerkingen:

  1. Hij evalueert de expressie aan de rechterkant van de operator :=.
  2. Hij wijst deze waarde toe aan een variabelenaam.
  3. Hij retourneert de waarde.

Haakjes zijn verplichte syntax wanneer je de toewijzingsexpressie gebruikt in plaats van een toewijzingsinstructie, zoals in dit voorbeeld, om ambiguïteit te verminderen. Later in deze tutorial lees je meer over de syntaxeisen voor de walrus-operator.

Een toewijzingsexpressie kan overal worden gebruikt waar expressies in Python zijn toegestaan. Kijk naar dit voorbeeld:

print(f"The number is {(value := 50)}")
print(value)
The number is 50
50

De variabelenaam value wordt gedefinieerd binnen de f-string op de eerste regel. Tegelijkertijd wordt het geheel ook direct teruggegeven aan de accolades in de f-string. De accolades in f-strings moeten een expressie bevatten, wat verklaart waarom je hier geen toewijzingsinstructie kunt gebruiken.

Use-cases voor Pythons walrus-operator

Twee veelvoorkomende use-cases voor de walrus-operator zijn het vereenvoudigen en optimaliseren van code.

Code vereenvoudigen

Een veelgebruikte toepassing van de walrus-operator is het vereenvoudigen van code waarin een variabele normaal gesproken eerst gedefinieerd moet worden voordat die gebruikt kan worden, zoals in sommige voorwaardelijke statements. Bekijk de volgende while-lus:

import random
value = random.randint(1, 20)
while value < 18:
    print(value)
    value = random.randint(1, 20)
7
5
1
11
12
1
9

Deze code voert het while-blok uit zolang de gegenereerde willekeurige waarde kleiner is dan 18. De variabele value moet vóór de while-lus worden gedefinieerd zodat die gebruikt kan worden in het while-statement. Aan het einde van het while-blok wordt een nieuw willekeurig getal aan value toegewezen.

De toewijzingsexpressie := kan het aantal regels verminderen en ervoor zorgen dat random.randint() maar op één plek in het programma wordt aangeroepen:

import random
while (value := random.randint(1, 20)) < 18:
    print(value)
1
9
2
4
9
15
11

Bij elke nieuwe iteratie van de while-lus wordt een nieuw willekeurig getal gegenereerd en aan value toegewezen.

Code optimaliseren

Laten we nog een voorbeeld met list comprehensions bekijken. Neem de volgende code, die een lijst met datumstrings filtert en een lijst maakt van datetime.datetime-objecten die alleen datums binnen een bepaald jaar bevatten:

from datetime import datetime
def format_date(date_str):
    return datetime.strptime(date_str, "%Y-%m-%d")
dates = ["2024-01-01", "2022-12-31", "2024-06-15", "2023-08-23", "2024-11-30"]
formatted_dates = [
    format_date(date)
    for date in dates
    if format_date(date).year == 2024
]
print(formatted_dates)
[datetime.datetime(2024, 1, 1, 0, 0), datetime.datetime(2024, 6, 15, 0, 0), datetime.datetime(2024, 11, 30, 0, 0)]

De functie format_date() accepteert een string met een datum in het jjjj-mm-dd-formaat en retourneert een datetime.datetime-object. Een van de attributen van datetime.datetime is .year, dat het jaardeel van de datum bevat.

Alleen de drie datums uit 2024 zijn opgenomen in de uiteindelijke lijst, die bovendien datetime.datetime-objecten bevat in plaats van strings. De list comprehension bevat een if-clausule die format_date() aanroept. Maar format_date() wordt ook aangeroepen in de eerste expressie van de list comprehension, die de waarden genereert om aan de lijst toe te voegen. De functie twee keer met hetzelfde argument aanroepen is inefficiënt, zeker als de functie een bottleneck vormt voor de performance van het programma.

Een manier om te voorkomen dat je dezelfde functie twee keer aanroept, is de functie aan te roepen en aan een variabele toe te wijzen voordat je hem gebruikt. Dat kan echter niet met een toewijzingsinstructie terwijl je toch een list comprehension gebruikt. Deze oplossing vereist een standaard for-lus:

from datetime import datetime
def format_date(date_str):
    return datetime.strptime(date_str, "%Y-%m-%d")
dates = ["2024-01-01", "2022-12-31", "2024-06-15", "2023-08-23", "2024-11-30"]
formatted_dates = []
for date in dates:
    formatted_date = format_date(date)
    if formatted_date.year == 2024:
        formatted_dates.append(formatted_date)
print(formatted_dates)
[datetime.datetime(2024, 1, 1, 0, 0), datetime.datetime(2024, 6, 15, 0, 0), datetime.datetime(2024, 11, 30, 0, 0)]

De walrus-operator biedt een alternatief dat wél samenwerkt met een list comprehension, omdat de opgemaakte datum die de functie retourneert zowel aan een variabelenaam kan worden toegewezen als in dezelfde expressie kan worden teruggegeven:

from datetime import datetime
def format_date(date_str):
    return datetime.strptime(date_str, "%Y-%m-%d")
dates = ["2024-01-01", "2022-12-31", "2024-06-15", "2023-08-23", "2024-11-30"]
formatted_dates = [
    formatted_date
    for date in dates
    if (formatted_date := format_date(date)).year == 2024
]
print(formatted_dates)
[datetime.datetime(2024, 1, 1, 0, 0), datetime.datetime(2024, 6, 15, 0, 0), datetime.datetime(2024, 11, 30, 0, 0)]

De toewijzingsexpressie in de list comprehension is de expressie tussen haakjes, (formatted_date := format_date(date)). Deze expressie roept de functie format_date() aan, wijst de waarde toe aan de variabele formatted_date en retourneert de waarde. Omdat de toewijzingsexpressie evalueert tot een datetime.datetime-object, wordt het .year-attribuut benaderd en met 2024 vergeleken met de gelijkheidsoperator.

De if-clausule in de list comprehension wordt uitgevoerd vóór de expressie die de waarde genereert die in de lijst wordt opgeslagen. Daarom wordt de walrus-operator in de if-clausule gebruikt en wordt de variabele die in deze toewijzingsexpressie is gedefinieerd gebruikt als de eerste expressie in de list comprehension.

Controverse rond Pythons walrus-operator

De toewijzingsexpressie is niet ieders favoriet. Sommigen zien het als een manier om code te vereenvoudigen, anderen vinden dat de leesbaarheid eronder lijdt en geven de voorkeur aan langere opties. Leesbaarheid is subjectief, dus meningen verschillen over de vraag of de walrus-operator code leesbaarder maakt of niet.

Het laatste voorbeeld in de vorige sectie laat de afwegingen zien. Zonder de walrus-operator moet in dit voorbeeld eerst een lege lijst worden geïnitialiseerd en worden waarden eraan toegevoegd in een for-lus. Deze oplossing is langer en minder efficiënt dan het gebruik van list comprehensions, die hiervoor zijn geoptimaliseerd.

Toch kan de standaardoplossing met een for-lus voor sommige programmeurs beter leesbaar zijn, zeker als performance geen rol speelt, en geven zij er de voorkeur aan om meer code te schrijven in plaats van de walrus-operator te gebruiken.

Veel stijlrichtlijnen voor code raden nog steeds aan de toewijzingsexpressie te vermijden of slechts spaarzaam te gebruiken.

Pythons walrus-operator: syntaxregels

Bij het ontdekken van de walrus-operator kom je vaak syntaxfouten tegen door de specifieke syntaxregels.

Om verwarring tussen de toewijzingsinstructie = en de toewijzingsexpressie := te voorkomen, bestaat er geen situatie waarin beide opties syntactisch geldig zijn. Daarom is het vaak nodig de toewijzingsexpressie tussen haakjes te zetten:

(value := 20)  # Valid
value := 20  # Invalid (SyntaxError)

De eis om de toewijzingsexpressie in dit geval tussen haakjes te plaatsen is het tegenovergestelde van de eis voor de toewijzingsinstructie, die niet tussen haakjes mag staan.

Toch heeft de toewijzingsexpressie niet altijd haakjes nodig:

import random
if value := random.randint(0, 3):
    print(f"{value} is greater than 0")
1 is greater than 0

De toewijzingsexpressie in het if-statement genereert een willekeurig getal tussen 0 en 3 en wijst het toe aan de variabele value. De gehele getallen 1, 2 en 3 zijn truthy, en de code in het if-blok wordt uitgevoerd wanneer deze waarden worden gegenereerd. Als random.randint() echter 0 (falsy) retourneert, wordt het if-blok niet uitgevoerd.

Een toewijzingsinstructie is niet geldig na een if-keyword. Er is dus geen verwarring in deze situatie en haakjes zijn niet nodig.

Dit voorbeeld illustreert ook een ander belangrijk punt over de toewijzingsexpressie. De scope van de toegewezen variabele volgt de normale LEGB-regels. De variabele value is in dit voorbeeld dus overal in de globale scope beschikbaar:

import random
if value := random.randint(0, 3):
    print(f"{value} is greater than 0")
   
print(value)
2 is greater than 0
2

Als een toewijzingsexpressie binnen een functiedefinitie wordt gebruikt, is de variabele alleen lokaal binnen de functie beschikbaar. Dit is hetzelfde gedrag als bij variabelen die met een toewijzingsinstructie worden gemaakt.

Dit voorbeeld kan worden aangepast om een ander potentieel struikelblok bij het gebruik van de walrus-operator te laten zien. Laten we de code wijzigen en een vergelijkingsoperator in het if-statement opnemen:

import random
if value := random.randint(0, 3) < 2:
    print(f"{value} is less than 2")
True is less than 2

De getoonde output wijst op het probleem: value is de Booleaan True in plaats van een geheel getal. De walrus-operator heeft de laagste prioriteit van alle operators, alleen de komma heeft een lagere prioriteit. Daarom wordt de kleiner-dan-operator < eerst geëvalueerd en wordt de uitkomst eraan toegewezen aan value. Haakjes zijn nodig om dit op te lossen:

import random
if (value := random.randint(0, 3)) < 2:
    print(f"{value} is less than 2")
0 is less than 2

De toewijzingsexpressie wordt nu eerst geëvalueerd, omdat haakjes de hoogste prioriteit hebben. Het getal dat door random.randint() wordt geretourneerd, wordt aan value toegewezen en vervolgens gebruikt als eerste operand voor de kleiner-dan-operator.

In tegenstelling tot de toewijzingsinstructie kan de walrus-operator alleen waarden toewijzen aan variabelenamen en niet aan subscripts of attributen:

(name := "James")  # Valid
points = {"James": 10, "Mary": 20}
(points["Sarah"] := 30)  # Invalid
class Article:
    pass
article = Article()
(article.title := "The Walrus Operator")  # Invalid

Het is niet mogelijk om met de walrus-operator een waarde toe te wijzen via een subscript of aan een attribuut.

Conclusie

De toewijzingsexpressie, die de syntax := gebruikt en vaak de walrus-operator wordt genoemd, is een relatief nieuwe toevoeging aan Python. De toewijzingsinstructie, die de operator = gebruikt, is de meer gebruikelijke manier om een waarde aan een variabelenaam toe te wijzen.

De walrus-operator breidt deze toewijzing echter uit door ook de toegewezen waarde te retourneren, waardoor deze toewijzing een expressie wordt. Daardoor kun je de toewijzingsexpressie gebruiken waar je in Python ook andere expressies kunt gebruiken.

De Python-community is echter verdeeld over het nut en de leesbaarheid van de walrus-operator. Daarom wordt de toewijzingsexpressie minder vaak gebruikt dan andere nieuwere toevoegingen aan Python. Het is goed om te weten hoe je deze operator gebruikt, maar overdrijf het niet!

Stephen Gruppetta

Auteur

Stephen Gruppetta

Ik studeerde natuurkunde en wiskunde op bachelorniveau aan de Universiteit van Malta. Daarna verhuisde ik naar Londen en behaalde ik mijn PhD in natuurkunde aan Imperial College. Ik werkte aan nieuwe optische technieken om het menselijk netvlies in beeld te brengen. Nu richt ik me op schrijven over Python, communiceren over Python en lesgeven in Python.

Onderwerpen

Gerelateerd