mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	Second round of updates to the descriptor howto guide (GH-22946) (GH-22958)
This commit is contained in:
		
							parent
							
								
									9cf26b00e4
								
							
						
					
					
						commit
						3d43f1dce3
					
				
					 1 changed files with 156 additions and 96 deletions
				
			
		| 
						 | 
				
			
			@ -29,8 +29,8 @@ This HowTo guide has three major sections:
 | 
			
		|||
Primer
 | 
			
		||||
^^^^^^
 | 
			
		||||
 | 
			
		||||
In this primer, we start with most basic possible example and then we'll add
 | 
			
		||||
new capabilities one by one.
 | 
			
		||||
In this primer, we start with the most basic possible example and then we'll
 | 
			
		||||
add new capabilities one by one.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Simple example: A descriptor that returns a constant
 | 
			
		||||
| 
						 | 
				
			
			@ -197,7 +197,7 @@ be recorded, giving each descriptor its own *public_name* and *private_name*::
 | 
			
		|||
 | 
			
		||||
    import logging
 | 
			
		||||
 | 
			
		||||
    logging.basicConfig(level=logging.INFO)
 | 
			
		||||
    logging.basicConfig(level=logging.INFO, force=True)
 | 
			
		||||
 | 
			
		||||
    class LoggedAccess:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -258,6 +258,10 @@ Closing thoughts
 | 
			
		|||
A :term:`descriptor` is what we call any object that defines :meth:`__get__`,
 | 
			
		||||
:meth:`__set__`, or :meth:`__delete__`.
 | 
			
		||||
 | 
			
		||||
Optionally, descriptors can have a :meth:`__set_name__` method.  This is only
 | 
			
		||||
used in cases where a descriptor needs to know either the class where it is
 | 
			
		||||
created or the name of class variable it was assigned to.
 | 
			
		||||
 | 
			
		||||
Descriptors get invoked by the dot operator during attribute lookup.  If a
 | 
			
		||||
descriptor is accessed indirectly with ``vars(some_class)[descriptor_name]``,
 | 
			
		||||
the descriptor instance is returned without invoking it.
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +295,7 @@ Validator class
 | 
			
		|||
A validator is a descriptor for managed attribute access.  Prior to storing
 | 
			
		||||
any data, it verifies that the new value meets various type and range
 | 
			
		||||
restrictions.  If those restrictions aren't met, it raises an exception to
 | 
			
		||||
prevents data corruption at its source.
 | 
			
		||||
prevent data corruption at its source.
 | 
			
		||||
 | 
			
		||||
This :class:`Validator` class is both an :term:`abstract base class` and a
 | 
			
		||||
managed attribute descriptor::
 | 
			
		||||
| 
						 | 
				
			
			@ -438,12 +442,12 @@ In general, a descriptor is an object attribute with "binding behavior", one
 | 
			
		|||
whose attribute access has been overridden by methods in the descriptor
 | 
			
		||||
protocol.  Those methods are :meth:`__get__`, :meth:`__set__`, and
 | 
			
		||||
:meth:`__delete__`.  If any of those methods are defined for an object, it is
 | 
			
		||||
said to be a descriptor.
 | 
			
		||||
said to be a :term:`descriptor`.
 | 
			
		||||
 | 
			
		||||
The default behavior for attribute access is to get, set, or delete the
 | 
			
		||||
attribute from an object's dictionary.  For instance, ``a.x`` has a lookup chain
 | 
			
		||||
starting with ``a.__dict__['x']``, then ``type(a).__dict__['x']``, and
 | 
			
		||||
continuing through the base classes of ``type(a)`` excluding metaclasses. If the
 | 
			
		||||
continuing through the base classes of ``type(a)``. If the
 | 
			
		||||
looked-up value is an object defining one of the descriptor methods, then Python
 | 
			
		||||
may override the default behavior and invoke the descriptor method instead.
 | 
			
		||||
Where this occurs in the precedence chain depends on which descriptor methods
 | 
			
		||||
