Dot equals operator (.=)

I’m certain this has been covered previously. That said, I’ve struggled to find any older discussions. Does anyone else feel an operator that replaces an existing variable with itself, after having a method applied, would be useful? Something along the lines of:

foo .= method(x, y, z)

as being equivalent to

foo = foo.method(x, y, z)

Using vscode to scan cpython repo seems to suggest it’s a common pattern (>1k matches).

image

I suspect it’s been considered before but rejected for an obvious reason (that’s eluding me).

3 Likes

Here’s an earlier discussion: '.=' assignment

4 Likes

Thank you. I did search for “.=” here on discourse but got a “Your search term is too short.” warning instead. Alas, from that thread, this seems to be an unpopular idea. Oh well.

This pattern of methods returning a “clone but with some difference” is getting more common as immutable classes are becoming widely used.
(There’s no true immutability in pure Python classes yet, but I digress.)

It is something that the Python community encourages nowadays, but at this stage I don’t think we need what’s essentially a syntactic sugar just to promote the use of the immutable class pattern. As it currently stands this is what I’d refer to as a solution waiting for an upcoming problem. If Python one day decides to add syntax support for true immutability as first-class concept (free-threading / JIT will likely create many use cases for such), the necessity of such things as .= can be revisited.

Though it may be a good idea to reserve this syntax now, to be implemented when the use cases become clearer in the future.

Edit: punctuations

1 Like

I like the idea, and I don’t see what can be so confusing about the syntax. Since . is already the attribute lookup operator, .= intuitively registers in my mind as its augmented assignment version.

I personally would use this operator often to apply filters to a QuerySet conditionally in Django, and I’m sure there are many other libraries that share a similar usage pattern.

Note however that whereas everything to the right of an existing augmented assignment can be an arbitrary expression, the right of .= would have to be strictly a name, so an implementation of .= would involve more than a simple expansion of the augassign grammar rule.

1 Like

Augmented assignment isn’t meant to be pure syntactic sugar. The intention is to allow the object to choose how it implements it, potentially including in-place modification. For example, these two are notably different:

stuff = [1, 2, 3]
stuff += [4, 5, 6]
stuff = stuff + [7, 8, 9]

Both of them will result in stuff having three more things on it, but one of them mutates the existing list, the other copies it and then adds to the new one.

What would .= do along these lines? Would an object have a chance to redefine what that does?

2 Likes

Drawing parallel to existing augmented assignments makes the .= syntax easy to understand since they do share conceptual similarities, but yes the similarities end there.

Like ., the RHS of .= is treated as a name rather than an expression, and
foo .= attribute is going to be indeed largely syntactic sugar of foo = foo.attribute with the only differences being:

  1. That foo is evaluated only once, which can be significant if it involves complex subscripts.
  2. That foo is evaluated only once provides optimization opportunites at the bytecode level.

I don’t think it makes sense for .= to be able to be separately redefined (on top of how . is already able to be redefined with __getattr__ and __getattribute__).

So yeah, while there are benefits in ergonomics and optimization opportunities from the new syntax, there isn’t a whole lot beyond syntactic sugar to make this idea more compelling than a nice-to-have.

1 Like

Yes, that’s a very minor advantage, but not enough to justify a new operator. Note that this advantage doesn’t exist with the simple example of foo = foo.attribute as there’s nothing TO evaluate, so you’d only see this sort of thing if you’re doing something like a.b.c.d.e = a.b.c.d.e.attribute and that’s not nearly as common.

Exactly. With all other augmented operators, they can be separately redefined - that’s why they exist. With this one, it would be nothing more than syntactic sugar. That’s not to say that syntactic sugar is BAD, of course, but it’s a much weaker justification than actual expressiveness.

Right, though in this particular case I think this pattern may be used often enough for the improved ergonomics alone to justify the new syntax (as seen in the OP’s code search).

I’m curious if the walrus operator is considered an augmented operator? I find myself using “:=” a lot these days; it really does simplify a lot of code as well as avoiding “double” calls to functions like len etc.

The walrus operator does not “augment” the operand in any way so it is not an augmented operator, but it’s a great precedent of pure syntactic sugar making its way to the Python language.

1 Like

I’m generally opposed to this operator, but if it were implemented, I think it would make more sense in the form of = . rather than .=.
I also think that this ‘operator’ would be better implemented as a kind of syntactic sugar that is resolved at compile time, rather than as an in-place operator.

