summaryrefslogtreecommitdiffstats
path: root/Doc/howto/descriptor.rst
diff options
context:
space:
mode:
authorRaymond Hettinger <rhettinger@users.noreply.github.com>2020-11-23 18:56:59 (GMT)
committerGitHub <noreply@github.com>2020-11-23 18:56:59 (GMT)
commitffae93248a33cd6fe73d1ea85d6802765bbf56d2 (patch)
treef398afaac56b7a56d4dc9cfa3d9b1ef64da5b127 /Doc/howto/descriptor.rst
parent2f2f9d0b5c96e68ed91cddd6673860ee752eb49c (diff)
downloadcpython-ffae93248a33cd6fe73d1ea85d6802765bbf56d2.zip
cpython-ffae93248a33cd6fe73d1ea85d6802765bbf56d2.tar.gz
cpython-ffae93248a33cd6fe73d1ea85d6802765bbf56d2.tar.bz2
Descriptor HowTo: Improve the fidelity of the member object simulation (GH-23475)
Diffstat (limited to 'Doc/howto/descriptor.rst')
-rw-r--r--Doc/howto/descriptor.rst57
1 files changed, 46 insertions, 11 deletions
diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst
index 76987fd..8c6e903 100644
--- a/Doc/howto/descriptor.rst
+++ b/Doc/howto/descriptor.rst
@@ -1079,6 +1079,8 @@ simulation where the actual C structure for slots is emulated by a private
``_slotvalues`` list. Reads and writes to that private structure are managed
by member descriptors::
+ null = object()
+
class Member:
def __init__(self, name, clsname, offset):
@@ -1091,20 +1093,28 @@ by member descriptors::
def __get__(self, obj, objtype=None):
'Emulate member_get() in Objects/descrobject.c'
# Also see PyMember_GetOne() in Python/structmember.c
- return obj._slotvalues[self.offset]
+ value = obj._slotvalues[self.offset]
+ if value is null:
+ raise AttributeError(self.name)
+ return value
def __set__(self, obj, value):
'Emulate member_set() in Objects/descrobject.c'
obj._slotvalues[self.offset] = value
+ def __delete__(self, obj):
+ 'Emulate member_delete() in Objects/descrobject.c'
+ value = obj._slotvalues[self.offset]
+ if value is null:
+ raise AttributeError(self.name)
+ obj._slotvalues[self.offset] = null
+
def __repr__(self):
'Emulate member_repr() in Objects/descrobject.c'
return f'<Member {self.name!r} of {self.clsname!r}>'
The :meth:`type.__new__` method takes care of adding member objects to class
-variables. The :meth:`object.__new__` method takes care of creating instances
-that have slots instead of an instance dictionary. Here is a rough equivalent
-in pure Python::
+variables::
class Type(type):
'Simulate how the type metaclass adds member objects for slots'
@@ -1117,6 +1127,10 @@ in pure Python::
mapping[name] = Member(name, clsname, offset)
return type.__new__(mcls, clsname, bases, mapping)
+The :meth:`object.__new__` method takes care of creating instances that have
+slots instead of an instance dictionary. Here is a rough simulation in pure
+Python::
+
class Object:
'Simulate how object.__new__() allocates memory for __slots__'
@@ -1124,13 +1138,33 @@ in pure Python::
'Emulate object_new() in Objects/typeobject.c'
inst = super().__new__(cls)
if hasattr(cls, 'slot_names'):
- inst._slotvalues = [None] * len(cls.slot_names)
+ empty_slots = [null] * len(cls.slot_names)
+ object.__setattr__(inst, '_slotvalues', empty_slots)
return inst
+ def __setattr__(self, name, value):
+ 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'
+ cls = type(self)
+ if hasattr(cls, 'slot_names') and name not in cls.slot_names:
+ raise AttributeError(
+ f'{type(self).__name__!r} object has no attribute {name!r}'
+ )
+ super().__setattr__(name, value)
+
+ def __delattr__(self, name):
+ 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'
+ cls = type(self)
+ if hasattr(cls, 'slot_names') and name not in cls.slot_names:
+ raise AttributeError(
+ f'{type(self).__name__!r} object has no attribute {name!r}'
+ )
+ super().__delattr__(name)
+
To use the simulation in a real class, just inherit from :class:`Object` and
set the :term:`metaclass` to :class:`Type`::
class H(Object, metaclass=Type):
+ 'Instance variables stored in slots'
slot_names = ['x', 'y']
@@ -1143,11 +1177,11 @@ At this point, the metaclass has loaded member objects for *x* and *y*::
>>> import pprint
>>> pprint.pp(dict(vars(H)))
{'__module__': '__main__',
+ '__doc__': 'Instance variables stored in slots',
'slot_names': ['x', 'y'],
'__init__': <function H.__init__ at 0x7fb5d302f9d0>,
'x': <Member 'x' of 'H'>,
- 'y': <Member 'y' of 'H'>,
- '__doc__': None}
+ 'y': <Member 'y' of 'H'>}
When instances are created, they have a ``slot_values`` list where the
attributes are stored::
@@ -1159,8 +1193,9 @@ attributes are stored::
>>> vars(h)
{'_slotvalues': [55, 20]}
-Unlike the real ``__slots__``, this simulation does have an instance
-dictionary just to hold the ``_slotvalues`` array. So, unlike the real code,
-this simulation doesn't block assignments to misspelled attributes::
+Misspelled or unassigned attributes will raise an exception::
- >>> h.xz = 30 # For actual __slots__ this would raise an AttributeError
+ >>> h.xz
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'H' object has no attribute 'xz'