| 
						 | 
				
			
			@ -492,60 +496,76 @@ Invoking Descriptors
 | 
			
		|||
A descriptor can be called directly by its method name.  For example,
 | 
			
		||||
``d.__get__(obj)``.
 | 
			
		||||
 | 
			
		||||
Alternatively, it is more common for a descriptor to be invoked automatically
 | 
			
		||||
upon attribute access.  For example, ``obj.d`` looks up ``d`` in the dictionary
 | 
			
		||||
of ``obj``.  If ``d`` defines the method :meth:`__get__`, then ``d.__get__(obj)``
 | 
			
		||||
But it is more common for a descriptor to be invoked automatically from
 | 
			
		||||
attribute access.  The expression ``obj.d`` looks up ``d`` in the dictionary of
 | 
			
		||||
``obj``.  If ``d`` defines the method :meth:`__get__`, then ``d.__get__(obj)``
 | 
			
		||||
is invoked according to the precedence rules listed below.
 | 
			
		||||
 | 
			
		||||
The details of invocation depend on whether ``obj`` is an object or a class.
 | 
			
		||||
The details of invocation depend on whether ``obj`` is an object, class, or
 | 
			
		||||
instance of super.
 | 
			
		||||
 | 
			
		||||
For objects, the machinery is in :meth:`object.__getattribute__` which
 | 
			
		||||
transforms ``b.x`` into ``type(b).__dict__['x'].__get__(b, type(b))``.  The
 | 
			
		||||
implementation works through a precedence chain that gives data descriptors
 | 
			
		||||
**Objects**:  The machinery is in :meth:`object.__getattribute__`.
 | 
			
		||||
 | 
			
		||||
It transforms ``b.x`` into ``type(b).__dict__['x'].__get__(b, type(b))``.
 | 
			
		||||
 | 
			
		||||
The implementation works through a precedence chain that gives data descriptors
 | 
			
		||||
priority over instance variables, instance variables priority over non-data
 | 
			
		||||
descriptors, and assigns lowest priority to :meth:`__getattr__` if provided.
 | 
			
		||||
 | 
			
		||||
The full C implementation can be found in :c:func:`PyObject_GenericGetAttr()` in
 | 
			
		||||
:source:`Objects/object.c`.
 | 
			
		||||
 | 
			
		||||
For classes, the machinery is in :meth:`type.__getattribute__` which transforms
 | 
			
		||||
``B.x`` into ``B.__dict__['x'].__get__(None, B)``.  In pure Python, it looks
 | 
			
		||||
like::
 | 
			
		||||
**Classes**:  The machinery is in :meth:`type.__getattribute__`.
 | 
			
		||||
 | 
			
		||||
    def __getattribute__(self, key):
 | 
			
		||||
It transforms ``A.x`` into ``A.__dict__['x'].__get__(None, A)``.
 | 
			
		||||
 | 
			
		||||
In pure Python, it looks like this::
 | 
			
		||||
 | 
			
		||||
    def __getattribute__(cls, key):
 | 
			
		||||
        "Emulate type_getattro() in Objects/typeobject.c"
 | 
			
		||||
        v = object.__getattribute__(self, key)
 | 
			
		||||
        v = object.__getattribute__(cls, key)
 | 
			
		||||
        if hasattr(v, '__get__'):
 | 
			
		||||
            return v.__get__(None, self)
 | 
			
		||||
            return v.__get__(None, cls)
 | 
			
		||||
        return v
 | 
			
		||||
 | 
			
		||||
The important points to remember are:
 | 
			
		||||
**Super**:  The machinery is in the custom :meth:`__getattribute__` method for
 | 
			
		||||
object returned by :class:`super()`.
 | 
			
		||||
 | 
			
		||||
* descriptors are invoked by the :meth:`__getattribute__` method
 | 
			
		||||
* overriding :meth:`__getattribute__` prevents automatic descriptor calls
 | 
			
		||||
* :meth:`object.__getattribute__` and :meth:`type.__getattribute__` make
 | 
			
		||||
  different calls to :meth:`__get__`.
 | 
			
		||||
