On Fri, 04 Nov 2005 21:14:17 -0500, Mike Meyer <[EMAIL PROTECTED]> wrote:
>[EMAIL PROTECTED] (Bengt Richter) writes: >> On Thu, 03 Nov 2005 13:37:08 -0500, Mike Meyer <[EMAIL PROTECTED]> wrote: >> [...] >>>> I think it even less sane, if the same occurce of b.a refers to two >>>> different objects, like in b.a += 2 >>> >>>That's a wart in +=, nothing less. The fix to that is to remove += >>>from the language, but it's a bit late for that. >>> >> Hm, "the" fix? Why wouldn't e.g. treating augassign as shorthand for a >> source transformation >> (i.e., asstgt <op>= expr becomes by simple text substitution asstgt = >> asstgt <op> expr) >> be as good a fix? Then we could discuss what >> >> b.a = b.a + 2 >> >> should mean ;-) > >The problem with += is how it behaves, not how you treat it. But you >can't treat it as a simple text substitution, because that would imply >that asstgt gets evaluated twice, which doesn't happen. I meant that it would _make_ that happen, and no one would wonder ;-) BTW, if b.a is evaluated once each for __get__ and __set__, does that not count as getting evaluated twice? >>> class shared(object): ... def __init__(self, v=0): self.v=v ... def __get__(self, *any): print '__get__'; return self.v ... def __set__(self, _, v): print '__set__'; self.v = v ... >>> class B(object): ... a = shared(1) ... >>> b=B() >>> b.a __get__ 1 >>> b.a += 2 __get__ __set__ >>> B.a __get__ 3 Same number of get/sets: >>> b.a = b.a + 10 __get__ __set__ >>> b.a __get__ 13 I posted the disassembly in another part of the thread, but I'll repeat: >>> def foo(): ... a.b += 2 ... a.b = a.b + 2 ... >>> import dis >>> dis.dis(foo) 2 0 LOAD_GLOBAL 0 (a) 3 DUP_TOP 4 LOAD_ATTR 1 (b) 7 LOAD_CONST 1 (2) 10 INPLACE_ADD 11 ROT_TWO 12 STORE_ATTR 1 (b) 3 15 LOAD_GLOBAL 0 (a) 18 LOAD_ATTR 1 (b) 21 LOAD_CONST 1 (2) 24 BINARY_ADD 25 LOAD_GLOBAL 0 (a) 28 STORE_ATTR 1 (b) 31 LOAD_CONST 0 (None) 34 RETURN_VALUE It looks like the thing that's done only once for += is the LOAD_GLOBAL (a) but DUP_TOP provides the two copies of the reference which are used either way with LOAD_ATTR followed by STORE_ATTR, which UIAM lead to the loading of the (descriptor above) attribute twice -- once each for the __GET__ and __SET__ calls respectively logged either way above. > >> OTOH, we could discuss how you can confuse yourself with the results of b.a >> += 2 >> after defining a class variable "a" as an instance of a class defining >> __iadd__ ;-) > >You may confuse yourself that way, I don't have any problems with it >per se. I should have said "one can confuse oneself," sorry ;-) Anyway, I wondered about the semantics of defining __iadd__, since it seems to work just like __add__ except for allowing you to know what source got you there. So whatever you return (unless you otherwise intercept instance attribute binding) will get bound to the instance, even though you internally mutated the target and return None by default (which gives me the idea of returning NotImplemented, but (see below) even that gets bound :-( BTW, semantically does/should not __iadd__ really implement a _statement_ and therefore have no business returning any expression value to bind anywhere? >>> class DoIadd(object): ... def __init__(self, v=0, **kw): ... self.v = v ... self.kw = kw ... def __iadd__(self, other): ... print '__iadd__(%r, %r) => '%(self, other), ... self.v += other ... retv = self.kw.get('retv', self.v) ... print repr(retv) ... return retv ... >>> class B(object): ... a = DoIadd(1) ... >>> b=B() >>> b.a <__main__.DoIadd object at 0x02EF374C> >>> b.a.v 1 The normal(?) mutating way: >>> b.a += 2 __iadd__(<__main__.DoIadd object at 0x02EF374C>, 2) => 3 >>> vars(b) {'a': 3} >>> B.a <__main__.DoIadd object at 0x02EF374C> >>> B.a.v 3 Now fake attempt to mutate self without returning anything (=> None) >>> B.a = DoIadd(1, retv=None) # naive default >>> b.a 3 Oops, remove instance attr >>> del b.a >>> b.a <__main__.DoIadd object at 0x02EF3D6C> >>> b.a.v 1 Ok, now try it >>> b.a +=2 __iadd__(<__main__.DoIadd object at 0x02EF3D6C>, 2) => None >>> vars(b) {'a': None} Returned value None still got bound to instance >>> B.a.v 3 Mutation did happen as planned Now let's try NotImplemented as a return >>> B.a = DoIadd(1, retv=NotImplemented) # mutate but probably do __add__ too >>> del b.a >>> b.a <__main__.DoIadd object at 0x02EF374C> >>> b.a.v 1 >>> b.a +=2 __iadd__(<__main__.DoIadd object at 0x02EF374C>, 2) => NotImplemented __iadd__(<__main__.DoIadd object at 0x02EF374C>, 2) => NotImplemented >>> vars(b) {'a': NotImplemented} >>> B.a.v 5 No problem with that? ;-) I'd say it looks like someone got tired of implementing __iadd__ since it's too easy to work around the problem. If _returning_ NotImplemented could have the meaning that return value processing (binding) should not be effected, then mutation could happen without a second evaluation of b.a as a target. ISTM a return value for __iadd__ is kind of strange in any case, since it's a statement implementation, not an expression term implementation. > >> Or point out that you can define descriptors (or use property to make it >> easy) >> to control what happens, pretty much in as much detail as you can describe >> requirements ;-) > >I've already pointed that out. Sorry, missed it. Big thread ;-) Regards, Bengt Richter -- http://mail.python.org/mailman/listinfo/python-list