From 6d6fc786550c1ce72c9268d0f04e01135b116eeb Mon Sep 17 00:00:00 2001 From: colinleach Date: Sun, 7 Jan 2024 12:29:05 -0700 Subject: [PATCH] [Walrus Operator] Add concept --- concepts/walrus-operator/.meta/config.json | 4 +- concepts/walrus-operator/about.md | 101 ++++++++++++++++++++- concepts/walrus-operator/links.json | 16 ++-- 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/concepts/walrus-operator/.meta/config.json b/concepts/walrus-operator/.meta/config.json index 9b9e8da5a9..f7557be772 100644 --- a/concepts/walrus-operator/.meta/config.json +++ b/concepts/walrus-operator/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Properly called assignment expressions, the walrus operator assigns values to variables as part of a larger expression.", + "authors": ["BethanyG", "colinleach"], "contributors": [] } diff --git a/concepts/walrus-operator/about.md b/concepts/walrus-operator/about.md index c628150d56..c391a50f64 100644 --- a/concepts/walrus-operator/about.md +++ b/concepts/walrus-operator/about.md @@ -1,2 +1,101 @@ -#TODO: Add about for this concept. +# About +Added in Python 3.8, assignment expressions use the `:=` operator, popularly called the ["walrus operator"][whats-new]. + +The central idea is that a variable can be assigned within the context of another statement. +A simple example is when something tested in a condition is needed again within the body of an `if` statement: + +```python +# without walrus operator +a = 'short' +if len(a) < 10: + print(f"{len(a)} characters is too short") + +# => "5 characters is too short" + +# with walrus operator +# Note the parentheses around the assignment (necessary) +if (n := len(a)) < 10: + print(f"{n} characters is too short") + +# => "5 characters is too short" +``` + +Thus, setting the `n` variable saves calculating `len(a)` twice. +The saving is fairly trivial in this case, but becomes significant for a more expensive operation such as file I/O: + +```python +# Loop over fixed length blocks +while (block := f.read(256)) != '': + process(block) +``` + +## Use cases + +The walrus operator was [controversial][controversy] in the Python community when [first introduced][pep-572] in 2018. +It is now an established part of the core language, simplifying code in some particular situations, but should not be over-used. +As so often in programming, we should consider if use of a feature increases or decreases the readability of the code. + +Some applications of the walrus operator are listed below. + +### Comprehensions + +List comprehensions, dictionary comprehensions, generator expressions, _etc_, all allow Python to do a lot in a terse piece of code. + +Including a walrus operator can _sometimes_ make such code shorter and clearer, especially if a value calculated in the `if` clause is also needed in other parts of the comprehension. + +```python +[clean_name.title() for name in names + if (clean_name := name.strip().lower()) in allowed_names] +``` + +Note that the calculated value `clean_name` is only in scope within the comprehension. +It cannot be used in other parts of the program. + +### RegEx matching + +Use of the walrus operator can simplify nested `if...else` statements. +Handling regular expression matches is [an example][walrus-blog] of this: + +```python +import re + +test = "Something we want to search" +pattern1 = r"^.*(thing).*" +pattern2 = r"^.*(missing).*" + +# without walrus +m = re.match(pattern1, test) +if m: + print(f"Matched the 1st pattern: {m.group(1)}") +else: + m = re.match(pattern2, test) + if m: + print(f"Matched the 2nd pattern: {m.group(1)}") +# => "Matched 1st pattern: 'thing'" + +# Cleaner with walrus +if m := (re.match(pattern1, test)): + print(f"Matched 1st pattern: '{m.group(1)}'") +elif m := (re.match(pattern2, test)): + print(f"Matched 2nd pattern: '{m.group(1)}'") +# => "Matched 1st pattern: 'thing'" +``` + +### Inside f-strings + +This is not a particularly common use of walrus operators, but it can sometimes simplify code. +As usual, the assignment must be within parentheses. + +```python +from datetime import datetime + +print(f"Today is: {(today:=datetime.today()):%Y-%m-%d}, which is {today:%A}") + +# => "Today is: 2024-01-06, which is Saturday" +``` + +[whats-new]: https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions +[walrus-blog]: https://martinheinz.dev/blog/79 +[pep-572]: https://peps.python.org/pep-0572/ +[controversy]: https://dev.to/renegadecoder94/the-controversy-behind-the-walrus-operator-in-python-4k4e diff --git a/concepts/walrus-operator/links.json b/concepts/walrus-operator/links.json index eb5fb7c38a..ed69fa5387 100644 --- a/concepts/walrus-operator/links.json +++ b/concepts/walrus-operator/links.json @@ -1,18 +1,18 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions/", + "description": "A What's New description for Python 3.8." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://peps.python.org/pep-0572/", + "description": "PEP 572 describes the walrus operator and its rationale." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://martinheinz.dev/blog/79/", + "description": "A thoughtful blog article from Martin Heinz." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://dev.to/renegadecoder94/the-controversy-behind-the-walrus-operator-in-python-4k4e/", + "description": "A discussion of the controversy surrounding the walrus operator when it was introduced." } ]