* data descriptors always override instance dictionaries.
 | 
			
		||||
* non-data descriptors may be overridden by instance dictionaries.
 | 
			
		||||
The attribute lookup ``super(A, obj).m`` searches ``obj.__class__.__mro__`` for
 | 
			
		||||
the base class ``B`` immediately following ``A`` and then returns
 | 
			
		||||
``B.__dict__['m'].__get__(obj, A)``.
 | 
			
		||||
 | 
			
		||||
The object returned by ``super()`` also has a custom :meth:`__getattribute__`
 | 
			
		||||
method for invoking descriptors.  The attribute lookup ``super(B, obj).m`` searches
 | 
			
		||||
``obj.__class__.__mro__`` for the base class ``A`` immediately following ``B``
 | 
			
		||||
and then returns ``A.__dict__['m'].__get__(obj, B)``.  If not a descriptor,
 | 
			
		||||
``m`` is returned unchanged.  If not in the dictionary, ``m`` reverts to a
 | 
			
		||||
search using :meth:`object.__getattribute__`.
 | 
			
		||||
If not a descriptor, ``m`` is returned unchanged.  If not in the dictionary,
 | 
			
		||||
``m`` reverts to a search using :meth:`object.__getattribute__`.
 | 
			
		||||
 | 
			
		||||
The implementation details are in :c:func:`super_getattro()` in
 | 
			
		||||
:source:`Objects/typeobject.c`.  and a pure Python equivalent can be found in
 | 
			
		||||
:source:`Objects/typeobject.c`.  A pure Python equivalent can be found in
 | 
			
		||||
`Guido's Tutorial`_.
 | 
			
		||||
 | 
			
		||||
.. _`Guido's Tutorial`: https://www.python.org/download/releases/2.2.3/descrintro/#cooperation
 | 
			
		||||
 | 
			
		||||
The details above show that the mechanism for descriptors is embedded in the
 | 
			
		||||
:meth:`__getattribute__()` methods for :class:`object`, :class:`type`, and
 | 
			
		||||
:func:`super`.  Classes inherit this machinery when they derive from
 | 
			
		||||
:class:`object` or if they have a metaclass providing similar functionality.
 | 
			
		||||
Likewise, classes can turn-off descriptor invocation by overriding
 | 
			
		||||
:meth:`__getattribute__()`.
 | 
			
		||||
**Summary**:  The details listed above show that the mechanism for descriptors is
 | 
			
		||||
embedded in the :meth:`__getattribute__()` methods for :class:`object`,
 | 
			
		||||
:class:`type`, and :func:`super`.
 | 
			
		||||
 | 
			
		||||
The important points to remember are:
 | 
			
		||||
 | 
			
		||||
* Descriptors are invoked by the :meth:`__getattribute__` method.
 | 
			
		||||
 | 
			
		||||
* Classes inherit this machinery from :class:`object`, :class:`type`, or
 | 
			
		||||
  :func:`super`.
 | 
			
		||||
 | 
			
		||||
* Overriding :meth:`__getattribute__` prevents automatic descriptor calls
 | 
			
		||||
  because all the descriptor logic is in that method.
 | 
			
		||||
 | 
			
		||||
* :meth:`object.__getattribute__` and :meth:`type.__getattribute__` make
 | 
			
		||||
  different calls to :meth:`__get__`.  The first includes the instance and may
 | 
			
		||||
  include the class.  The second puts in ``None`` for the instance and always
 | 
			
		||||
  includes the class.
 | 
			
		||||
 | 
			
		||||
* Data descriptors always override instance dictionaries.
 | 
			
		||||
 | 
			
		||||
* Non-data descriptors may be overridden by instance dictionaries.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Automatic Name Notification
 | 
			
		||||
| 
						 | 
				
			
			@ -569,47 +589,70 @@ afterwards, :meth:`__set_name__` will need to be called manually.
 | 
			
		|||
Descriptor Example
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
The following code creates a class whose objects are data descriptors which
 | 
			
		||||
print a message for each get or set.  Overriding :meth:`__getattribute__` is
 | 
			
		||||
