@@ -16,7 +16,7 @@ storage, and deletion.
16
16
This guide has four major sections:
17
17
18
18
1) The "primer" gives a basic overview, moving gently from simple examples,
19
- adding one feature at a time. It is a great place to start .
19
+ adding one feature at a time. Start here if you're new to descriptors .
20
20
21
21
2) The second section shows a complete, practical descriptor example. If you
22
22
already know the basics, start there.
@@ -42,7 +42,8 @@ add new capabilities one by one.
42
42
Simple example: A descriptor that returns a constant
43
43
----------------------------------------------------
44
44
45
- The :class: `Ten ` class is a descriptor that always returns the constant ``10 ``::
45
+ The :class: `Ten ` class is a descriptor that always returns the constant ``10 ``
46
+ from its :meth: `__get__ ` method::
46
47
47
48
48
49
class Ten:
@@ -64,9 +65,11 @@ and descriptor lookup::
64
65
>>> a.y # Descriptor lookup
65
66
10
66
67
67
- In the ``a.x `` attribute lookup, the dot operator finds the value ``5 `` stored
68
- in the class dictionary. In the ``a.y `` descriptor lookup, the dot operator
69
- calls the descriptor's :meth: `__get__() ` method. That method returns ``10 ``.
68
+ In the ``a.x `` attribute lookup, the dot operator finds the key ``x `` and the
69
+ value ``5 `` in the class dictionary. In the ``a.y `` lookup, the dot operator
70
+ finds a descriptor instance, recognized by its ``__get__ `` method, and calls
71
+ that method which returns ``10 ``.
72
+
70
73
Note that the value ``10 `` is not stored in either the class dictionary or the
71
74
instance dictionary. Instead, the value ``10 `` is computed on demand.
72
75
@@ -79,7 +82,8 @@ In the next section, we'll create something more useful, a dynamic lookup.
79
82
Dynamic lookups
80
83
---------------
81
84
82
- Interesting descriptors typically run computations instead of doing lookups::
85
+ Interesting descriptors typically run computations instead of returning
86
+ constants::
83
87
84
88
import os
85
89
@@ -98,16 +102,15 @@ Interesting descriptors typically run computations instead of doing lookups::
98
102
An interactive session shows that the lookup is dynamic — it computes
99
103
different, updated answers each time::
100
104
101
- >>> g = Directory('games')
102
105
>>> s = Directory('songs')
106
+ >>> g = Directory('games')
107
+ >>> s.size # The songs directory has twenty files
108
+ 20
103
109
>>> g.size # The games directory has three files
104
110
3
105
- >>> os.system('touch games/newfile') # Add a fourth file to the directory
106
- 0
107
- >>> g.size # Automatically updated
111
+ >>> open('games/newfile').close() # Add a fourth file to the directory
112
+ >>> g.size # File count is automatically updated
108
113
4
109
- >>> s.size # The songs directory has twenty files
110
- 20
111
114
112
115
Besides showing how descriptors can run computations, this example also
113
116
reveals the purpose of the parameters to :meth: `__get__ `. The *self *
@@ -208,7 +211,7 @@ be recorded, giving each descriptor its own *public_name* and *private_name*::
208
211
209
212
def __set_name__(self, owner, name):
210
213
self.public_name = name
211
- self.private_name = f'_{name}'
214
+ self.private_name = '_' + name
212
215
213
216
def __get__(self, obj, objtype=None):
214
217
value = getattr(obj, self.private_name)
@@ -265,17 +268,18 @@ A :term:`descriptor` is what we call any object that defines :meth:`__get__`,
265
268
266
269
Optionally, descriptors can have a :meth: `__set_name__ ` method. This is only
267
270
used in cases where a descriptor needs to know either the class where it was
268
- created or the name of class variable it was assigned to.
271
+ created or the name of class variable it was assigned to. (This method, if
272
+ present, is called even if the class is not a descriptor.)
269
273
270
- Descriptors get invoked by the dot operator during attribute lookup. If a
274
+ Descriptors get invoked by the dot " operator" during attribute lookup. If a
271
275
descriptor is accessed indirectly with ``vars(some_class)[descriptor_name] ``,
272
276
the descriptor instance is returned without invoking it.
273
277
274
278
Descriptors only work when used as class variables. When put in instances,
275
279
they have no effect.
276
280
277
281
The main motivation for descriptors is to provide a hook allowing objects
278
- stored in class variables to control what happens during dotted lookup.
282
+ stored in class variables to control what happens during attribute lookup.
279
283
280
284
Traditionally, the calling class controls what happens during lookup.
281
285
Descriptors invert that relationship and allow the data being looked-up to
@@ -310,7 +314,7 @@ managed attribute descriptor::
310
314
class Validator(ABC):
311
315
312
316
def __set_name__(self, owner, name):
313
- self.private_name = f'_{name}'
317
+ self.private_name = '_' + name
314
318
315
319
def __get__(self, obj, objtype=None):
316
320
return getattr(obj, self.private_name)
@@ -435,23 +439,21 @@ Defines descriptors, summarizes the protocol, and shows how descriptors are
435
439
called. Provides an example showing how object relational mappings work.
436
440
437
441
Learning about descriptors not only provides access to a larger toolset, it
438
- creates a deeper understanding of how Python works and an appreciation for the
439
- elegance of its design.
442
+ creates a deeper understanding of how Python works.
440
443
441
444
442
445
Definition and introduction
443
446
---------------------------
444
447
445
- In general, a descriptor is an object attribute with "binding behavior", one
446
- whose attribute access has been overridden by methods in the descriptor
447
- protocol. Those methods are :meth: `__get__ `, :meth: `__set__ `, and
448
- :meth: `__delete__ `. If any of those methods are defined for an object, it is
449
- said to be a :term: `descriptor `.
448
+ In general, a descriptor is an attribute value that has one of the methods in
449
+ the descriptor protocol. Those methods are :meth: `__get__ `, :meth: `__set__ `,
450
+ and :meth: `__delete__ `. If any of those methods are defined for an the
451
+ attribute, it is said to be a :term: `descriptor `.
450
452
451
453
The default behavior for attribute access is to get, set, or delete the
452
454
attribute from an object's dictionary. For instance, ``a.x `` has a lookup chain
453
455
starting with ``a.__dict__['x'] ``, then ``type(a).__dict__['x'] ``, and
454
- continuing through the base classes of ``type(a) ``. If the
456
+ continuing through the method resolution order of ``type(a) ``. If the
455
457
looked-up value is an object defining one of the descriptor methods, then Python
456
458
may override the default behavior and invoke the descriptor method instead.
457
459
Where this occurs in the precedence chain depends on which descriptor methods
@@ -479,7 +481,7 @@ as an attribute.
479
481
480
482
If an object defines :meth: `__set__ ` or :meth: `__delete__ `, it is considered
481
483
a data descriptor. Descriptors that only define :meth: `__get__ ` are called
482
- non-data descriptors (they are typically used for methods but other uses are
484
+ non-data descriptors (they are often used for methods but other uses are
483
485
possible).
484
486
485
487
Data and non-data descriptors differ in how overrides are calculated with
@@ -504,8 +506,9 @@ But it is more common for a descriptor to be invoked automatically from
504
506
attribute access.
505
507
506
508
The expression ``obj.x `` looks up the attribute ``x `` in the chain of
507
- namespaces for ``obj ``. If the search finds a descriptor, its :meth: `__get__ `
508
- method is invoked according to the precedence rules listed below.
509
+ namespaces for ``obj ``. If the search finds a descriptor outside of the
510
+ instance ``__dict__ ``, its :meth: `__get__ ` method is invoked according to the
511
+ precedence rules listed below.
509
512
510
513
The details of invocation depend on whether ``obj `` is an object, class, or
511
514
instance of super.
@@ -529,25 +532,38 @@ a pure Python equivalent::
529
532
"Emulate PyObject_GenericGetAttr() in Objects/object.c"
530
533
null = object()
531
534
objtype = type(obj)
532
- value = getattr(objtype, name, null)
533
- if value is not null and hasattr(value, '__get__'):
534
- if hasattr(value, '__set__') or hasattr(value, '__delete__'):
535
- return value.__get__(obj, objtype) # data descriptor
536
- try:
537
- return vars(obj)[name] # instance variable
538
- except (KeyError, TypeError):
539
- pass
540
- if hasattr(value, '__get__'):
541
- return value.__get__(obj, objtype) # non-data descriptor
542
- if value is not null:
543
- return value # class variable
544
- # Emulate slot_tp_getattr_hook() in Objects/typeobject.c
545
- if hasattr(objtype, '__getattr__'):
546
- return objtype.__getattr__(obj, name) # __getattr__ hook
535
+ cls_var = getattr(objtype, name, null)
536
+ descr_get = getattr(type(cls_var), '__get__', null)
537
+ if descr_get is not null:
538
+ if (hasattr(type(cls_var), '__set__')
539
+ or hasattr(type(cls_var), '__delete__')):
540
+ return descr_get(cls_var, obj, objtype) # data descriptor
541
+ if hasattr(obj, '__dict__') and name in vars(obj):
542
+ return vars(obj)[name] # instance variable
543
+ if descr_get is not null:
544
+ return descr_get(cls_var, obj, objtype) # non-data descriptor
545
+ if cls_var is not null:
546
+ return cls_var # class variable
547
547
raise AttributeError(name)
548
548
549
- The :exc: `TypeError ` exception handler is needed because the instance dictionary
550
- doesn't exist when its class defines :term: `__slots__ `.
549
+ Interestingly, attribute lookup doesn't call :meth: `object.__getattribute__ `
550
+ directly. Instead, both the dot operator and the :func: `getattr ` function
551
+ perform attribute lookup by way of a helper function::
552
+
553
+ def getattr_hook(obj, name):
554
+ "Emulate slot_tp_getattr_hook() in Objects/typeobject.c"
555
+ try:
556
+ return obj.__getattribute__(name)
557
+ except AttributeError:
558
+ if not hasattr(type(obj), '__getattr__'):
559
+ raise
560
+ return type(obj).__getattr__(obj, name) # __getattr__
561
+
562
+ So if :meth: `__getattr__ ` exists, it is called whenever :meth: `__getattribute__ `
563
+ raises :exc: `AttributeError ` (either directly or in one of the descriptor calls).
564
+
565
+ Also, if a user calls :meth: `object.__getattribute__ ` directly, the
566
+ :meth: `__getattr__ ` hook is bypassed entirely.
551
567
552
568
553
569
Invocation from a class
@@ -690,6 +706,7 @@ it can be updated::
690
706
>>> Movie('Star Wars').director
691
707
'J.J. Abrams'
692
708
709
+
693
710
Pure Python Equivalents
694
711
^^^^^^^^^^^^^^^^^^^^^^^
695
712
0 commit comments