This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: in-place addition of a shadowed class field
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 3.4
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: jethro, r.david.murray
Priority: normal Keywords:

Created on 2015-03-31 14:01 by jethro, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (6)
msg239714 - (view) Author: Jethro (jethro) Date: 2015-03-31 14:01
Look at this simple code:

    class A:
	tot = 0
	def __init__(self, a):
		self.tot += a

    x = A(3)
    print(x.tot, A.tot)

Result from print: 

    3 0

What the interpreter did was that it first resolved self.tot to be the class field tot, which resolves to 0, then it created an instance field tot to hold the result 3.

This is not correct, as one single self.tot meant two different things. Two ways to fix this: 

1. report a name undefined error (interpret self.tot as instance field)

2. increase A.tot (interpret self.tot as class field)

Clearly 1 seems more naturally to Python, even though I wished python could disallow a class field to be shadowed by instance field, which seems quite a reasonable thing to do.
msg239715 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-03-31 14:11
Yep, this is the way it works.  When a class attribute name is referenced on an instance object, you are referencing the object pointed to by the class name.  What happens next depends on what kind of object you have, and what kind of operation you perform.  In this case, you have an immutable object, and the addition operation returns a new immutable object, which gets assigned (as always happens) to the instance attribute name.  If tot was, say, [0] and the operation was self.tot += [1], your print would print [0, 1] [0, 1], because the in-place addition operation on lists *mutatates* the list, and what gets assigned to the instance attribute name is a pointer to the *same* object that the class attribute points to.

In short, this is a more subtle instance of the confusion discussed here: https://docs.python.org/3/faq/programming.html#why-did-changing-list-y-also-change-list-x (and also the FAQ that follows that one).
msg239717 - (view) Author: Jethro (jethro) Date: 2015-03-31 14:39
I believe Mr. Murray somehow missed the point. My point is that the very same self.tot is interpreted as two different things: instance field and class field. In another analogous case, the interpreter would be more consistent:

>>> tot = 0
>>> def addtot(x): tot+=x
>>> addtot(3)

If this tot is allowed to be resolved to different things, there should be no problem. Instead you get an error:

Traceback (most recent call last):
  File "<pyshell#106>", line 1, in <module>
    addtot(3)
  File "<pyshell#105>", line 1, in addtot
    def addtot(x): tot += x
UnboundLocalError: local variable 'tot' referenced before assignment
msg239721 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-03-31 14:57
Ah, now I understand your confusion.  Class variables are special.  The first time you reference a name on an instance that is not currently defined on that instance but is defined on the class, the interpreter gets the object pointer from the class reference.  It then performs the operation, and assigns the result to the *instance* attribute.  This is how class attributes work, and is an integral part of Python.  This is documented in the 'class instances' section of https://docs.python.org/3/reference/datamodel.html#objects-values-and-types.
msg239766 - (view) Author: Jethro (jethro) Date: 2015-04-01 00:01
I'm not confused. In my first report, I have already explained why python behaves that way. My point is, this is not the right way to go. Because the very same self.tot in the in-place operation self.tot+=a is first resolved to be the class field, then an instance field. This is against any sensible design of a language. If this is OK, then my second example should also be OK. In my second example, tot += x, the very same tot can be first resolved to the global variable tot, then the result is assigned to a local variable.
msg239808 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-04-01 13:40
And I'm telling you that *this is how Python is designed*, and it is not going to change.  Class attributes are *special*, for a reason.

Please do not reopen this issue again.  If you wish to pursue this further, the language design discussion forum is the python-ideas mailing list.  Guido participates in that list; so, you can talk directly to the language designer about this if you wish.  You aren't going to get a very warm reception by saying it is broken, though, because there is lots and lots of Python code that makes use of how class attributes work.  And like I said they are special, so suggesting that the way other scopes work be changed is also pretty much a non-starter, I'm pretty sure.
History
Date User Action Args
2022-04-11 14:58:14adminsetgithub: 68012
2015-04-01 13:40:30r.david.murraysetstatus: open -> closed
resolution: not a bug
messages: + msg239808
2015-04-01 00:01:07jethrosetstatus: closed -> open
resolution: not a bug -> (no value)
messages: + msg239766
2015-03-31 16:51:09serhiy.storchakasetstatus: open -> closed
resolution: not a bug
2015-03-31 14:57:28r.david.murraysetmessages: + msg239721
2015-03-31 14:41:00jethrosetstatus: closed -> open
resolution: not a bug -> (no value)
2015-03-31 14:39:18jethrosetmessages: + msg239717
2015-03-31 14:11:20r.david.murraysetstatus: open -> closed

nosy: + r.david.murray
messages: + msg239715

resolution: not a bug
stage: resolved
2015-03-31 14:01:23jethrocreate