alternate approach that could do this for every attribute.  However, this
 | 
			
		||||
descriptor is useful for monitoring just a few chosen attributes::
 | 
			
		||||
The following code is simplified skeleton showing how data descriptors could
 | 
			
		||||
be used to implement an `object relational mapping
 | 
			
		||||
<https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping>`_.
 | 
			
		||||
 | 
			
		||||
    class RevealAccess:
 | 
			
		||||
        """A data descriptor that sets and returns values
 | 
			
		||||
           normally and prints a message logging their access.
 | 
			
		||||
        """
 | 
			
		||||
The essential idea is that instances only hold keys to a database table.  The
 | 
			
		||||
actual data is stored in an external table that is being dynamically updated::
 | 
			
		||||
 | 
			
		||||
        def __init__(self, initval=None, name='var'):
 | 
			
		||||
            self.val = initval
 | 
			
		||||
            self.name = name
 | 
			
		||||
    class Field:
 | 
			
		||||
 | 
			
		||||
        def __set_name__(self, owner, name):
 | 
			
		||||
            self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;'
 | 
			
		||||
            self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;'
 | 
			
		||||
 | 
			
		||||
        def __get__(self, obj, objtype=None):
 | 
			
		||||
            print('Retrieving', self.name)
 | 
			
		||||
            return self.val
 | 
			
		||||
            return conn.execute(self.fetch, [obj.key]).fetchone()[0]
 | 
			
		||||
 | 
			
		||||
        def __set__(self, obj, val):
 | 
			
		||||
            print('Updating', self.name)
 | 
			
		||||
            self.val = val
 | 
			
		||||
        def __set__(self, obj, value):
 | 
			
		||||
            conn.execute(self.store, [value, obj.key])
 | 
			
		||||
            conn.commit()
 | 
			
		||||
 | 
			
		||||
    class B:
 | 
			
		||||
        x = RevealAccess(10, 'var "x"')
 | 
			
		||||
        y = 5
 | 
			
		||||
We can use the :class:`Field` to define "models" that describe the schema for
 | 
			
		||||
each table in a database::
 | 
			
		||||
 | 
			
		||||
    >>> m = B()
 | 
			
		||||
    >>> m.x
 | 
			
		||||
    Retrieving var "x"
 | 
			
		||||
    10
 | 
			
		||||
    >>> m.x = 20
 | 
			
		||||
    Updating var "x"
 | 
			
		||||
    >>> m.x
 | 
			
		||||
    Retrieving var "x"
 | 
			
		||||
    20
 | 
			
		||||
    >>> m.y
 | 
			
		||||
    5
 | 
			
		||||
    class Movie:
 | 
			
		||||
        table = 'Movies'                    # Table name
 | 
			
		||||
        key = 'title'                       # Primary key
 | 
			
		||||
        director = Field()
 | 
			
		||||
        year = Field()
 | 
			
		||||
 | 
			
		||||
The protocol is simple and offers exciting possibilities.  Several use cases are
 | 
			
		||||
so common that they have been packaged into individual function calls.
 | 
			
		||||
Properties, bound methods, static methods, and class methods are all
 | 
			
		||||
        def __init__(self, key):
 | 
			
		||||
            self.key = key
 | 
			
		||||
 | 
			
		||||
    class Song:
 | 
			
		||||
        table = 'Music'
 | 
			
		||||
        key = 'title'
 | 
			
		||||
        artist = Field()
 | 
			
		||||
        year = Field()
 | 
			
		||||
        genre = Field()
 | 
			
		||||
 | 
			
		||||
        def __init__(self, key):
 | 
			
		||||
            self.key = key
 | 
			
		||||
 | 
			
		||||
An interactive session shows how data is retrieved from the database and how
 | 
			
		||||
