Class Variable Access and Assignment

S

Steven D'Aprano

For the record, I've only seen one interpreter that actually
interpreted the source directly. Pretty much all of the rest of them
do a lexical analysis, turning keywords into magic tokens (dare I say
"byte codes") and removing as much white space as possible. Or maybe
that's what you meant?

We could argue about details of a throw away line for hours :)

What I meant was, there is the way Python does it, and then there are (or
were) interpreters that when faced with a block like this:

for i in range(10):
print i

parses "print i" ten times.

It doesn't really matter whether any interpreters back in the 1970s were
actually that bad, or just toy interpreters as taught about in undergrad
university courses.
 
S

Steven D'Aprano

So add:

self.size = Paper.size

and you've removed the weirdness. What do you gain here by inheriting?


Documents which don't care what paper size they are will automatically use
the default paper size on whatever system they are opened under. Send them
to somebody in the US, and they will use USLetter. Send to someone in
Australia, and they will use A4.

In any case, even if you conclude that there is little benefit to
inheritance in this particular example, the principle is sound:
sometimes you gain benefit by inheriting state.
 
B

Bengt Richter

Small correction, it expands to b.a = B.a.__class__.__iadd__(b.a,2),
assuming all relevant quantities are defined. For integers, you're
perfectly right.
But before you get to that, a (possibly inherited) type(b).a better
not have a __get__ method trumping __class__ and the rest ;-)

Regards,
Bengt Richter
 
B

Bengt Richter

[email protected] (Bengt Richter) said:
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

Consider "a[f()] += 3". You don't want to eval f() twice.

Well, if you accepted macro semantics IWT you _would_ want to ;-)

Hm, reminds me of typical adding of parens in macros to control precedence
in expressions ... so I tried
SyntaxError: augmented assign to tuple literal or generator expression not possible

;-/

Regards,
Bengt Richter
 
B

Bengt Richter

This isn't an implementation detail; to leading order, anything that
impacts the values of objects attached to names is a specification issue.

An implementation detail is something like when garbage collection
actually happens; what happens to:

b.a += 2

is very much within the language specification. Indeed, the language
specification dictates that an instance variable b.a is created if one
didn't exist before; this is true no matter if type(b.a) == int, or if
b.a is some esoteric mutable object that just happens to define
__iadd__(self,type(other) == int).
But if it is an esoteric descriptor (or even a simple property, which is
a descriptor), the behaviour will depend on the descriptor, and an instance
variable can be created or not, as desired, along with any side effect you like.

Regards,
Bengt Richter
 
S

Steven D'Aprano

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

Actually, no "perhaps" about it -- we've already discussed the case of
lists.

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)
-151501076
py> L += [4,5]; id(L)
-151501076
py> L = L + []; id(L)
-151501428


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
add-and-store.

In the case of lists, setting Class.a = [] and then calling instance.a +=
[1] would not exhibit the behaviour Antoon does not like, because the
addition is done in place. But calling instance.a = instance.a + [1]
would.

My question still stands: why would you want instance.a = <something>
to operate as instance.__class__.a = <something>?
 
S

Steven D'Aprano

That's bogus. Initialize the current level in the __init__ method
where it belongs.

It might be bogus to you, but it isn't to me. I prefer to delay setting
instance attributes until they are needed.

It also allows you to do something like this:

class ExpertGame(Game):
current_level = 100


and then use ExpertGame anywhere you would have used Game with no problems.

Yes, there are other ways to do this. I won't say they are wrong, but I
don't believe they are better.
 
S

Steven D'Aprano

There are good use cases for a lot of things python doesn't provide.
There are good use cases for writable closures, but python doesn't
provide it, shrug, I can live with that. Use cases is a red herring
here.

Is that a round-about way of saying that you really have no idea of
whether, how or when your proposed behaviour would be useful?

Personally, I think that when you are proposing a major change to a
language that would break the way inheritance works, there should be more
benefits to the new way than the old way.

I didn't call the model for inheritance insane.

Antoon, I've been pedanted at by experts, and you ain't one. The behaviour
which you repeatedly described as not sane implements the model for
inheritance. The fact that you never explicitly said "the standard OO
model of inheritance" cuts no ice with me, not when you've written
multiple posts saying that the behaviour of that standard inheritance
model is not sane.

Please don't make this about what I *want*. I don't want anything. I
just noted that one and the same reference can be processed multiple
times by the python machinery, resulting in that same reference
referencing differnt variables at the same time and stated that that was
unsane behaviour.

"Unsane" now?

Heaven forbid that I should criticise people for inventing new words, but
how precisely is unsane different from insane? In standard English,
something which is not sane is insane.

