[Python-ideas] Augmented assignment syntax for objects.

Steven D'Aprano steve at pearwood.info
Fri Apr 28 11:31:44 EDT 2017


On Fri, Apr 28, 2017 at 05:23:59PM +1000, Chris Angelico wrote:

> Waaaaaait a minute. Since when is the *declaration* doing this? 

That's what the suggested syntax says. You put the attribute assignment 
you want in the parameter list, which is part of the function/method 
declaration, not the body. If I recall correctly, the proposal was 
exactly:

    def __init__(self, self.attr):

That's not in the body of the function, it's in the parameter list, 
hence part of the declaration. When you call the constructor:

instance = MyClass(42)

__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.


> No no no. In every proposal, it's been a part of the function's *execution*.

I did explicitly state that the attribute assignment doesn't occur until 
the function is called, so I'm aware of this. Nevertheless, the source 
code being executed at function call time does not reside in the body of 
the function, but in the function signature.

Maybe I'd be less disturbed by this if we had late binding of defaults 
(defaults are re-evaluated each time they are needed), and a history of 
abusing that for the side-effects:

def function(a, b=print("using b default") or default):

But Python function defaults are early binding, and they're only 
evaluated once, so there's currently no analogy to this proposal.


[...]
> > 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?


> 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.


[...]
> > 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.

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.

(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 :-)


-- 
Steve


More information about the Python-ideas mailing list