it can be updated::
 | 
			
		||||
 | 
			
		||||
    >>> import sqlite3
 | 
			
		||||
    >>> conn = sqlite3.connect('entertainment.db')
 | 
			
		||||
 | 
			
		||||
    >>> Movie('Star Wars').director
 | 
			
		||||
    'George Lucas'
 | 
			
		||||
    >>> jaws = Movie('Jaws')
 | 
			
		||||
    >>> f'Released in {jaws.year} by {jaws.director}'
 | 
			
		||||
    'Released in 1975 by Steven Spielberg'
 | 
			
		||||
 | 
			
		||||
    >>> Song('Country Roads').artist
 | 
			
		||||
    'John Denver'
 | 
			
		||||
 | 
			
		||||
    >>> Movie('Star Wars').director = 'J.J. Abrams'
 | 
			
		||||
    >>> Movie('Star Wars').director
 | 
			
		||||
    'J.J. Abrams'
 | 
			
		||||
 | 
			
		||||
The descriptor protocol is simple and offers exciting possibilities.  Several
 | 
			
		||||
use cases are so common that they have been packaged into individual function
 | 
			
		||||
calls.  Properties, bound methods, static methods, and class methods are all
 | 
			
		||||
based on the descriptor protocol.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -619,7 +662,7 @@ Properties
 | 
			
		|||
Calling :func:`property` is a succinct way of building a data descriptor that
 | 
			
		||||
triggers function calls upon access to an attribute.  Its signature is::
 | 
			
		||||
 | 
			
		||||
    property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
 | 
			
		||||
    property(fget=None, fset=None, fdel=None, doc=None) -> property
 | 
			
		||||
 | 
			
		||||
The documentation shows a typical use to define a managed attribute ``x``::
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -695,17 +738,30 @@ Functions and Methods
 | 
			
		|||
Python's object oriented features are built upon a function based environment.
 | 
			
		||||
Using non-data descriptors, the two are merged seamlessly.
 | 
			
		||||
 | 
			
		||||
Class dictionaries store methods as functions.  In a class definition, methods
 | 
			
		||||
are written using :keyword:`def` or :keyword:`lambda`, the usual tools for
 | 
			
		||||
creating functions.  Methods only differ from regular functions in that the
 | 
			
		||||
first argument is reserved for the object instance.  By Python convention, the
 | 
			
		||||
instance reference is called *self* but may be called *this* or any other
 | 
			
		||||
variable name.
 | 
			
		||||
Functions stored in class dictionaries get turned into methods when invoked.
 | 
			
		||||
Methods only differ from regular functions in that the object instance is
 | 
			
		||||
prepended to the other arguments.  By convention, the instance is called
 | 
			
		||||
*self* but could be called *this* or any other variable name.
 | 
			
		||||
 | 
			
		||||
To support method calls, functions include the :meth:`__get__` method for
 | 
			
		||||
binding methods during attribute access.  This means that all functions are
 | 
			
		||||
non-data descriptors which return bound methods when they are invoked from an
 | 
			
		||||
object.  In pure Python, it works like this::
 | 
			
		||||
Methods can be created manually with :class:`types.MethodType` which is
 | 
			
		||||
