[Python-ideas] Augmented assignment syntax for objects.

Chris Angelico rosuav at gmail.com
Fri Apr 28 14:01:18 EDT 2017


On Sat, Apr 29, 2017 at 1:31 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Fri, Apr 28, 2017 at 05:23:59PM +1000, Chris Angelico wrote:
> __init__ is called, the argument 42 is bound to the formal parameter
> "attr", the assignment self.attr = attr is run, and THEN the body of the
> method is called. (Before you object, see below for justification for
> why there has to be both a local variable and an attribute.)
>
> If there is an exception, what do you think the stack trace will point
> to? It cannot point to a line of source code that says "self.attr =
> attr", because there is no such line. At best, it can point to the
> declaration "def __init__(self, self.attr):" although it might not even
> do that.

I don't see why it couldn't. You'll get an AttributeError trying to
set "attr", or maybe it'll be an error from inside __setattr__, but
either way, it's going to come from the 'def' line.

>> > Why should it be defined in terms of general assignment? That's the
>> > point I'm making. While function sigs are a form of assignment, they're
>> > more of a declaration than an executable statement like the other
>> > binding statements. There's a superficial connection, but they really
>> > are quite different things.
>>
>> There's a lot more similarities than you might think. For example,
>> both of these will create "spam" as a local name, shadowing the
>> global:
>>
>> spam = 1
>> def func1(spam):
>>     print(spam)
>> def func2():
>>     spam = 2
>>     print(spam)
>
> Right. Function parameters are local variables. But you can't write:
>
> def func(global spam):
>
> to make it assign to a global instead of a local, or nonlocal, and you
> can't (currently) write:
>
> def func(math.spam):
>
> to make it assign to an attribute of the math module. It's a big
> conceptual leap to go from "formal parameters are always bound to local
> variables of the function" to "formal parameters of the function are
> bound to anything, anywhere".
>
> I wonder whether any other language allows this?

It's a big conceptual leap to go from "iteration steps through a
sequence, binding a variable to successive items in it" to the full
flexibility of Python's for loop. Just look at this:

for idx, val in enumerate(iterable):

It's completely intuitive to an expert Python programmer because it's
a common idiom, but think about it. Firstly, you create an iterable as
a derivative of another iterable. We're building not just lists out of
lists, but lazy iterables out of each other. And that iterable yields
two-item tuples. Finally, we do an unpacking assignment, stashing the
two items into two separate names. And nobody is suggesting that we
should restrict this syntax to simple examples like this; there is
absolutely nothing wrong with having incredibly complicated and
ridiculous assignments inside a for loop. Why? Because a for loop does
simple assignment, nothing more, nothing less.

>> As will many other forms of assignment, like "import spam" or "with x
>> as spam:". Some forms are more restricted than others ("import"
>> requires a NAME, not an arbitrary expression),
>
> Yes, we could restrict the syntax to simple identifiers with a maximum
> of a single dot:
>
>     def __init__(self, self.attr,   # allowed
>                        self[0].foo(1)['key'],  # SyntaxError
>                        ):
>
> which will avoid the worst of the horrors.

Yeah. In that sense, it actually is more like decorator syntax, which
is defined as a strict subset of expression syntax - you have an
absolute guarantee that any legal decorator is semantically equivalent
to the same expression elsewhere, but there are lots of expressions
that you can't use in a decorator. I'd be fine with that. It's still
defined in terms of assignment, but your example would indeed give a
SyntaxError.

>> > You need to speak to more beginners if you think the connection between
>> > spam.x and x is obvious:
>> >
>> >     def func(spam.x):
>> >         print(x)
>> >
>> > Where is x declared? It looks like there's a local spam.x which isn't
>> > used, and a global x that is. But that's completely wrong. Without the
>> > context of somebody telling you "this is syntax for magically assigning
>> > to self.attributes in the constructor", I believe this will be
>> > unfathomable to the average non-expert.
>>
>> Not sure what you mean. By my reading, that's exactly correct - there
>> IS a global x that is being used here,
>
> No there isn't. "x" is the parameter name, even though it is written
> with a "spam." prefix. Let's go back to the original:
>
> class MyClass:
>     def __init__(self, self.attr):
>         ...
>
> instance = MyClass(42)
>
> That binds 42 to the local variable "attr", as well as doing the
> self.attr=attr assignment. After all, surely we want to be able to refer
> to the parameter by its local variable, for efficiency (avoiding
> redundant look-ups of self), or to by-pass any __getattr__ on self.

Waaaaaaaaaait. Why? If you're slapping it in there, you should have no
guarantee that it exists under any other name.

> Or to really push the message, think about calling it by keyword:
>
> instance = MyClass(self.attr=42)  # No.
> instance = MyClass(attr=42)  # Yes.
>
> In other words, even though the formal parameter is written as
> "self.attr", the argument is bound to the local "attr".
>
> Quite frankly, I thought this was so self-evident that it didn't even
> occur to me that anyone would have imagined that no local variable
> "attr" would be created. As weird as it is to have a parameter declared
> as "self.attr" but bound to "attr", that's not as weird as having a
> parameter declared as "self.attr" but not bound to any local at all.

No way is that self-evident. If you want something in two places, you
put it there yourself. The parameter puts the value into exactly one
place. In all my examples of equivalence, there was not a single hint
of a local name "attr". Maybe that's why you consider this weird?
Because your imagined semantics are NOT equivalent to assignment?

> (If instance attributes and locals used the same notation, namely a bare
> identifier, we could gloss over this distinction. But we're not using
> Java :-)

Haha. Yeah, well, that actually doesn't work - or at least, not in C++
(not sure about Java but I expect it's the same). You can have locals
and instance attributes with the same names, and then you refer to the
latter as "this->foo". So it still wouldn't help. :)

ChrisA


More information about the Python-ideas mailing list