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: Possible different behaviour of explicit and implicit __dict__ accessing when involving builtin types' __dict__ descriptors
Type: behavior Stage:
Components: Interpreter Core Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: rhettinger, serhiy.storchaka, xiang.zhang
Priority: normal Keywords:

Created on 2016-06-29 09:57 by xiang.zhang, last changed 2022-04-11 14:58 by admin.

Messages (2)
msg269470 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2016-06-29 09:57
Usually when we access an object's __dict__, it will finally reach 
subtype_dict[0]. When involving builtin base, it may delegates to the 
base's __dict__ descriptor. In such a case, it is possible for the 
__dict__ descriptor to initialize the dict object if it's NULL. This is
 what PyObject_GenericGetDict does. But if the __dict__ descriptor
initialize dict to other mapping objects instead of dict object, 
accessing __dict__ explicitly first or implicitly by attribute setting
 will result in different behaviours. If we first access obj.__dict__, 
obj.__dict__ will set to the mapping object, otherwise it will be set
 to a dict.

Let's make a example. For convenience I alter BaseException's __dict__
 descriptor, changing PyObject_GenericGetDict to custom_getdict:

PyObject *
custom_getdict(PyObject *obj, void *context)
{
    PyObject *dict, **dictptr = _PyObject_GetDictPtr(obj);
    dict = *dictptr;
    if (dict == NULL) {
        *dictptr = dict = PyDict_New();
        PyDict_SetItemString(dict, "test", PyDict_New());
    }
    Py_XINCREF(dict);
    return dict;
}

And then the use case:

>>> BaseException().__dict__
{'test': {}}
>>> class A:
... 	pass
... 
>>> class B(A, BaseException):
... 	def __init__(self):
... 	    self.__dict__['a'] = 1
...  	    self.b = 2
... 
>>> class C(A, BaseException):
... 	def __init__(self):
...         self.a = 1   
...         self.__dict__['b'] = 2
...
>>> B().__dict__
{'b': 2, 'test': {}, 'a': 1}
>>> C().__dict__
{'a': 1, 'b': 2}

Since a and b are not descriptors, I think B().__dict__ and C().__dict__ should lead to same result but they are not. I think it's
because PyObject_GenericSetAttr doesn't go the same way as subtype_dict
so when it finds __dict__ is not initialized it always assign it a new
 dict.

I am not sure this is a bug or a case worth fixing or is designed deliberately. BTW, subtype_dict was introduced in [1].
 
[0] https://hg.python.org/cpython/file/tip/Objects/typeobject.c#l2069
[1] http://bugs.python.org/issue1303614
msg277510 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-09-27 10:13
See also issue26906.
History
Date User Action Args
2022-04-11 14:58:33adminsetgithub: 71598
2016-09-27 10:13:50serhiy.storchakasetmessages: + msg277510
2016-06-29 09:57:04xiang.zhangcreate