line = .strip() # same as line = line.strip()
5 Likes

It’s not - there’s no “do a thing and assign” equivalent (there’s no x : y operation that could be done as x = x : y). It’s an assignment expression, which is a different variation on assignment.

Anything can be described as “pure syntactic sugar”, since everything can be implemented in some other way. Check out these blog posts by Brett Cannon for some impressive examples of how much of Python can be rewritten in other ways. The point is, though, that expressiveness is the goal. We want to be able to better write the programmer’s intent. Python, as a general rule, does a good job of this. Want a really good demonstration of intent vs effect? Write a nice piece of idiomatic Python code, then run it through dis.dis() and read through the ACTUAL steps that have to be performed. Believe me, you wouldn’t want to have to work with THAT as your source code!!

In a sense, from the moment Python became Turing-complete, every new feature has just been syntactic sugar. But as someone who’s sitting here with a tub of jelly beans beside me, sugar’s a good thing when used correctly.

4 Likes

What makes you think foo would only be evaluated once? Consider something like x[1] .= y(). Reduced to primitives, that does setitem(x, 1, getitem(x, 1).y()). That’s the same as x[1] = x[1].y(), and in neither case does x[1] get evaluated twice…

Of course, in x[1][2][3][4] .= y(), the subexpression x[1][2][3] is evaluated once rather than twice, but you can write that as element = x[1][2][3]; element[4] = element[4].y() and get the same effect in terms of not evaluating things twice.

So I think the only actual benefit here is not having to type a complex expression twice, which is a genuine benefit, but unlikely to be sufficient to justify this syntax change on its own.

As a side note, when I wrote x[1] .= y() above, I had to hesitate to be sure I’d written it correctly. I wanted to write .y - it appears that I think of a method call or attribute access as very much including the dot, and separating the two in the way that the .= operator does adds a significant readability (and writeability) degradation. And not one that I would have imagined before I tried to write that example…

2 Likes

In your example, x and 1 would be evaluated twice. In contrast, x[1] += 2 only evaluates them once. It’s an extremely minor distinction, but could be of value occasionally; for example, I am quite happy to write code like:

count[whatever()] += 1`

which is guaranteed to call the function precisely once, and reuse its return value. I would expect that, if a .= operator were to be added, it would also have this behaviour.

(But I don’t think .= is of value.)

2 Likes

This is what I want. I know it’s perhaps a bit selfish. I would like to reduce how much typing I do & reduce some screen real estate at the same time. There’s also a smaller capacity of introducing bugs (eg did i mean df1 = df11.reset_index() or df1 .= reset_index()).

Someone else mentioned, if implemented, they’d prefer “=.” to “.=”. Although, I feel this would play against my muscle memory but I’m sure I’d eventually get used to it. The order of tokens wouldn’t be affected in the same way B/C were when they initially chose “=+” to “+=”.

This is an interesting twist. Initially, it would likely play with my muscle memory but I’d probably get used to it.

1 Like

Thank you for being clear. It’s an extremely reasonable thing to want, and in languages that have a feature like this, it can result in a terse, expressive style that feels extremely natural and comfortable.

But (and you knew there was a “but” coming, didn’t you?) it’s nowhere near as reasonable to add to Python. The problem is twofold - the obvious one, which everyone will mention straight away, is that Python has many years of history at this point, which means a lot of training materials and information that would need updating, an ecosystem of tools (linters, type checkers, etc) that need modifying, etc. That’s a lot of work for a relatively minor change.

The second problem is harder to describe precisely, but it’s what people mean when they talk about whether something is “Pythonic”. Idiomatic Python code doesn’t tend to value terseness as much as some other languages. Using an extra variable to name a subexpression is seen as perfectly OK, for example. As is the occasional need to repeat yourself. So it’s not at all clear that a .= operator would fit well in idiomatic Python code. Idioms change, of course (Python originally didn’t have generator expressions, and now I couldn’t imagine Python code without them), but not necessarily for something this small.

Personally, I can imagine using this feature if it was added. Not often, and I won’t miss it if it’s not added, but it might occasionally simplify a really complex expression that I don’t have the time to refactor properly. But if I had my choice, it’s not what I want the Python developers to spend their time on.

9 Likes

I agree with this proposal, but I also agree that it’s only syntactic sugar. Here’s where I wanted to use it today:

some_set.=union(some_other_set)