python / cpython

The Python programming language
https://www.python.org/
Other
61.06k stars 29.49k forks source link

Can't reassign __class__ despite the assigned class having identical slots #79229

Open df79943f-4aee-4531-a00d-c6b12816eb70 opened 5 years ago

df79943f-4aee-4531-a00d-c6b12816eb70 commented 5 years ago
BPO 35048
Nosy @rhettinger, @mdickinson, @pitrou, @benjaminp, @serhiy-storchaka, @srinivasreddy, @mr-nfamous, @tirkarthi

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields: ```python assignee = None closed_at = None created_at = labels = ['3.7'] title = "Can't reassign __class__ despite the assigned class having identical slots" updated_at = user = 'https://github.com/mr-nfamous' ``` bugs.python.org fields: ```python activity = actor = 'rhettinger' assignee = 'none' closed = False closed_date = None closer = None components = [] creation = creator = 'bup' dependencies = [] files = [] hgrepos = [] issue_num = 35048 keywords = [] message_count = 2.0 messages = ['328296', '357983'] nosy_count = 9.0 nosy_names = ['rhettinger', 'mark.dickinson', 'pitrou', 'benjamin.peterson', 'serhiy.storchaka', 'thatiparthy', 'bup', 'xtreak', 'Sterling Smith'] pr_nums = [] priority = 'normal' resolution = None stage = None status = 'open' superseder = None type = None url = 'https://bugs.python.org/issue35048' versions = ['Python 3.7'] ```

df79943f-4aee-4531-a00d-c6b12816eb70 commented 5 years ago

>> class a(dict): __slots = '__dict', 'x'

>> class b(dict): __slots = '__dict', 'x'

>>> self = a(); self.__class__ = b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    self=a(); self.__class__ = b
TypeError: __class__ assignment: 'b' object layout differs from 'a'

This always occurs when __dict and/or __weakref are defined as slots, even when both classes have otherwise identical slots. This behavior appears to contradict what the docs say wrt to __class__ assignment, which is (in its entirety):

"__class assignment works only if both classes have the same __slots. "

Not sure if this is just a case of ambiguous documentation and intentional behavior or not. Since two classes with identical slots will always have identical internal struct layouts, I can't see a reason for this error.

rhettinger commented 4 years ago

The relevant logic is in the compatible_for_assignment() function on line 3972 in Objects/typeobject.c.

After checking that the tp_free slots are the same, it proceeds as follows:

/*
 It's tricky to tell if two arbitrary types are sufficiently compatible as
 to be interchangeable; e.g., even if they have the same tp_basicsize, they
 might have totally different struct fields. It's much easier to tell if a
 type and its supertype are compatible; e.g., if they have the same
 tp_basicsize, then that means they have identical fields. So to check
 whether two arbitrary types are compatible, we first find the highest
 supertype that each is compatible with, and then if those supertypes are
 compatible then the original types must also be compatible.
*/

So what is happening is that "class a" and "class b" aren't being directly compared to one another. Instead, they are being compared to their parent "dict". Since *dict* doesn't have __dict or __weakref, "a" and "b" are deemed to have incompatible layouts. See lines 3951 to 3954 in same_slots_added() in Objects/typeobject.c.

sam-s commented 5 months ago

Possibly related ipython bug: autoreload gratuitous "object layout differs" error