Class Variable Access and Assignment
bokr at oz.net
Sun Nov 6 00:15:30 CET 2005
On Sat, 05 Nov 2005 14:37:19 +1100, Steven D'Aprano <steve at REMOVETHIScyber.com.au> wrote:
>On Sat, 05 Nov 2005 00:25:34 +0000, Bengt Richter wrote:
>> On Fri, 04 Nov 2005 02:59:35 +1100, Steven D'Aprano <steve at REMOVETHIScyber.com.au> wrote:
>>>On Thu, 03 Nov 2005 14:13:13 +0000, Antoon Pardon wrote:
>>>> Fine, we have the code:
>>>> b.a += 2
>>>> We found the class variable, because there is no instance variable,
>>>> then why is the class variable not incremented by two now?
>> Because the class variable doesn't define a self-mutating __iadd__
>> (which is because it's an immutable int, of course). If you want
>> b.__dict__['a'] += 2 or b.__class__.__dict__['a'] += 2 you can
>> always write it that way ;-)
>> (Of course, you can use a descriptor to define pretty much whatever semantics
>> you want, when it comes to attributes).
>>>Because b.a += 2 expands to b.a = b.a + 2. Why would you want b.a =
>> No, it doesn't expand like that. (Although, BTW, a custom import could
>> make it so by transforming the AST before compiling it ;-)
>> Note BINARY_ADD is not INPLACE_ADD:
>Think about *what* b.a += 2 does, not *how* it does it. Perhaps for some
what it does, or what in the abstract it was intended to do? (which we need
BDFL channeling to know for sure ;-)
It looks like it means, "add two to <whatever b.a is>". I think Antoon
is unhappy that <whatever b.a is> is not determined once for the one b.a
expression in the statement. I sympathize, though it's a matter of defining
what b.a += 2 is really intended to mean.
The parses are certainly distinguishable:
>>> import compiler
>>> compiler.parse('b.a +=2','exec').node
Stmt([AugAssign(Getattr(Name('b'), 'a'), '+=', Const(2))])
>>> compiler.parse('b.a = b.a + 2','exec').node
Stmt([Assign([AssAttr(Name('b'), 'a', 'OP_ASSIGN')], Add((Getattr(Name('b'), 'a'), Const(2))))])
Which I think leads to the different (BINARY_ADD vs INPLACE_ADD) code, which probably really
ought to have a conditional STORE_ATTR for the result of INPLACE_ADD, so that if __iadd__
was defined, it would be assumed that the object took care of everything (normally mutating itself)
and no STORE_ATTR should be done. But that's not the way it works now. (See also my reply to Mike).
Perhaps all types that want to be usable with inplace ops ought to inherit from some base providing
that, and there should never be a return value. This would be tricky for immutables though, since
re-binding is necessary, and the __iadd__ method would have to be passed the necessary binding context
and methods. Probably too much of a rewrite to be practical.
>other data type it would make a difference whether the mechanism was
>BINARY_ADD (__add__) or INPLACE_ADD (__iadd__), but in this case it does
>not. Both of them do the same thing.
Unfortunately you seem to be right in this case.
>Actually, no "perhaps" about it -- we've already discussed the case of
Well, custom objects have to be considered too. And where attribute access
is involved, descriptors.
>Sometimes implementation makes a difference. I assume BINARY_ADD and
>INPLACE_ADD work significantly differently for lists, because their
>results are significantly (but subtly) different:
>py> L = [1,2,3]; id(L)
>py> L += [4,5]; id(L)
>py> L = L + ; id(L)
>But all of this is irrelevant to the discussion about binding b.a
>differently on the left and right sides of the equals sign. We have
>discussed that the behaviour is different with mutable objects, because
>they are mutable -- if I recall correctly, I was the first one in this
>thread to bring up the different behaviour when you append to a list
>rather than reassign, that is, modify the class attribute in place.
>I'll admit that my choice of terminology was not the best, but it wasn't
>misleading. b.a += 2 can not modify ints in place, and so the
>effect of b.a += 2 is the same as b.a = b.a + 2, regardless of what
>byte-codes are used, or even what C code eventually implements that
It is so currently, but that doesn't mean that it couldn't be otherwise.
I think there is some sense to the idea that b.a should be re-bound in
the same namespace where it was found with the single apparent evaluation
of "b.a" in "b.a += 2" (which incidentally is Antoon's point, I think).
This is just for augassign, of course.
OTOH, this would be find-and-rebind logic for attributes when augassigned,
and that would enable some tricky name-collision bugs for typos, and code
that used instance.attr += incr depending on current behavior would break.
>In the case of lists, setting Class.a =  and then calling instance.a +=
> would not exhibit the behaviour Antoon does not like, because the
>addition is done in place. But calling instance.a = instance.a + 
>My question still stands: why would you want instance.a = <something>
>to operate as instance.__class__.a = <something>?
Because in the case of instance.a += <increment>, "instance.a"
is a short spelling for "instance.__class__.a" (in the limited case we are discussing),
and that spelling specifies _both_ source and target in a _single_ expression,
unlike instance.a = instance.a + <incr> where two expressions are used, which
one should expect to have their meaning accoring to the dynamic moment and
context of their evaluation.
If 'a' in vars(instance) then instance.a has the meaning instance.__dict__['a']
for both source and target of +=.
I think you can argue for the status quo or find-and-rebind, but since there
are adequate workarounds to let you do what you want, I don't expect a change.
I do think that returning NotImplemented from __iadd__ to indicate no binding
of return value desired (as opposed to __iadd__ itself not implemented, which
is detected before the call) might make things more controllable for custom objects.
Sorry about cramming too much into sentences ;-/
More information about the Python-list