You don't know what I want. You only know that I have my criticism of
particular behaviour. You seem to have your idea about what the
alternative would be like, and project that to what I would want.

Well now is a good time for you to stop being so coy and tell us what you
want. You don't like the current behaviour. So what is your alternative?
I've given you some suggestions for alternative behaviour. You've refused
to say which one you prefer, or suggest your own.

If you're just trolling, you've done a great job of it because you fooled
me well and good. But if you are serious in your criticism about the
behaviour, then stop mucking about and tell us what the behaviour should
be. Otherwise your criticism isn't going to have any practical effect on
the language at all.


What my behaviour? I don't need to specify alternative behaviour in
order to judge specific behaviour.

If you are serious about wanting the behaviour changed, and not just
whining, then somebody has to come up with an alternative behaviour that
is better. If not you, then who? Most of the folks who have commented on
this thread seem to like the existing behaviour.
 
P

Paul Rubin

Steven D'Aprano said:
It also allows you to do something like this:

class ExpertGame(Game):
current_level = 100
and then use ExpertGame anywhere you would have used Game with no problems.

Well, let's say you set, hmm, current_score = 100 instead of current_level.
Scores in some games can get pretty large as you get to the higher
levels, enough so that you start needing long ints, which maybe are
used elsewhere in your game too, like for the cryptographic signatures
that authenticate the pieces of treasure in the dungeon. Next you get
some performance gain by using gmpy to handle the long int arithmetic,
and guess what? Eventually a version of your game comes along that
enables the postulated (but not yet implemented) mutable int feature
of gmpy for yet more performance gains. So now, current_score += 3000
increments the class variable instead of creating an instance
variable, and whoever maintains your code by then now has a very weird
bug to track down and fix.

Anyway, I'm reacting pretty badly to the construction you're
describing. I haven't gotten around to looking at the asyncore code
but will try to do so.
 
M

Mike Meyer

[email protected] (Bengt Richter) said:
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

Consider "a[f()] += 3". You don't want to eval f() twice.

Well, if you accepted macro semantics IWT you _would_ want to ;-)

Another one of those throw-away lines.

I'd say that was true only if the macro was poorly written. Unless a
macros is intended as a tool to repeatedly evaluate an argument, it
should only evaluate it at most once.

Of course, if you're using some rock-stupid textual macro system, you
really don't have much choice in the matter.

<mike
 
M

Mike Meyer

Paul Rubin said:
Well, let's say you set, hmm, current_score = 100 instead of current_level.
Scores in some games can get pretty large as you get to the higher
levels, enough so that you start needing long ints, which maybe are
used elsewhere in your game too, like for the cryptographic signatures
that authenticate the pieces of treasure in the dungeon. Next you get
some performance gain by using gmpy to handle the long int arithmetic,
and guess what? Eventually a version of your game comes along that
enables the postulated (but not yet implemented) mutable int feature
of gmpy for yet more performance gains. So now, current_score += 3000
increments the class variable instead of creating an instance
variable, and whoever maintains your code by then now has a very weird
bug to track down and fix.

I'd say that's a wart with +=, not with Python's inheritance
mechanisms. += is neither + nor =, but takes on different aspects of
each depending on what it's operating on. While it's true that python
is dynamic enough that the you can create classes that make this true
for any operator, += is the only one that acts like that on the
builtin types.

<mike
 
S

Steve Holden

Paul said:
Well, let's say you set, hmm, current_score = 100 instead of current_level.
Scores in some games can get pretty large as you get to the higher
levels, enough so that you start needing long ints, which maybe are
used elsewhere in your game too, like for the cryptographic signatures
that authenticate the pieces of treasure in the dungeon. Next you get
some performance gain by using gmpy to handle the long int arithmetic,
and guess what? Eventually a version of your game comes along that
enables the postulated (but not yet implemented) mutable int feature
of gmpy for yet more performance gains. So now, current_score += 3000
increments the class variable instead of creating an instance
variable, and whoever maintains your code by then now has a very weird
bug to track down and fix.

Anyway, I'm reacting pretty badly to the construction you're
describing. I haven't gotten around to looking at the asyncore code
but will try to do so.

I wouldn't bother. From memory it's just using a class variable as an
initialiser.

regards
Steve
 
S

Steven D'Aprano

Well, let's say you set, hmm, current_score = 100 instead of current_level.

Right. Because inheriting scores makes so much sense. But that's okay.
Assume I think of some use for inheriting scores and implement this.
Scores in some games can get pretty large as you get to the higher
levels, enough so that you start needing long ints, which maybe are
used elsewhere in your game too, like for the cryptographic signatures
that authenticate the pieces of treasure in the dungeon.

Python already converts ints to long automatically, but please, do go on.
Next you get
some performance gain by using gmpy to handle the long int arithmetic,

