Hence the former can be used in contexts like "if x := 10: pass", which is the whole point of the PEP.
Hence the former can be used in contexts like "if x := 10: pass", which is the whole point of the PEP.
For example, import mod is NOT defined as
mod = eval(open("mod.py").read())
but involves abstract load module operation, which is dependant on the environment.That's why := is just syntactic sugar; there are no new semantics.
I don't think that's right; what expression/statement is `x := y` equivalent to? I'm thinking in particular about using mutable collections to emulate assignment in a lambda, e.g.
>>> counter = (lambda c: lambda: (c.append(c.pop() + 1), c[0])[1])([0])
>>> counter()
1
>>> counter()
2
>>> counter()
3
It looks like this could now be done as: >>> counter = (lambda c: lambda: (c := c + 1))(0)
Yet the semantics here are very different: one is pushing and popping the contents of a list, without changing any variable bindings (`c` always points to the same list, but that list's contents changes); the other has no list, no pushing/popping, and does change the variable bindings (`c` keeps pointing to different integers).Maybe it's equivalent to using a `=` statement, but statements are forbidden inside lambdas. Maybe the lambdas are equivalent to `def ...` functions, but what would their names be? Even if we made the outer one `def counter(c)...` the resulting value would have a different `func_name` (`counter` versus `<lambda>`).
Even the `if` examples that are scattered around this page don't seem to have an equivalent. For example:
if (x := foo() is not None):
do_something()
We can't "desugar" this, e.g. to something like the following: x = foo()
if x is not None:
do_something
The reason is that we're changing the point at which the binding takes place. For example, Python guarantees to evaluate the elements of a tuple in left to right order (which we exploited in the above push/pop example). That means we could write: if (sys.stdout.write(x), x := foo() is not None)[1]:
do_something
This will print the current value of `x`, then update `x` to the return value of `foo()`. I can't think of a way to desugar this which preserves the semantics. For example, using the incorrect method from above: x = foo()
if (sys.stdout.write(x), x is not None)[1]:
do_something
This isn't equivalent, since it will print the new value of `x`. Maybe we could float the `write` call out of the condition too, but what about something like: if foo(x) and (x := bar()):
do_something
We would have to perform `foo(x)` with the old value of `x`, store the result somewhere (a fresh temporary variable?), perform the `x = bar()` assignment, reconstruct the condition using the temporary variable and the new value of `x`, then `del` the temporary variable (in case `do_something` makes use of `locals()`).PS: I think this `:=` is a good thing, and writing the above examples just reminded me how infuriating it is when high-level languages distinguish between statements and expressions, rather than having everything be an expression!
> You need to use a temporary variable but then your example is easy.
Yes this example, of `if foo(x) and (x := bar()):`, would be easy with a temporary variable. But there are infinite variations we can make:
if foo(x) and (x := bar()):
if foo(x) or (x := bar()):
if (x := baz()) and foo(x) and (x := bar()):
if foo(x, y) and (x := bar()) and baz(x) and (y := quux()):
...
I fail to see how something is "just sugar" when desugaring it seems to require implementing a general-purpose compiler from "Python" to "Python without ':='".I would suggest that if you can express the exact same semantics with a "few" more lines then it's just sugar.
In the case of x := y, it's always possible to rewrite the program with a "few" extra lines where it means the same thing. It's just combining the assignment and expose operations.
I agree. The important question is what we mean by "the exact same semantics". I would say that observational equivalence is the most appropriate; i.e. that no other code can tell that there's a difference (without performing unpredictable side-effects like parsing the contents of the source file). Python is a really difficult language for this, since it provides so many hooks for redefining behaviour. For example in many languages we could say that 'x + x' and 'x * 2' and 'x << 1' are semantically the same (they double 'x'), but in Python those are very different expressions, which can each invoke distinct, arbitrary code (a `__mul__` method, an `__add__` method, etc.). The fact they often do the same thing is purely a coincidence (engineered by developers who wish to remain sane).
It's fine if we only care about the 'black box' input/output behaviour, but at that point it no longer matters which language we're using; we could have something more akin to a compiler rather than desugaring into expressions from the same language.
> it's always possible to rewrite the program
There's an important distinction here too. Are we saying that "a semantically equivalent program exists"? That's a trivial consequence of Turing completeness (e.g. there's always an equivalent turing machine; and an equivalent lambda calculus expression; and an equivalent Java program; etc.)
Are we saying that an algorithm exists to perform this rewriting? That would be more useful, since it tells us that Rice's theorem doesn't apply for this case (otherwise it might be impossible to tell if two programs are equivalent or not, due to the halting problem).
Are we saying that we know an algorithm which will perform this rewriting? This is the only answer which lets us actually run something (whether we call that an "elaborator", a "compiler", etc.). Yet in this case I don't know of any algorithm which is capable of rewriting Python involving `:=` into Python which avoids it. I think such an algorithm might exist, but I wouldn't be surprised if Python's dynamic 'hooks' actually make such rewriting impossible in general.
I certainly don't think that a local rewrite is possible, i.e. where we can swap out any expression of the form `x := y` without changing any other code, and keep the same semantics. If it is possible, I would say that such a local, observational equivalence preserving rewrite rule would qualify for the name "syntactic sugar".
> It's just combining the assignment and expose operations.
I'm not sure what you mean by "expose", and a search for "python expose" didn't come up with anything. It would be nice to know if I've missed out on some Python functionality!