Python is constantly evolving, with each new version bringing various optimizations and new tools. In Python 3.8, the Walrus Operator (:=) was introduced, sparking significant debate within the community. This article delves into the Walrus Operator, its implications, and its uses.
The Story Behind the Walrus Operator and Guido van Rossum’s Resignation
Guido van Rossum, the creator of Python, played a central role in decision-making for Python’s development. For a long time, he single-handedly determined Python’s direction, studying user feedback and personally selecting changes for each new version. This earned him the semi-humorous title of Python’s “Benevolent Dictator for Life.”
In 2018, Guido announced his resignation from this position, citing PEP 572, which introduced the Walrus Operator. The document led to heated debates among Python developers. Many believed that the ideas in PEP 572 contradicted Python’s philosophy and reflected Guido’s personal opinion rather than industry best practices. Some developers found the syntax of := complex and unintuitive. Nevertheless, Guido approved PEP 572, and the Walrus Operator was included in Python 3.8.
The backlash was overwhelming. Guido received numerous negative comments and eventually decided to step down. In his resignation letter, he expressed his frustration: “I don’t ever want to have to fight so hard for a PEP and see so much negativity directed at me.”
After Guido’s resignation, the project governance model was revised. A steering council of senior developers, who had made significant contributions to Python, was formed to make final decisions. Guido later returned to the project as a core developer, contributing without the burden of leadership.
Understanding the Walrus Operator
Syntax and Usage
The Walrus Operator := was introduced in Python 3.8 and allows assigning a value to a variable and returning that value simultaneously. The basic syntax is:
variable := expression
First, expression
is evaluated, then its value is assigned to variable
, and finally, this value is returned. The operator is called “walrus” because it resembles a walrus’s eyes and tusks.
Differences from the Assignment Operator =
The main difference between := and the classic assignment operator = is that := allows assigning values within expressions. For example:
num = 7
print(num)
Using the Walrus Operator, this can be condensed to one line:
print(num := 7)
This assigns 7 to num
and then returns it as an argument for print()
. Using = in a similar way would result in a syntax error.
Practical Examples of the Walrus Operator
Example 1: Filtering and Printing Keyword Lengths
Consider the task of printing Python keywords longer than five characters:
from keyword import kwlist
for word in kwlist:
if len(word) > 5:
print(f'{word} has {len(word)} characters.')
This code calculates len(word)
twice. Using :=, we can optimize it:
from keyword import kwlist
for word in kwlist:
if (n := len(word)) > 5:
print(f'{word} has {n} characters.')
Example 2: Input Loop Until “stop”
The task is to collect words until “stop” is entered:
words = []
word = input()
while word != 'stop':
words.append(word)
word = input()
With :=, this can be simplified:
words = []
while (word := input()) != 'stop':
words.append(word)
Example 3: Reading File Lines
Reading lines from a file until an empty line is encountered:
with open('input.txt', 'r') as file:
line = file.readline().rstrip()
while line:
print(line)
line = file.readline().rstrip()
With :=, it becomes:
with open('input.txt', 'r') as file:
while line := file.readline().rstrip():
print(line)
Example 4: Filtering Factorials
Generating a list of factorials less than 1000:
from math import factorial
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new_data = [factorial(x) for x in data if factorial(x) <= 1000]
print(new_data)
With :=, the factorial is computed only once:
from math import factorial
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new_data = [fact for num in data if (fact := factorial(num)) <= 1000]
print(new_data)
Example 5: Extracting Non-None Names
Filtering a list of dictionaries to print names and occupations:
users = [
{'name': 'Timur Guev', 'occupation': 'python generation guru'},
{'name': None, 'occupation': 'driver'},
{'name': 'Anastasiya Korotkova', 'occupation': 'python generation bee'},
{'name': None, 'occupation': 'driver'},
{'name': 'Valeriy Svetkin', 'occupation': 'python generation bee'}
]
for user in users:
name = user.get('name')
if name is not None:
print(f'{name} is a {user.get("occupation")}.')
With :=, it is simplified:
users = [
{'name': 'Timur Guev', 'occupation': 'python generation guru'},
{'name': None, 'occupation': 'driver'},
{'name': 'Anastasiya Korotkova', 'occupation': 'python generation bee'},
{'name': None, 'occupation': 'driver'},
{'name': 'Valeriy Svetkin', 'occupation': 'python generation bee'}
]
for user in users:
if (name := user.get('name')) is not None:
print(f'{name} is a {user.get("occupation")}.')
Example 6: Pattern Matching
Matching patterns in a text:
import re
text="Python is a powerful programming language."
pattern1 = r'beegeek'
pattern2 = r'Python'
m = re.search(pattern1, text)
if m:
print(f'Match found: {m.group()}')
else:
m = re.search(pattern2, text)
if m:
print(f'Match found: {m.group()}')
else:
print('No matches')
With :=, it can be streamlined:
import re
text="Python is a powerful programming language."
pattern1 = r'beegeek'
pattern2 = r'Python'
if m := re.search(pattern1, text):
print(f'Match found: {m.group()}')
else:
if m := re.search(pattern2, text):
print(f'Match found: {m.group()}')
else:
print('No matches')
Example 7: Checking Conditions on a List
Determining if any number in a list is greater than 10 and if all numbers are less than 10:
numbers = [1, 4, 6, 2, 12, 4, 15]
print(any(number > 10 for number in numbers))
print(all(number < 10 for number in numbers))
With :=, you can capture the last checked value:
numbers = [1, 4, 6, 2, 12, 4, 15]
print(any((value := number) > 10 for number in numbers))
print(value)
print(all((value := number) < 10 for number in numbers))
print(value)
Caveats and Best Practices
While the Walrus Operator can make code more concise and efficient, it can also introduce pitfalls, such as incorrect precedence and unintended variable scope changes. For instance:
from math import factorial
fact = 0
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
factorial_data = [fact for num in data if (fact := factorial(num)) <= 1000]
print(fact)
This code overwrites fact
in the outer scope.
Another potential issue arises with empty iterables in functions like any()
or all()
, where the variable might not be created:
numbers = []
print(any((value := number) > 10 for number in numbers))
print(value)
This leads to a NameError
.
Using := in conditional expressions also requires caution to ensure variables are properly defined:
for i in range(1, 101):
if (two := i % 2 == 0) and (three := i % 3 == 0):
print(f"{i} is divisible by 6.")
elif two:
print(f"{i} is divisible by 2.")
elif three:
print(f"{i} is divisible by 3.")
This will raise a NameError
if (two := i % 2 == 0)
is false, as three
won’t be defined.
Conclusion
The Walrus Operator can make your code more concise and efficient, but it should be used judiciously. Avoid overusing it, and ensure that
In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.