12

I just learned that the new walrus operator (:=) can't be used to set instance attributes, it's supposedly invalid syntax (raises a SyntaxError).

Why is this? (And can you provide a link to official docs mentioning this?)

I looked through PEP 572, and couldn't find if/where this is documented.


Research

This answer mentions this limitation without an explanation or source:

you can't use the walrus operator on object attributes


Sample Code

class Foo:
    def __init__(self):
        self.foo: int = 0

    def bar(self, value: int) -> None:
        self.spam(self.foo := value)  # Invalid syntax

    def baz(self, value: int) -> None:
        self.spam(temp := value)
        self.foo = temp

    def spam(self, value: int) -> None:
        """Do something with value."""

Trying to import Foo results in a SyntaxError:

    self.spam(self.foo := value)
              ^
SyntaxError: cannot use assignment expressions with attribute
Intrastellar Explorer
  • 764
  • 2
  • 14
  • 44
  • 3
    @SuperStew can you explain why? – jtbandes Sep 24 '20 at 22:37
  • @SuperStew sometimes bad is the lesser of two evils. I find the examples in PEP 572 compelling – Caleth Sep 24 '20 at 22:39
  • @jtbandes just seems to run counter to the zen of python – SuperStew Sep 24 '20 at 22:39
  • 1
    I suspect it is because assignment to an attribute is really a method call in disguise, and could have hidden side-effects. A pure assignment to a name cannot. – chepner Sep 24 '20 at 22:40
  • 6
    "I don't like walrus operators" is a fair statement. "Walrus operators _are bad_ and _you should feel bad_" is quite another. Let's not forget that the walrus operator went through rigorous review and discussion from many python experts who have strong, well developed opinions about what the zen of python means for the language. It's fine to disagree with them, but not nice to tell random people to feel bad because of that disagreement. – inclement Sep 24 '20 at 22:49

2 Answers2

9

PEP 572 describes the purpose of this (emphasis mine):

This is a proposal for creating a way to assign to variables within an expression using the notation NAME := expr.

self.foo isn't a variable, it's an attribute of an object.

The Syntax and semantics section specifies it further:

NAME is an identifier.

self.foo isn't an identifier, it's two identifiers separated by the . operator.

While we often use variables and attributes similarly, and sometimes will sloppily refer to self.foo as a variable, they aren't the same. Assigning to self.foo is actually just a shorthand for

setattr(self, 'foo', temp)

This is what allows you to define getters and setters for attributes. It would complicate the specification and implementation of the assignment expression if it had to work with attributes that have customized setters.

For instance, if the setter transforms the value that's being assigned, should the value of the assignment expression be the original value or the transformed value?

Variables, on the other hand, cannot be customized. Assigning to a variable always has the same, simple semantics, and it's easy for the expression to evaluate to the value that was assigned.

Similarly, you can't use the walrus operator with slice assignment. This isn't valid:

foo1[2:4] := foo2[1:3]
Barmar
  • 596,455
  • 48
  • 393
  • 495
2

Interestingly, the prohibition on walrussing object.attributes seems to be present only in Python's parser that parses text code into an abstract syntax tree (ast), which then would be compiled and executed. If you manually create an ast syntax tree with self.foo taking the place of var in (var := temp) and then compile or exec that tree, it compiles and executes just as you would intuitively expect it to.

So apparently the underlying functionality is there to allow walrus-assignment to object.attributes, they just chose not to let us use it, because they were worried it would make people write confusing code or something. Thanks a lot...

So anyway, an extreme (not at all recommended!) solution would be for you to do a bit of pre-compilation ast-surgery to splice your object.attribute targets into your walrus operators, and then it'd probably run as you expect. (I discovered this since I was already doing ast-surgery that replaced simple variables with object.attributes for other reasons, and I was happy to find that walrus assignment still worked!)

JustinFisher
  • 246
  • 1
  • 2
  • 7