As part of my series on Python's syntactic sugar, I initially skipped tackling assignment expressions because I made them more complicated than I needed to in my head. 😅 But there is some key subtlety to unravelling them which may not be obvious.
Let's start with a simple example of an assignment expression inside of a function call:
Now naively you might think you can just lift that assignment expression out to be an assignment statement and things would just work, like so:
Unfortunately this doesn't work for more complicated examples. For instance, if you changed
a.d, that naive approach no longer works. Remember that attribute access executes code, so you can't guarantee that
a.d doesn't have a side-effect that would influence the outcome of
c(). So to do this properly you need to break down the expression into each constituent step, all executed in the proper order. Essentially you have to follow each step that the CPython interpreter takes when pushing something on to its stack when it operates.
Luckily this solution partially handles the scoping for assignment expressions in the face of generator expressions. Specifically, assignment expressions inside of generator expressions (and thus comprehensions) are expected to leak out of their generator expression scope to the enclosing scope. That means
any((comment := line).startswith('#') for line in lines) actually has
comment accessible outside of the generator expression.
Unfortunately there is a complication here when the assignment expression variable is never executed. In that case, a
NameError is supposed to be raised when trying to use the variable. To do that right you would need to check if the variable was set to some initial sentinel value before first access, raising
NameError if the sentinel is found, else moving on otherwise. It's a pain and leads to more unravelling code, but it should still lead to the appropriate exception being raised. For instance, if you were to do
print(comment) for the example, you would need to unravel it to:
What this all means is to make the unravelling as safe and thorough as possible, you essentially need to translate Python code to SSA form which is not the most readable. 😅 But it's at least doable and gets the appropriate result.