Search code examples
pythonabstract-syntax-treepython-3.8

Multiple assignments via walrus := operator?


I've attempted to make multiple assignments with the walrus operator, and seen questions on StackOverflow such as this which also fail to assign multiple variables using a walrus operator, and am just wondering what a successful multiple assignment would look like, or whether it is not possible.

The purpose of doing so is to add support for detecting all assigned variable names in my library mvdef (specifically, within the find_assigned_args function in the mvdef.src.ast_util module).

From running ast.parse I can see that the := operator produces an ast.NamedExpr node, and this has a .target attribute which is an ast.Name object, from which I can obtain the assigned name from the object's .id attribute.

If I had to guess, I'd presume that if it were to be at all possible, the .target attribute would be a list of ast.Name objects instead of a single ast.Name object, however the fact that I can't seem to get an example of this makes me wonder whether it is impossible, at least for the time being (in which case I can simplify my code and not just guess at what an implementation should be).

If anyone knows which specific part of the Python source to look at to tell me if this is possible or not, that'd be helpful, thanks!

P.S. - from looking at the test cases in Lib/test/test_parser.py provided in the initial commit (via), there don't seem to be examples of multiple assignments with the walrus operator, so I'm going to assume for now it's not possible (but please chime in if I'm wrong!)

def test_named_expressions(self):
    self.check_suite("(a := 1)")
    self.check_suite("(a := a)")
    self.check_suite("if (match := pattern.search(data)) is None: pass")
    self.check_suite("[y := f(x), y**2, y**3]")
    self.check_suite("filtered_data = [y for x in data if (y := f(x)) is None]")
    self.check_suite("(y := f(x))")
    self.check_suite("y0 = (y1 := f(x))")
    self.check_suite("foo(x=(y := f(x)))")
    self.check_suite("def foo(answer=(p := 42)): pass")
    self.check_suite("def foo(answer: (p := 42) = 5): pass")
    self.check_suite("lambda: (x := 1)")
    self.check_suite("(x := lambda: 1)")
    self.check_suite("(x := lambda: (y := 1))")  # not in PEP
    self.check_suite("lambda line: (m := re.match(pattern, line)) and m.group(1)")
    self.check_suite("x = (y := 0)")
    self.check_suite("(z:=(y:=(x:=0)))")
    self.check_suite("(info := (name, phone, *rest))")
    self.check_suite("(x:=1,2)")
    self.check_suite("(total := total + tax)")
    self.check_suite("len(lines := f.readlines())")
    self.check_suite("foo(x := 3, cat='vector')")
    self.check_suite("foo(cat=(category := 'vector'))")
    self.check_suite("if any(len(longline := l) >= 100 for l in lines): print(longline)")
    self.check_suite(
        "if env_base := os.environ.get('PYTHONUSERBASE', None): return env_base"
    )
    self.check_suite(
        "if self._is_special and (ans := self._check_nans(context=context)): return ans"
    )
    self.check_suite("foo(b := 2, a=1)")
    self.check_suite("foo(b := 2, a=1)")
    self.check_suite("foo((b := 2), a=1)")
    self.check_suite("foo(c=(b := 2), a=1)")


Solution

  • Iterable packing and unpacking is one difference between = and :=, with only the former supporting them. As found in PEP-572:

    # Equivalent needs extra parentheses
    loc = x, y  # Use (loc := (x, y))
    info = name, phone, *rest  # Use (info := (name, phone, *rest))
    
    # No equivalent
    px, py, pz = position
    name, phone, email, *other_info = contact