@@ -1079,6 +1079,8 @@ simulation where the actual C structure for slots is emulated by a private
1079
1079
``_slotvalues `` list. Reads and writes to that private structure are managed
1080
1080
by member descriptors::
1081
1081
1082
+ null = object()
1083
+
1082
1084
class Member:
1083
1085
1084
1086
def __init__(self, name, clsname, offset):
@@ -1091,20 +1093,28 @@ by member descriptors::
1091
1093
def __get__(self, obj, objtype=None):
1092
1094
'Emulate member_get() in Objects/descrobject.c'
1093
1095
# 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
1095
1100
1096
1101
def __set__(self, obj, value):
1097
1102
'Emulate member_set() in Objects/descrobject.c'
1098
1103
obj._slotvalues[self.offset] = value
1099
1104
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
+
1100
1112
def __repr__(self):
1101
1113
'Emulate member_repr() in Objects/descrobject.c'
1102
1114
return f'<Member {self.name!r} of {self.clsname!r}>'
1103
1115
1104
1116
The :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::
1108
1118
1109
1119
class Type(type):
1110
1120
'Simulate how the type metaclass adds member objects for slots'
@@ -1117,20 +1127,44 @@ in pure Python::
1117
1127
mapping[name] = Member(name, clsname, offset)
1118
1128
return type.__new__(mcls, clsname, bases, mapping)
1119
1129
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
+
1120
1134
class Object:
1121
1135
'Simulate how object.__new__() allocates memory for __slots__'
1122
1136
1123
1137
def __new__(cls, *args):
1124
1138
'Emulate object_new() in Objects/typeobject.c'
1125
1139
inst = super().__new__(cls)
1126
1140
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)
1128
1143
return inst
1129
1144
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
+
1130
1163
To use the simulation in a real class, just inherit from :class: `Object ` and
1131
1164
set the :term: `metaclass ` to :class: `Type `::
1132
1165
1133
1166
class H(Object, metaclass=Type):
1167
+ 'Instance variables stored in slots'
1134
1168
1135
1169
slot_names = ['x', 'y']
1136
1170
@@ -1143,11 +1177,11 @@ At this point, the metaclass has loaded member objects for *x* and *y*::
1143
1177
>>> import pprint
1144
1178
>>> pprint.pp(dict(vars(H)))
1145
1179
{'__module__': '__main__',
1180
+ '__doc__': 'Instance variables stored in slots',
1146
1181
'slot_names': ['x', 'y'],
1147
1182
'__init__': <function H.__init__ at 0x7fb5d302f9d0>,
1148
1183
'x': <Member 'x' of 'H'>,
1149
- 'y': <Member 'y' of 'H'>,
1150
- '__doc__': None}
1184
+ 'y': <Member 'y' of 'H'>}
1151
1185
1152
1186
When instances are created, they have a ``slot_values `` list where the
1153
1187
attributes are stored::
@@ -1159,8 +1193,9 @@ attributes are stored::
1159
1193
>>> vars(h)
1160
1194
{'_slotvalues': [55, 20]}
1161
1195
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::
1165
1197
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