":=" Syntax and Assignment Expressions: What and Why

How are property assignment expressions handled in C#?

In C#, an assignment is the result of the assignment itself.

For example:

string a = null;

if((a = "hello world").Contains("world"))
{
Console.WriteLine("It has 'world' word!");
}

As you see, it's not about properties, but assignments.

An statement like this:

int n = N = 1;

...is equivalent to:

int n = (N = 1);

The point of this is (N = 1) has a return value itself: an integer value of 1.

How do Assignment Expressions `:=` work in Python?

As GreenCloakGuy mentioned, it is there to avoid confusion, as said here, I think this line sums it all:

there is no syntactic position where both = and := are valid.

It also makes things like these invalid because too confusing:

y0 = y1 := f(x)
foo(x = y := f(x))

What is the use of Assignment expressions in python 3.8

Lets take the first example from the What's new in 3.8 documentation:

if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")

"Normal" assignment is a statement, it can't be used as part of expressions. Without the expression-assignment ("walrus") operator the above example would have to be something like

n = len(a)
if n > 10:
print(f"List is too long ({n} elements, expected <= 10)")

With the expression-assignment you can combine the assignment to n with the condition.

why can't I use := to assign values in this situation?

@mkopriva's answer is correct but I think it's worth clarifying this as this confused me as a newbie also.

tl:dr

The language is defined this way.

How is it defined this way?

If you look at the spec

Unlike regular variable declarations, a short variable declaration may
redeclare variables provided they were originally declared earlier in
the same block (or the parameter lists if the block is the function
body) with the same type, and at least one of the non-blank variables
is new.

It would appear your syntax should work.

type test struct {
string
int
}

t := test{}
t.string, i := foo()

But the key point is that one is a declaration and assignment, and one is just an assignment.

:= is the short form of declaring a variable and = is the assignment operator. Despite looking deceptively similar, the language spec allows them to do different things.

If you look at the spec for Assignments you can trace the valid syntax to be:

PrimaryExpr =
Operand |
Conversion |
MethodExpr |
PrimaryExpr Selector |
PrimaryExpr Index |
PrimaryExpr Slice |
PrimaryExpr TypeAssertion |
PrimaryExpr Arguments .

Operand contains the syntax for an identifier (variable). You can see that both Operand and PrimaryExpr Selector are valid to be assigned to. The syntax t.string is a selector, not an identifier.

Looking at the spec for short declarations and find the list of valid syntax:

ShortVarDecl = IdentifierList ":=" ExpressionList .
/.../
IdentifierList = identifier { "," identifier } .

You can see the only thing that is allowed on the LHS is an identifier.

With assignment expressions in Python 3.8, why do we need to use `as` in `with`?

TL;DR: The behaviour is not the same for both constructs, even though there wouldn't be discernible differences between the 2 examples.

You should almost never need := in a with statement, and sometimes it is very wrong. When in doubt, always use with ... as ... when you need the managed object within the with block.


In with context_manager as managed, managed is bound to the return value of context_manager.__enter__(), whereas in with (managed := context_manager), managed is bound to the context_manager itself and the return value of the __enter__() method call is discarded. The behaviour is almost identical for open files, because their __enter__ method returns self.

The first excerpt is roughly analogous to

_mgr = (f := open('file.txt')) # `f` is assigned here, even if `__enter__` fails
_mgr.__enter__() # the return value is discarded

exc = True
try:
try:
BLOCK
except:
# The exceptional case is handled here
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
_mgr.__exit__(None, None, None)

whereas the as form would be

_mgr = open('file.txt')   # 
_value = _mgr.__enter__() # the return value is kept

exc = True
try:
try:
f = _value # here f is bound to the return value of __enter__
# and therefore only when __enter__ succeeded
BLOCK
except:
# The exceptional case is handled here
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
_mgr.__exit__(None, None, None)

i.e. with (f := open(...)) would set f to the return value of open, whereas with open(...) as f binds f to the return value of the implicit __enter__() method call.

Now, in case of files and streams, file.__enter__() will return self if it succeeds, so the behaviour for these two approaches is almost the same - the only difference is in the event that __enter__ throws an exception.

The fact that assignment expressions will often work instead of as is deceptive, because there are many classes where _mgr.__enter__() returns an object that is distinct from self. In that case an assignment expression works differently: the context manager is assigned, instead of the managed object. For example unittest.mock.patch is a context manager that will return the mock object. The documentation for it has the following example:

>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
... assert thing is mock_thing
... thing()
...
Traceback (most recent call last):
...
TypeError: 'NonCallableMock' object is not callable

Now, if it were to be written to use an assignment expression, the behaviour would be different:

>>> thing = object()
>>> with (mock_thing := patch('__main__.thing', new_callable=NonCallableMock)):
... assert thing is mock_thing
... thing()
...
Traceback (most recent call last):
...
AssertionError
>>> thing
<object object at 0x7f4aeb1ab1a0>
>>> mock_thing
<unittest.mock._patch object at 0x7f4ae910eeb8>

mock_thing is now bound to the context manager instead of the new mock object.

What is the assignment-expression in array brackets in C?

In C89 there was no support for variable length arrays. This means that array sizes must be fixed at compile time.

Starting with C99, declarations and statements may be mixed within a block, so full expressions that must be executed are now allowed as an initializer. This also is what allows for variable length arrays to be created.

So a declaration like int arr[i=0]; is valid syntax, although it's invalid because it created an array of size 0. int arr[i=2]; is valid and will create arr as an array of int of size 2, and it sets i to 2.

Why does if not a := say_empty() raise a SyntaxError?

Operator precedence indicates that := has a lower precedence than not. So not a := is read as trying to assign to not a, hence the syntax error.

You can use parentheses to clarify the meaning:

if not (a := say_empty()):
...

Why we have to use Assignment Expressions in obtaining the return value from await?

Assignment with = was designed expressly to not do this kind of nested side-effecting inside a larger expression. With other languages (looking at you, C) people can type = where they meant to use == and assign something by accident. See this question for more discussion of why = was designed this way.

Assignment with = is a statement and not an expression, it does not evaluate to what's on the right hand side of the equal sign. That means there isn't a value for the while to test in order to decide whether to continue. The := (AKA the walrus operator) provides a value the while can test, see this question.



Related Topics



Leave a reply



Submit