@@ -1079,6 +1079,8 @@ simulation where the actual C structure for slots is emulated by a private
10791079``_slotvalues `` list. Reads and writes to that private structure are managed
10801080by member descriptors::
10811081
1082+ null = object()
1083+
10821084 class Member:
10831085
10841086 def __init__(self, name, clsname, offset):
@@ -1091,20 +1093,28 @@ by member descriptors::
10911093 def __get__(self, obj, objtype=None):
10921094 'Emulate member_get() in Objects/descrobject.c'
10931095 # Also see PyMember_GetOne() in Python/structmember.c
1094- return obj._slotvalues[self.offset]
1096+ value = obj._slotvalues[self.offset]
1097+ if value is null:
1098+ raise AttributeError(self.name)
1099+ return value
10951100
10961101 def __set__(self, obj, value):
10971102 'Emulate member_set() in Objects/descrobject.c'
10981103 obj._slotvalues[self.offset] = value
10991104
1105+ def __delete__(self, obj):
1106+ 'Emulate member_delete() in Objects/descrobject.c'
1107+ value = obj._slotvalues[self.offset]
1108+ if value is null:
1109+ raise AttributeError(self.name)
1110+ obj._slotvalues[self.offset] = null
1111+
11001112 def __repr__(self):
11011113 'Emulate member_repr() in Objects/descrobject.c'
11021114 return f'<Member {self.name!r} of {self.clsname!r}>'
11031115
11041116The :meth: `type.__new__ ` method takes care of adding member objects to class
1105- variables. The :meth: `object.__new__ ` method takes care of creating instances
1106- that have slots instead of an instance dictionary. Here is a rough equivalent
1107- in pure Python::
1117+ variables::
11081118
11091119 class Type(type):
11101120 'Simulate how the type metaclass adds member objects for slots'
@@ -1117,20 +1127,44 @@ in pure Python::
11171127 mapping[name] = Member(name, clsname, offset)
11181128 return type.__new__(mcls, clsname, bases, mapping)
11191129
1130+ The :meth: `object.__new__ ` method takes care of creating instances that have
1131+ slots instead of an instance dictionary. Here is a rough simulation in pure
1132+ Python::
1133+
11201134 class Object:
11211135 'Simulate how object.__new__() allocates memory for __slots__'
11221136
11231137 def __new__(cls, *args):
11241138 'Emulate object_new() in Objects/typeobject.c'
11251139 inst = super().__new__(cls)
11261140 if hasattr(cls, 'slot_names'):
1127- inst._slotvalues = [None] * len(cls.slot_names)
1141+ empty_slots = [null] * len(cls.slot_names)
1142+ object.__setattr__(inst, '_slotvalues', empty_slots)
11281143 return inst
11291144
1145+ def __setattr__(self, name, value):
1146+ 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'
1147+ cls = type(self)
1148+ if hasattr(cls, 'slot_names') and name not in cls.slot_names:
1149+ raise AttributeError(
1150+ f'{type(self).__name__!r} object has no attribute {name!r}'
1151+ )
1152+ super().__setattr__(name, value)
1153+
1154+ def __delattr__(self, name):
1155+ 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'
1156+ cls = type(self)
1157+ if hasattr(cls, 'slot_names') and name not in cls.slot_names:
1158+ raise AttributeError(
1159+ f'{type(self).__name__!r} object has no attribute {name!r}'
1160+ )
1161+ super().__delattr__(name)
1162+
11301163To use the simulation in a real class, just inherit from :class: `Object ` and
11311164set the :term: `metaclass ` to :class: `Type `::
11321165
11331166 class H(Object, metaclass=Type):
1167+ 'Instance variables stored in slots'
11341168
11351169 slot_names = ['x', 'y']
11361170
@@ -1143,11 +1177,11 @@ At this point, the metaclass has loaded member objects for *x* and *y*::
11431177 >>> import pprint
11441178 >>> pprint.pp(dict(vars(H)))
11451179 {'__module__': '__main__',
1180+ '__doc__': 'Instance variables stored in slots',
11461181 'slot_names': ['x', 'y'],
11471182 '__init__': <function H.__init__ at 0x7fb5d302f9d0>,
11481183 'x': <Member 'x' of 'H'>,
1149- 'y': <Member 'y' of 'H'>,
1150- '__doc__': None}
1184+ 'y': <Member 'y' of 'H'>}
11511185
11521186When instances are created, they have a ``slot_values `` list where the
11531187attributes are stored::
@@ -1159,8 +1193,9 @@ attributes are stored::
11591193 >>> vars(h)
11601194 {'_slotvalues': [55, 20]}
11611195
1162- Unlike the real ``__slots__ ``, this simulation does have an instance
1163- dictionary just to hold the ``_slotvalues `` array. So, unlike the real code,
1164- this simulation doesn't block assignments to misspelled attributes::
1196+ Misspelled or unassigned attributes will raise an exception::
11651197
1166- >>> h.xz = 30 # For actual __slots__ this would raise an AttributeError
1198+ >>> h.xz
1199+ Traceback (most recent call last):
1200+ ...
1201+ AttributeError: 'H' object has no attribute 'xz'
0 commit comments