Skip to content

Commit 81dd2c0

Browse files
Expand and clarify the "Invoking Descriptors" section of the Descriptor HowTo (GH-23078) (GH-23080)
1 parent 0312efc commit 81dd2c0

File tree

2 files changed

+79
-38
lines changed

2 files changed

+79
-38
lines changed

Doc/howto/descriptor.rst

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ To use the descriptor, it must be stored as a class variable in another class::
5252

5353
class A:
5454
x = 5 # Regular class attribute
55-
y = Ten() # Descriptor
55+
y = Ten() # Descriptor instance
5656

5757
An interactive session shows the difference between normal attribute lookup
5858
and descriptor lookup::
@@ -80,7 +80,6 @@ Dynamic lookups
8080

8181
Interesting descriptors typically run computations instead of doing lookups::
8282

83-
8483
import os
8584

8685
class DirectorySize:
@@ -90,7 +89,7 @@ Interesting descriptors typically run computations instead of doing lookups::
9089

9190
class Directory:
9291

93-
size = DirectorySize() # Descriptor
92+
size = DirectorySize() # Descriptor instance
9493

9594
def __init__(self, dirname):
9695
self.dirname = dirname # Regular instance attribute
@@ -147,11 +146,11 @@ the lookup or update::
147146

148147
class Person:
149148

150-
age = LoggedAgeAccess() # Descriptor
149+
age = LoggedAgeAccess() # Descriptor instance
151150

152151
def __init__(self, name, age):
153152
self.name = name # Regular instance attribute
154-
self.age = age # Calls the descriptor
153+
self.age = age # Calls __set__()
155154

156155
def birthday(self):
157156
self.age += 1 # Calls both __get__() and __set__()
@@ -221,8 +220,8 @@ be recorded, giving each descriptor its own *public_name* and *private_name*::
221220

222221
class Person:
223222

224-
name = LoggedAccess() # First descriptor
225-
age = LoggedAccess() # Second descriptor
223+
name = LoggedAccess() # First descriptor instance
224+
age = LoggedAccess() # Second descriptor instance
226225

227226
def __init__(self, name, age):
228227
self.name = name # Calls the first descriptor
@@ -494,56 +493,98 @@ called. Defining the :meth:`__set__` method with an exception raising
494493
placeholder is enough to make it a data descriptor.
495494

496495

497-
Invoking Descriptors
498-
--------------------
496+
Overview of Descriptor Invocation
497+
---------------------------------
499498

500-
A descriptor can be called directly by its method name. For example,
501-
``d.__get__(obj)``.
499+
A descriptor can be called directly with ``desc.__get__(obj)`` or
500+
``desc.__get__(None, cls)``.
502501

503502
But it is more common for a descriptor to be invoked automatically from
504-
attribute access. The expression ``obj.d`` looks up ``d`` in the dictionary of
505-
``obj``. If ``d`` defines the method :meth:`__get__`, then ``d.__get__(obj)``
506-
is invoked according to the precedence rules listed below.
503+
attribute access.
504+
505+
The expression ``obj.x`` looks up the attribute ``x`` in the chain of
506+
namespaces for ``obj``. If the search finds a descriptor, its :meth:`__get__`
507+
method is invoked according to the precedence rules listed below.
507508

508509
The details of invocation depend on whether ``obj`` is an object, class, or
509510
instance of super.
510511

511-
**Objects**: The machinery is in :meth:`object.__getattribute__`.
512512

513-
It transforms ``b.x`` into ``type(b).__dict__['x'].__get__(b, type(b))``.
513+
Invocation from an Instance
514+
---------------------------
515+
516+
Instance lookup scans through a chain of namespaces giving data descriptors
517+
the highest priority, followed by instance variables, then non-data
518+
descriptors, then class variables, and lastly :meth:`__getattr__` if it is
519+
provided.
520+
521+
If a descriptor is found for ``a.x``, then it is invoked with:
522+
``desc.__get__(a, type(a))``.
523+
524+
The logic for a dotted lookup is in :meth:`object.__getattribute__`. Here is
525+
a pure Python equivalent::
526+
527+
def object_getattribute(obj, name):
528+
"Emulate PyObject_GenericGetAttr() in Objects/object.c"
529+
null = object()
530+
objtype = type(obj)
531+
value = getattr(objtype, name, null)
532+
if value is not null and hasattr(value, '__get__'):
533+
if hasattr(value, '__set__') or hasattr(value, '__delete__'):
534+
return value.__get__(obj, objtype) # data descriptor
535+
try:
536+
return vars(obj)[name] # instance variable
537+
except (KeyError, TypeError):
538+
pass
539+
if hasattr(value, '__get__'):
540+
return value.__get__(obj, objtype) # non-data descriptor
541+
if value is not null:
542+
return value # class variable
543+
# Emulate slot_tp_getattr_hook() in Objects/typeobject.c
544+
if hasattr(objtype, '__getattr__'):
545+
return objtype.__getattr__(obj, name) # __getattr__ hook
546+
raise AttributeError(name)
547+
548+
The :exc:`TypeError` exception handler is needed because the instance dictionary
549+
doesn't exist when its class defines :term:`__slots__`.
514550

515-
The implementation works through a precedence chain that gives data descriptors
516-
priority over instance variables, instance variables priority over non-data
517-
descriptors, and assigns lowest priority to :meth:`__getattr__` if provided.
518551

519-
The full C implementation can be found in :c:func:`PyObject_GenericGetAttr()` in
520-
:source:`Objects/object.c`.
552+
Invocation from a Class
553+
-----------------------
521554

522-
**Classes**: The machinery is in :meth:`type.__getattribute__`.
555+
The logic for a dotted lookup such as ``A.x`` is in
556+
:meth:`type.__getattribute__`. The steps are similar to those for
557+
:meth:`object.__getattribute__` but the instance dictionary lookup is replaced
558+
by a search through the class's :term:`method resolution order`.
523559

524-
It transforms ``A.x`` into ``A.__dict__['x'].__get__(None, A)``.
560+
If a descriptor is found, it is invoked with ``desc.__get__(None, A)``.
525561

526-
The full C implementation can be found in :c:func:`type_getattro()` in
527-
:source:`Objects/typeobject.c`.
562+
The full C implementation can be found in :c:func:`type_getattro()` and
563+
:c:func:`_PyType_Lookup()` in :source:`Objects/typeobject.c`.
528564

529-
**Super**: The machinery is in the custom :meth:`__getattribute__` method for
565+
566+
Invocation from Super
567+
---------------------
568+
569+
The logic for super's dotted lookup is in the :meth:`__getattribute__` method for
530570
object returned by :class:`super()`.
531571

532-
The attribute lookup ``super(A, obj).m`` searches ``obj.__class__.__mro__`` for
533-
the base class ``B`` immediately following ``A`` and then returns
572+
A dotted lookup such as ``super(A, obj).m`` searches ``obj.__class__.__mro__``
573+
for the base class ``B`` immediately following ``A`` and then returns
534574
``B.__dict__['m'].__get__(obj, A)``. If not a descriptor, ``m`` is returned
535-
unchanged. If not in the dictionary, ``m`` reverts to a search using
536-
:meth:`object.__getattribute__`.
575+
unchanged.
537576

538-
The implementation details are in :c:func:`super_getattro()` in
577+
The full C implementation can be found in :c:func:`super_getattro()` in
539578
:source:`Objects/typeobject.c`. A pure Python equivalent can be found in
540-
`Guido's Tutorial`_.
579+
`Guido's Tutorial
580+
<https://www.python.org/download/releases/2.2.3/descrintro/#cooperation>`_.
541581

542-
.. _`Guido's Tutorial`: https://www.python.org/download/releases/2.2.3/descrintro/#cooperation
543582

544-
**Summary**: The mechanism for descriptors is embedded in the
545-
:meth:`__getattribute__()` methods for :class:`object`, :class:`type`, and
546-
:func:`super`.
583+
Summary of Invocation Logic
584+
---------------------------
585+
586+
The mechanism for descriptors is embedded in the :meth:`__getattribute__()`
587+
methods for :class:`object`, :class:`type`, and :func:`super`.
547588

548589
The important points to remember are:
549590

@@ -652,7 +693,7 @@ Pure Python Equivalents
652693
^^^^^^^^^^^^^^^^^^^^^^^
653694

654695
The descriptor protocol is simple and offers exciting possibilities. Several
655-
use cases are so common that they have been prepackaged into builtin tools.
696+
use cases are so common that they have been prepackaged into built-in tools.
656697
Properties, bound methods, static methods, and class methods are all based on
657698
the descriptor protocol.
658699

Doc/tools/susp-ignored.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ c-api/sequence,,:i2,o[i1:i2]
55
c-api/tuple,,:high,p[low:high]
66
c-api/unicode,,:end,str[start:end]
77
c-api/unicode,,:start,unicode[start:start+length]
8-
distutils/examples,267,`,This is the description of the ``foobar`` package.
8+
distutils/examples,,`,This is the description of the ``foobar`` package.
99
distutils/setupscript,,::,
1010
extending/embedding,,:numargs,"if(!PyArg_ParseTuple(args, "":numargs""))"
1111
extending/extending,,:myfunction,"PyArg_ParseTuple(args, ""D:myfunction"", &c);"

0 commit comments

Comments
 (0)