Then whatever happens next will be my own stupid fault for prematurely
optimising code.

and guess what? Eventually a version of your game comes along that
enables the postulated (but not yet implemented) mutable int feature
of gmpy for yet more performance gains.

This would be using Python3 or Python4?
So now, current_score += 3000 increments the class variable instead of
creating an instance variable, and whoever maintains your code by then
now has a very weird bug to track down and fix.

That's a lot of words to say "If ints become mutable when you expect
them to be immutable, things will go badly for you." Well duh.

What exactly is your point? That bugs can happen if the behaviour of your
underlying libraries changes? If list.sort suddenly starts randomizing the
list instead of sorting it, I'll have bugs too. Should I avoid using sort
just in case?
 
P

Paul Rubin

Steven D'Aprano said:
Then whatever happens next will be my own stupid fault for prematurely optimising code.

Huh? There's nothing premature about using gmpy if you need better long int performance.
It was written for a reason, after all.
This would be using Python3 or Python4?

No, it would be a gmpy feature, not a Python feature. So it could be
used with any version of Python.
What exactly is your point? That bugs can happen if the behaviour of your
underlying libraries changes?

That your initialization scheme is brittle--the idea of data
abstraction is being able to change object behaviors -without- making
surprising bugs like that one. You don't even need the contrived gmpy
example. You might replace the level number with, say, a list of
levels that have been visited.

I don't think the culprit is the mutable/immutable distinction +=
uses, though that is certainly somewhat odd. I think Antoon is on the
right track: namespaces in Python live in sort of a ghetto unbecoming
of how the Zen list describes them as a "honking great idea". These
things we call variables are boxed objects where the namespace is the
box. So having x+=y resolve x to a slot in a namespace before
incrementing that same slot by y, maybe better uses the notion of
namespaces than what happens now. I'm too sleepy to see for sure
whether it gets rid of the mutable/immutable weirdness.
 
B

Bengt Richter

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?
... 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
... ... a = shared(1)
... __get__
1 __get__
__set__ __get__
3

Same number of get/sets:
__get__
__set__ __get__
13

I posted the disassembly in another part of the thread, but I'll repeat:
... a.b += 2
... a.b = a.b + 2
... 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.
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?
... 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
... ... a = DoIadd(1)
... 1

The normal(?) mutating way: 3

Now fake attempt to mutate self without returning anything (=> None) 3
Oops, remove instance attr 1
Ok, now try it {'a': None}
Returned value None still got bound to instance 3
Mutation did happen as planned

Now let's try NotImplemented as a return 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.
I've already pointed that out.
Sorry, missed it. Big thread ;-)

Regards,
Bengt Richter
 
B

Bengt Richter

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
lists.
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)
-151501076
py> L += [4,5]; id(L)
-151501076
py> L = L + []; id(L)
-151501428
Yes.

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
add-and-store.
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 +=
[1] would not exhibit the behaviour Antoon does not like, because the
addition is done in place. But calling instance.a = instance.a + [1]
would.

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 ;-/

Regards,
Bengt Richter
 
S

Steven D'Aprano

BTW, semantically does/should not __iadd__ really implement a _statement_ and therefore
have no business returning any expression value to bind anywhere?

We get to practicality versus purity here.

Consider x += y for some object type x. If x is a mutable object, then
__iadd__ could be a statement, because it can/should/must modify x in
place. That is the pure solution.

But do you want x += y to work for immutable objects as well? Then
__iadd__ cannot be a statement, because x can't be modified in place.
Our pure "add in place" solution fails in practice, unless we needlessly
restrict what can use it, or have the same syntactical expression (x +=
y) bind to two different methods (__iadd__ statement, and __riadd__
function, r for return). Either pure solution is yucky. (That's a
technical term for "it sucks".) So for practical reasons, __iadd__ can't
be a statement, it needs to return an object which gets bound to x.

Fortunately, that behaviour works for mutables as well, because __iadd__
simply returns self, which gets re-bound to x.

While I am enjoying the hoops people are jumping through to modify the
language so that b.a += 2 assigns b.a in the same scope as it was
accessed, I'm still rather perplexed as to why you would want that
behaviour. It seems to me like spending many hours building a wonderfully
polished, ornate, exquisite device for building hiking boots for mountain
goats.
 
S

Steven D'Aprano

Huh? There's nothing premature about using gmpy if you need better long int performance.
It was written for a reason, after all.

Sure, but I would be willing to bet that incrementing a counter isn't it.

That your initialization scheme is brittle--the idea of data
abstraction is being able to change object behaviors -without- making
surprising bugs like that one. You don't even need the contrived gmpy
example. You might replace the level number with, say, a list of
levels that have been visited.

Do you expect level += 1 to still work when you change level to a list of
levels?

The problem with data abstraction is if you take it seriously, it means
"You should be able to do anything with anything". If I change
object.__dict__ to None, attribute lookup should work, yes? No? Then
Python isn't sufficiently abstract.

As soon as you accept that there are some things you can't do with some
data, you have to stop abstracting. *Prematurely* locking yourself into
one *specific* data structure is bad: as a basic principle, data
abstraction is very valuable -- but in practice there comes a time where
you have to say "Look, just choose a damn design and live with it." If you
choose sensibly, then it won't matter if your counter is an int or a long
or a float or a rational -- but you can't sensibly expect to change your
counter to a binary tree without a major redesign of your code.

I've watched developers with an obsession with data abstraction in
practice. I've watched one comp sci graduate, the ink on his diploma not
even dry yet, spend an hour mapping out state diagrams for a factorial
function.

Hello McFly? The customer is paying for this you know. Get a move on. I've
written five different implementations of factorial in ten minutes, and
while none of them worked with symbolic algebra I didn't need symbolic
algebra support, so I lost nothing by not supporting it.

So I hope you'll understand why I get a bad taste in my mouth when people
start talking about data abstraction.


I don't think the culprit is the mutable/immutable distinction +=
uses, though that is certainly somewhat odd. I think Antoon is on the
right track: namespaces in Python live in sort of a ghetto unbecoming
of how the Zen list describes them as a "honking great idea". These
things we call variables are boxed objects where the namespace is the
box. So having x+=y resolve x to a slot in a namespace before
incrementing that same slot by y, maybe better uses the notion of
namespaces than what happens now.

Perhaps it does, but it breaks inheritance, which is more important than
purity of namespace resolution. Practicality beats purity.

I'm too sleepy to see for sure
whether it gets rid of the mutable/immutable weirdness.

What weirdness? What would be weird is if mutable and immutable objects
worked the same as each other. They behave differently because they are
different. If you fail to see that, you are guilty of excessive data
abstraction.
 
P

Paul Rubin

Steven D'Aprano said:
But do you want x += y to work for immutable objects as well? Then
__iadd__ cannot be a statement, because x can't be modified in place.

It never occurred to me that immutable objects could implement __iadd__.
If they can, I'm puzzled as to why.
While I am enjoying the hoops people are jumping through to modify the
language so that b.a += 2 assigns b.a in the same scope as it was
accessed, I'm still rather perplexed as to why you would want that
behaviour.

Weren't you the one saying += acting differently for mutables and
immutables was a wart? If it's such a wart, why are do you find it so
important to be able to rely on the more bizarre consequences of the
wartiness? Warts should be (if not fixed) avoided, not relied on.
 
S

Steven D'Aprano

It never occurred to me that immutable objects could implement __iadd__.
If they can, I'm puzzled as to why.

???

The classic += idiom comes from C, where you typically use it on ints and
pointers.

In C, ints aren't objects, they are just bytes, so you can modify them
in place. I'm surprised that it never occurred to you that people might
want to do something like x = 1; x += 1 in Python, especially as the
lack of such a feature (as I recall) was one of the biggest complaints
from C programmers crossing over to Python.

Personally, I'm not fussed about +=. Now that it is in the language, I'll
use it, but I never missed it when it wasn't in the language.
Weren't you the one saying += acting differently for mutables and
immutables was a wart?

Nope, not me.
If it's such a wart, why are do you find it so
important to be able to rely on the more bizarre consequences of the
wartiness? Warts should be (if not fixed) avoided, not relied on.

The consequences of instance.attribute += 1 may be unexpected for those
who haven't thought it through, or read the documentation, but they aren't
bizarre. Whether that makes it a feature or a wart depends on whether you
think non-method attributes should be inherited or not. I think they
should be.

I can respect the position of somebody who says that only methods
should be inherited -- somebody, I think it was you, suggested that there
is at least one existing OO language that doesn't allow inheritance for
attributes, but never responded to my asking what language it was.
Personally, I would not like an OO language that didn't inherit
attributes, but at least that is consistent. (At least, if you don't
consider methods to be a particular sort of attribute.)

But I can't understand the position of folks who want inheritance but
don't want the behaviour that Python currently exhibits.
instance.attribute sometimes reading from the class attribute is a feature
of inheritance; instance.attribute always writing to the instance is a
feature of OOP; instance.attribute sometimes writing to the instance and
sometimes writing to the class would be, in my opinion, not just a wart
but a full-blown misfeature.

I ask and I ask and I ask for some use of this proposed behaviour, and
nobody is either willing or able to tell me where how or why it would be
useful. What should I conclude from this?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top