⟵back

Python walrus operator

With Python 3.8 (which was a while ago now) came support for the walrus operator. Known by a few other names, such as the "named expression operator", in short, it assigns a variable while doing something.

Let's take a look at a classic fruit example.

def check_for_banana(fruit: list[string]) -> bool:
    return bool("banana" in fruit)

# the common way to write it:
has_banana = check_for_banana(["kiwi", "apple", "orange"])
if has_banana:
    print("The list contains a banana")

# save a line of code with the walrus operator:
if has_banana := check_for_banana(["kiwi", "apple", "orange"]):
    print(has_banana)

There is not really much point to assigning a variable in this way, though, if you're not going to use it. In the example I used, we want to print has_banana to the console, but if not, you may as well just write

if check_for_banana(["banana", "apple", "orange"]):

because check_for_banana() returns a boolean anyway.

I recently came across a particularly elegant way to use the walrus operator in Django code. If you've used choices before in models, where you assign constants and then declare these within a nested tuple, you might have thought this was a bit clunky:

FLAT_WHITE = "flat_white"
PUMPKIN_SPICE_LATTE = "pumpkin_spice_latte"

HOT_DRINK_CHOICES = (
    (FLAT_WHITE, "Flat white"),
    (PUMPKIN_SPICE_LATTE, "Pumpkin spice latte")
)

Enter the walrus operator:

HOT_DRINK_CHOICES = (
    ((FLAT_WHITE := "flat_white"), "Flat white"),
    ((PUMPKIN_SPICE_LATTE := "pumpkin_spice_latte"), "Pumpkin spice latte")
)

Here is another example from some code in my main side project, getting the percentage of entries completed from the available data I have:

def get_percentage(self, dividend: int, divisor: int) -> float | None:
    try:
        divided = dividend / divisor
        return round(divided * 100, 2)
    except ZeroDivisionError:
        return None

def entries_completed(self, obj: District) -> str:
    if isinstance((percentage := self.get_percentage(
        obj.number_of_completed_streets, obj.number_of_added_streets
    )), float):
        return f"{percentage}%"
    return "Unknown"

entries_completed() could be back-refactored as follows:

def entries_completed(self, obj: District) -> str:
    percentage = self.get_percentage(obj.number_of_completed_streets, obj.number_of_added_streets)
    if isinstance(percentage, float):
        return f"{percentage}%"
    return "Unknown"

The walrus operator may be cute and quirky, but it is not without its detractors. To some degree, I do agree that it should be used sparingly in order to maintain code readability. You may notice there's an extra, perhaps superfluous set of parentheses in the entries_completed() example, which is one such trade-off. There are also certain situations it's downright unsuitable for, such as within print statements.

I would consider one rule of thumb to be that if your code is already quite self-explanatory (e.g. you're using "readable" variable names rather than ones that merely identify, such as x), it's a good way to reduce lines of code. However, there absolutely are times when it's obvious it's being used just to show off without providing any benefits — neither to fellow/future developers nor to the performance of the program. So if in doubt, probably best to revisit The Zen of Python and consider whether your use case matches up.

To sum up: the tusks (the = part) assign the variable, while the eyes (the : part) use the variable. There is actually a lot more to learn on this, so I'll end the post by throwing this article your way.