roughly equivalent to::
 | 
			
		||||
 | 
			
		||||
    class Method:
 | 
			
		||||
        "Emulate Py_MethodType in Objects/classobject.c"
 | 
			
		||||
 | 
			
		||||
        def __init__(self, func, obj):
 | 
			
		||||
            self.__func__ = func
 | 
			
		||||
            self.__self__ = obj
 | 
			
		||||
 | 
			
		||||
        def __call__(self, *args, **kwargs):
 | 
			
		||||
            func = self.__func__
 | 
			
		||||
            obj = self.__self__
 | 
			
		||||
            return func(obj, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
To support automatic creation of methods, functions include the
 | 
			
		||||
:meth:`__get__` method for binding methods during attribute access.  This
 | 
			
		||||
means that functions are non-data descriptors which return bound methods
 | 
			
		||||
during dotted lookup from an instance.  Here's how it works::
 | 
			
		||||
 | 
			
		||||
    class Function:
 | 
			
		||||
        ...
 | 
			
		||||
| 
						 | 
				
			
			@ -716,15 +772,20 @@ object.  In pure Python, it works like this::
 | 
			
		|||
                return self
 | 
			
		||||
            return types.MethodType(self, obj)
 | 
			
		||||
 | 
			
		||||
Running the following in class in the interpreter shows how the function
 | 
			
		||||
Running the following class in the interpreter shows how the function
 | 
			
		||||
descriptor works in practice::
 | 
			
		||||
 | 
			
		||||
    class D:
 | 
			
		||||
        def f(self, x):
 | 
			
		||||
             return x
 | 
			
		||||
 | 
			
		||||
Access through the class dictionary does not invoke :meth:`__get__`.  Instead,
 | 
			
		||||
it just returns the underlying function object::
 | 
			
		||||
The function has a :term:`qualified name` attribute to support introspection::
 | 
			
		||||
 | 
			
		||||
    >>> D.f.__qualname__
 | 
			
		||||
    'D.f'
 | 
			
		||||
 | 
			
		||||
Accessing the function through the class dictionary does not invoke
 | 
			
		||||
:meth:`__get__`.  Instead, it just returns the underlying function object::
 | 
			
		||||
 | 
			
		||||
    >>> D.__dict__['f']
 | 
			
		||||
    <function D.f at 0x00C45070>
 | 
			
		||||
| 
						 | 
				
			
			@ -735,13 +796,8 @@ underlying function unchanged::
 | 
			
		|||
    >>> D.f
 | 
			
		||||
    <function D.f at 0x00C45070>
 | 
			
		||||
 | 
			
		||||
The function has a :term:`qualified name` attribute to support introspection::
 | 
			
		||||
 | 
			
		||||
    >>> D.f.__qualname__
 | 
			
		||||
    'D.f'
 | 
			
		||||
 | 
			
		||||
Dotted access from an instance calls :meth:`__get__` which returns a bound
 | 
			
		||||
method object::
 | 
			
		||||
The interesting behavior occurs during dotted access from an instance.  The
 | 
			
		||||
dotted lookup calls :meth:`__get__` which returns a bound method object::
 | 
			
		||||
 | 
			
		||||
    >>> d = D()
 | 
			
		||||
    >>> d.f
 | 
			
		||||
| 
						 | 
				
			
			@ -752,9 +808,13 @@ instance::
 | 
			
		|||
 | 
			
		||||
    >>> d.f.__func__
 | 
			
		||||
    <function D.f at 0x1012e5ae8>
 | 
			
		||||
 | 
			
		||||
    >>> d.f.__self__
 | 
			
		||||
    <__main__.D object at 0x1012e1f98>
 | 
			
		||||
 | 
			
		||||
If you have ever wondered where *self* comes from in regular methods or where
 | 
			
		||||
*cls* comes from in class methods, this is it!
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Static Methods and Class Methods
 | 
			
		||||
--------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -798,8 +858,8 @@ in statistical work but does not directly depend on a particular dataset.
 | 
			
		|||
It can be called either from an object or the class:  ``s.erf(1.5) --> .9332`` or
 | 
			
		||||
``Sample.erf(1.5) --> .9332``.
 | 
			
		||||
 | 
			
		||||
Since staticmethods return the underlying function with no changes, the example
 | 
			
		||||
calls are unexciting::
 | 
			
		||||
Since static methods return the underlying function with no changes, the
 | 
			
		||||
example calls are unexciting::
 | 
			
		||||
 | 
			
		||||
    class E:
 | 
			
		||||
        @staticmethod
 | 
			
		||||
| 
						 | 
				
			
			@ -840,7 +900,7 @@ for whether the caller is an object or a class::
 | 
			
		|||
 | 
			
		||||
This behavior is useful whenever the function only needs to have a class
 | 
			
		||||
reference and does not care about any underlying data.  One use for
 | 
			
		||||
classmethods is to create alternate class constructors.  The classmethod
 | 
			
		||||
class methods is to create alternate class constructors.  The classmethod
 | 
			
		||||
:func:`dict.fromkeys` creates a new dictionary from a list of keys.  The pure
 | 
			
		||||
Python equivalent is::
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue