| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | .. _descriptorhowto:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | ======================
 | 
					
						
							|  |  |  | Descriptor HowTo Guide
 | 
					
						
							|  |  |  | ======================
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :Author: Raymond Hettinger
 | 
					
						
							|  |  |  | :Contact: <python at rcn dot com>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. Contents::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | :term:`Descriptors <descriptor>` let objects customize attribute lookup,
 | 
					
						
							|  |  |  | storage, and deletion.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | This guide has four major sections:
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 1) The "primer" gives a basic overview, moving gently from simple examples,
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |    adding one feature at a time.  Start here if you're new to descriptors.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 2) The second section shows a complete, practical descriptor example.  If you
 | 
					
						
							|  |  |  |    already know the basics, start there.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) The third section provides a more technical tutorial that goes into the
 | 
					
						
							|  |  |  |    detailed mechanics of how descriptors work.  Most people don't need this
 | 
					
						
							|  |  |  |    level of detail.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | 4) The last section has pure Python equivalents for built-in descriptors that
 | 
					
						
							|  |  |  |    are written in C.  Read this if you're curious about how functions turn
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  |    into bound methods or about the implementation of common tools like
 | 
					
						
							|  |  |  |    :func:`classmethod`, :func:`staticmethod`, :func:`property`, and
 | 
					
						
							|  |  |  |    :term:`__slots__`.
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Primer
 | 
					
						
							|  |  |  | ^^^^^^
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | In this primer, we start with the most basic possible example and then we'll
 | 
					
						
							|  |  |  | add new capabilities one by one.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Simple example: A descriptor that returns a constant
 | 
					
						
							|  |  |  | ----------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 22:05:42 -08:00
										 |  |  | The :class:`Ten` class is a descriptor whose :meth:`__get__` method always
 | 
					
						
							|  |  |  | returns the constant ``10``:
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class Ten:
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             return 10
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | To use the descriptor, it must be stored as a class variable in another class:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class A:
 | 
					
						
							|  |  |  |         x = 5                       # Regular class attribute
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |         y = Ten()                   # Descriptor instance
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | An interactive session shows the difference between normal attribute lookup
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | and descriptor lookup:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> a = A()                     # Make an instance of class A
 | 
					
						
							|  |  |  |     >>> a.x                         # Normal attribute lookup
 | 
					
						
							|  |  |  |     5
 | 
					
						
							|  |  |  |     >>> a.y                         # Descriptor lookup
 | 
					
						
							|  |  |  |     10
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 22:05:42 -08:00
										 |  |  | In the ``a.x`` attribute lookup, the dot operator finds ``'x': 5``
 | 
					
						
							|  |  |  | in the class dictionary.  In the ``a.y`` lookup, the dot operator
 | 
					
						
							|  |  |  | finds a descriptor instance, recognized by its ``__get__`` method.
 | 
					
						
							|  |  |  | Calling that method returns ``10``.
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | Note that the value ``10`` is not stored in either the class dictionary or the
 | 
					
						
							|  |  |  | instance dictionary.  Instead, the value ``10`` is computed on demand.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This example shows how a simple descriptor works, but it isn't very useful.
 | 
					
						
							|  |  |  | For retrieving constants, normal attribute lookup would be better.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In the next section, we'll create something more useful, a dynamic lookup.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Dynamic lookups
 | 
					
						
							|  |  |  | ---------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | Interesting descriptors typically run computations instead of returning
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | constants:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     import os
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class DirectorySize:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             return len(os.listdir(obj.dirname))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Directory:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |         size = DirectorySize()              # Descriptor instance
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, dirname):
 | 
					
						
							|  |  |  |             self.dirname = dirname          # Regular instance attribute
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | An interactive session shows that the lookup is dynamic — it computes
 | 
					
						
							|  |  |  | different, updated answers each time::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> s = Directory('songs')
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |     >>> g = Directory('games')
 | 
					
						
							|  |  |  |     >>> s.size                              # The songs directory has twenty files
 | 
					
						
							|  |  |  |     20
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     >>> g.size                              # The games directory has three files
 | 
					
						
							|  |  |  |     3
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:09:01 -07:00
										 |  |  |     >>> os.remove('games/chess')            # Delete a game
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |     >>> g.size                              # File count is automatically updated
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:09:01 -07:00
										 |  |  |     2
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Besides showing how descriptors can run computations, this example also
 | 
					
						
							|  |  |  | reveals the purpose of the parameters to :meth:`__get__`.  The *self*
 | 
					
						
							|  |  |  | parameter is *size*, an instance of *DirectorySize*.  The *obj* parameter is
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | either *g* or *s*, an instance of *Directory*.  It is the *obj* parameter that
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | lets the :meth:`__get__` method learn the target directory.  The *objtype*
 | 
					
						
							|  |  |  | parameter is the class *Directory*.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Managed attributes
 | 
					
						
							|  |  |  | ------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | A popular use for descriptors is managing access to instance data.  The
 | 
					
						
							|  |  |  | descriptor is assigned to a public attribute in the class dictionary while the
 | 
					
						
							|  |  |  | actual data is stored as a private attribute in the instance dictionary.  The
 | 
					
						
							|  |  |  | descriptor's :meth:`__get__` and :meth:`__set__` methods are triggered when
 | 
					
						
							|  |  |  | the public attribute is accessed.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In the following example, *age* is the public attribute and *_age* is the
 | 
					
						
							|  |  |  | private attribute.  When the public attribute is accessed, the descriptor logs
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | the lookup or update:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     import logging
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logging.basicConfig(level=logging.INFO)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class LoggedAgeAccess:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             value = obj._age
 | 
					
						
							|  |  |  |             logging.info('Accessing %r giving %r', 'age', value)
 | 
					
						
							|  |  |  |             return value
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             logging.info('Updating %r to %r', 'age', value)
 | 
					
						
							|  |  |  |             obj._age = value
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Person:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |         age = LoggedAgeAccess()             # Descriptor instance
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, name, age):
 | 
					
						
							|  |  |  |             self.name = name                # Regular instance attribute
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |             self.age = age                  # Calls __set__()
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def birthday(self):
 | 
					
						
							|  |  |  |             self.age += 1                   # Calls both __get__() and __set__()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | An interactive session shows that all access to the managed attribute *age* is
 | 
					
						
							| 
									
										
										
										
											2020-11-25 01:54:24 -08:00
										 |  |  | logged, but that the regular attribute *name* is not logged:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import logging, sys
 | 
					
						
							|  |  |  |     logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> mary = Person('Mary M', 30)         # The initial age update is logged
 | 
					
						
							|  |  |  |     INFO:root:Updating 'age' to 30
 | 
					
						
							|  |  |  |     >>> dave = Person('David D', 40)
 | 
					
						
							|  |  |  |     INFO:root:Updating 'age' to 40
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> vars(mary)                          # The actual data is in a private attribute
 | 
					
						
							|  |  |  |     {'name': 'Mary M', '_age': 30}
 | 
					
						
							|  |  |  |     >>> vars(dave)
 | 
					
						
							|  |  |  |     {'name': 'David D', '_age': 40}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> mary.age                            # Access the data and log the lookup
 | 
					
						
							|  |  |  |     INFO:root:Accessing 'age' giving 30
 | 
					
						
							|  |  |  |     30
 | 
					
						
							|  |  |  |     >>> mary.birthday()                     # Updates are logged as well
 | 
					
						
							|  |  |  |     INFO:root:Accessing 'age' giving 30
 | 
					
						
							|  |  |  |     INFO:root:Updating 'age' to 31
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> dave.name                           # Regular attribute lookup isn't logged
 | 
					
						
							|  |  |  |     'David D'
 | 
					
						
							|  |  |  |     >>> dave.age                            # Only the managed attribute is logged
 | 
					
						
							|  |  |  |     INFO:root:Accessing 'age' giving 40
 | 
					
						
							|  |  |  |     40
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | One major issue with this example is that the private name *_age* is hardwired in
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | the *LoggedAgeAccess* class.  That means that each instance can only have one
 | 
					
						
							|  |  |  | logged attribute and that its name is unchangeable.  In the next example,
 | 
					
						
							|  |  |  | we'll fix that problem.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Customized names
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | ----------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | When a class uses descriptors, it can inform each descriptor about which
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | variable name was used.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In this example, the :class:`Person` class has two descriptor instances,
 | 
					
						
							|  |  |  | *name* and *age*.  When the :class:`Person` class is defined, it makes a
 | 
					
						
							|  |  |  | callback to :meth:`__set_name__` in *LoggedAccess* so that the field names can
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | be recorded, giving each descriptor its own *public_name* and *private_name*:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     import logging
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |     logging.basicConfig(level=logging.INFO)
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class LoggedAccess:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set_name__(self, owner, name):
 | 
					
						
							|  |  |  |             self.public_name = name
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |             self.private_name = '_' + name
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             value = getattr(obj, self.private_name)
 | 
					
						
							|  |  |  |             logging.info('Accessing %r giving %r', self.public_name, value)
 | 
					
						
							|  |  |  |             return value
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             logging.info('Updating %r to %r', self.public_name, value)
 | 
					
						
							|  |  |  |             setattr(obj, self.private_name, value)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Person:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |         name = LoggedAccess()                # First descriptor instance
 | 
					
						
							|  |  |  |         age = LoggedAccess()                 # Second descriptor instance
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, name, age):
 | 
					
						
							|  |  |  |             self.name = name                 # Calls the first descriptor
 | 
					
						
							|  |  |  |             self.age = age                   # Calls the second descriptor
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def birthday(self):
 | 
					
						
							|  |  |  |             self.age += 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | An interactive session shows that the :class:`Person` class has called
 | 
					
						
							|  |  |  | :meth:`__set_name__` so that the field names would be recorded.  Here
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | we call :func:`vars` to look up the descriptor without triggering it:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> vars(vars(Person)['name'])
 | 
					
						
							|  |  |  |     {'public_name': 'name', 'private_name': '_name'}
 | 
					
						
							|  |  |  |     >>> vars(vars(Person)['age'])
 | 
					
						
							|  |  |  |     {'public_name': 'age', 'private_name': '_age'}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-25 01:54:24 -08:00
										 |  |  | The new class now logs access to both *name* and *age*:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import logging, sys
 | 
					
						
							|  |  |  |     logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> pete = Person('Peter P', 10)
 | 
					
						
							|  |  |  |     INFO:root:Updating 'name' to 'Peter P'
 | 
					
						
							|  |  |  |     INFO:root:Updating 'age' to 10
 | 
					
						
							|  |  |  |     >>> kate = Person('Catherine C', 20)
 | 
					
						
							|  |  |  |     INFO:root:Updating 'name' to 'Catherine C'
 | 
					
						
							|  |  |  |     INFO:root:Updating 'age' to 20
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | The two *Person* instances contain only the private names:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> vars(pete)
 | 
					
						
							|  |  |  |     {'_name': 'Peter P', '_age': 10}
 | 
					
						
							|  |  |  |     >>> vars(kate)
 | 
					
						
							|  |  |  |     {'_name': 'Catherine C', '_age': 20}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Closing thoughts
 | 
					
						
							|  |  |  | ----------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | A :term:`descriptor` is what we call any object that defines :meth:`__get__`,
 | 
					
						
							|  |  |  | :meth:`__set__`, or :meth:`__delete__`.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | Optionally, descriptors can have a :meth:`__set_name__` method.  This is only
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | used in cases where a descriptor needs to know either the class where it was
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | created or the name of class variable it was assigned to.  (This method, if
 | 
					
						
							|  |  |  | present, is called even if the class is not a descriptor.)
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 22:05:42 -08:00
										 |  |  | Descriptors get invoked by the dot operator during attribute lookup.  If a
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | descriptor is accessed indirectly with ``vars(some_class)[descriptor_name]``,
 | 
					
						
							|  |  |  | the descriptor instance is returned without invoking it.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Descriptors only work when used as class variables.  When put in instances,
 | 
					
						
							|  |  |  | they have no effect.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The main motivation for descriptors is to provide a hook allowing objects
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | stored in class variables to control what happens during attribute lookup.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Traditionally, the calling class controls what happens during lookup.
 | 
					
						
							|  |  |  | Descriptors invert that relationship and allow the data being looked-up to
 | 
					
						
							|  |  |  | have a say in the matter.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Descriptors are used throughout the language.  It is how functions turn into
 | 
					
						
							|  |  |  | bound methods.  Common tools like :func:`classmethod`, :func:`staticmethod`,
 | 
					
						
							|  |  |  | :func:`property`, and :func:`functools.cached_property` are all implemented as
 | 
					
						
							|  |  |  | descriptors.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Complete Practical Example
 | 
					
						
							|  |  |  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In this example, we create a practical and powerful tool for locating
 | 
					
						
							|  |  |  | notoriously hard to find data corruption bugs.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | prevent data corruption at its source.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | This :class:`Validator` class is both an :term:`abstract base class` and a
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | managed attribute descriptor:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     from abc import ABC, abstractmethod
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Validator(ABC):
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set_name__(self, owner, name):
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |             self.private_name = '_' + name
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             return getattr(obj, self.private_name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             self.validate(value)
 | 
					
						
							|  |  |  |             setattr(obj, self.private_name, value)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @abstractmethod
 | 
					
						
							|  |  |  |         def validate(self, value):
 | 
					
						
							|  |  |  |             pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | Custom validators need to inherit from :class:`Validator` and must supply a
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | :meth:`validate` method to test various restrictions as needed.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Custom validators
 | 
					
						
							|  |  |  | -----------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Here are three practical data validation utilities:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) :class:`OneOf` verifies that a value is one of a restricted set of options.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2) :class:`Number` verifies that a value is either an :class:`int` or
 | 
					
						
							|  |  |  |    :class:`float`.  Optionally, it verifies that a value is between a given
 | 
					
						
							|  |  |  |    minimum or maximum.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) :class:`String` verifies that a value is a :class:`str`.  Optionally, it
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |    validates a given minimum or maximum length.  It can validate a
 | 
					
						
							|  |  |  |    user-defined `predicate
 | 
					
						
							|  |  |  |    <https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)>`_ as well.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class OneOf(Validator):
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, *options):
 | 
					
						
							|  |  |  |             self.options = set(options)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def validate(self, value):
 | 
					
						
							|  |  |  |             if value not in self.options:
 | 
					
						
							|  |  |  |                 raise ValueError(f'Expected {value!r} to be one of {self.options!r}')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Number(Validator):
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, minvalue=None, maxvalue=None):
 | 
					
						
							|  |  |  |             self.minvalue = minvalue
 | 
					
						
							|  |  |  |             self.maxvalue = maxvalue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def validate(self, value):
 | 
					
						
							|  |  |  |             if not isinstance(value, (int, float)):
 | 
					
						
							|  |  |  |                 raise TypeError(f'Expected {value!r} to be an int or float')
 | 
					
						
							|  |  |  |             if self.minvalue is not None and value < self.minvalue:
 | 
					
						
							|  |  |  |                 raise ValueError(
 | 
					
						
							|  |  |  |                     f'Expected {value!r} to be at least {self.minvalue!r}'
 | 
					
						
							|  |  |  |                 )
 | 
					
						
							|  |  |  |             if self.maxvalue is not None and value > self.maxvalue:
 | 
					
						
							|  |  |  |                 raise ValueError(
 | 
					
						
							|  |  |  |                     f'Expected {value!r} to be no more than {self.maxvalue!r}'
 | 
					
						
							|  |  |  |                 )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class String(Validator):
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, minsize=None, maxsize=None, predicate=None):
 | 
					
						
							|  |  |  |             self.minsize = minsize
 | 
					
						
							|  |  |  |             self.maxsize = maxsize
 | 
					
						
							|  |  |  |             self.predicate = predicate
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def validate(self, value):
 | 
					
						
							|  |  |  |             if not isinstance(value, str):
 | 
					
						
							|  |  |  |                 raise TypeError(f'Expected {value!r} to be an str')
 | 
					
						
							|  |  |  |             if self.minsize is not None and len(value) < self.minsize:
 | 
					
						
							|  |  |  |                 raise ValueError(
 | 
					
						
							|  |  |  |                     f'Expected {value!r} to be no smaller than {self.minsize!r}'
 | 
					
						
							|  |  |  |                 )
 | 
					
						
							|  |  |  |             if self.maxsize is not None and len(value) > self.maxsize:
 | 
					
						
							|  |  |  |                 raise ValueError(
 | 
					
						
							|  |  |  |                     f'Expected {value!r} to be no bigger than {self.maxsize!r}'
 | 
					
						
							|  |  |  |                 )
 | 
					
						
							|  |  |  |             if self.predicate is not None and not self.predicate(value):
 | 
					
						
							|  |  |  |                 raise ValueError(
 | 
					
						
							|  |  |  |                     f'Expected {self.predicate} to be true for {value!r}'
 | 
					
						
							|  |  |  |                 )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | Practical application
 | 
					
						
							|  |  |  | ---------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Here's how the data validators can be used in a real class:
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class Component:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         name = String(minsize=3, maxsize=10, predicate=str.isupper)
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |         kind = OneOf('wood', 'metal', 'plastic')
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         quantity = Number(minvalue=0)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, name, kind, quantity):
 | 
					
						
							|  |  |  |             self.name = name
 | 
					
						
							|  |  |  |             self.kind = kind
 | 
					
						
							|  |  |  |             self.quantity = quantity
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-25 01:54:24 -08:00
										 |  |  | The descriptors prevent invalid instances from being created:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     >>> Component('Widget', 'metal', 5)      # Blocked: 'Widget' is not all uppercase
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget'
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> Component('WIDGET', 'metle', 5)      # Blocked: 'metle' is misspelled
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> Component('WIDGET', 'metal', -5)     # Blocked: -5 is negative
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     ValueError: Expected -5 to be at least 0
 | 
					
						
							|  |  |  |     >>> Component('WIDGET', 'metal', 'V')    # Blocked: 'V' isn't a number
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     TypeError: Expected 'V' to be an int or float
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> c = Component('WIDGET', 'metal', 5)  # Allowed:  The inputs are valid
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Technical Tutorial
 | 
					
						
							|  |  |  | ^^^^^^^^^^^^^^^^^^
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | What follows is a more technical tutorial for the mechanics and details of how
 | 
					
						
							|  |  |  | descriptors work.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | Abstract
 | 
					
						
							|  |  |  | --------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Defines descriptors, summarizes the protocol, and shows how descriptors are
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | called.  Provides an example showing how object relational mappings work.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Learning about descriptors not only provides access to a larger toolset, it
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | creates a deeper understanding of how Python works.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Definition and introduction
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | ---------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | In general, a descriptor is an attribute value that has one of the methods in
 | 
					
						
							|  |  |  | the descriptor protocol.  Those methods are :meth:`__get__`, :meth:`__set__`,
 | 
					
						
							| 
									
										
										
										
											2021-02-02 22:28:36 -05:00
										 |  |  | and :meth:`__delete__`.  If any of those methods are defined for an
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | attribute, it is said to be a :term:`descriptor`.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | continuing through the method resolution order of ``type(a)``. If the
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 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
 | 
					
						
							| 
									
										
										
										
											2011-12-12 18:54:29 +01:00
										 |  |  | were defined.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Descriptors are a powerful, general purpose protocol.  They are the mechanism
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | behind properties, methods, static methods, class methods, and
 | 
					
						
							|  |  |  | :func:`super()`.  They are used throughout Python itself.  Descriptors
 | 
					
						
							|  |  |  | simplify the underlying C code and offer a flexible set of new tools for
 | 
					
						
							|  |  |  | everyday Python programs.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Descriptor protocol
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | -------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 23:35:38 +02:00
										 |  |  | ``descr.__get__(self, obj, type=None) -> value``
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 23:35:38 +02:00
										 |  |  | ``descr.__set__(self, obj, value) -> None``
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 23:35:38 +02:00
										 |  |  | ``descr.__delete__(self, obj) -> None``
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | That is all there is to it.  Define any of these methods and an object is
 | 
					
						
							|  |  |  | considered a descriptor and can override default behavior upon being looked up
 | 
					
						
							|  |  |  | as an attribute.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-20 19:46:42 -04:00
										 |  |  | If an object defines :meth:`__set__` or :meth:`__delete__`, it is considered
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | a data descriptor.  Descriptors that only define :meth:`__get__` are called
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | non-data descriptors (they are often used for methods but other uses are
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | possible).
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Data and non-data descriptors differ in how overrides are calculated with
 | 
					
						
							|  |  |  | respect to entries in an instance's dictionary.  If an instance's dictionary
 | 
					
						
							|  |  |  | has an entry with the same name as a data descriptor, the data descriptor
 | 
					
						
							|  |  |  | takes precedence.  If an instance's dictionary has an entry with the same
 | 
					
						
							|  |  |  | name as a non-data descriptor, the dictionary entry takes precedence.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | To make a read-only data descriptor, define both :meth:`__get__` and
 | 
					
						
							|  |  |  | :meth:`__set__` with the :meth:`__set__` raising an :exc:`AttributeError` when
 | 
					
						
							|  |  |  | called.  Defining the :meth:`__set__` method with an exception raising
 | 
					
						
							|  |  |  | placeholder is enough to make it a data descriptor.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Overview of descriptor invocation
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | ---------------------------------
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | A descriptor can be called directly with ``desc.__get__(obj)`` or
 | 
					
						
							|  |  |  | ``desc.__get__(None, cls)``.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | But it is more common for a descriptor to be invoked automatically from
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | attribute access.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The expression ``obj.x`` looks up the attribute ``x`` in the chain of
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | namespaces for ``obj``.  If the search finds a descriptor outside of the
 | 
					
						
							|  |  |  | instance ``__dict__``, its :meth:`__get__` method is invoked according to the
 | 
					
						
							|  |  |  | precedence rules listed below.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | The details of invocation depend on whether ``obj`` is an object, class, or
 | 
					
						
							|  |  |  | instance of super.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Invocation from an instance
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | ---------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Instance lookup scans through a chain of namespaces giving data descriptors
 | 
					
						
							|  |  |  | the highest priority, followed by instance variables, then non-data
 | 
					
						
							|  |  |  | descriptors, then class variables, and lastly :meth:`__getattr__` if it is
 | 
					
						
							|  |  |  | provided.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | If a descriptor is found for ``a.x``, then it is invoked with:
 | 
					
						
							|  |  |  | ``desc.__get__(a, type(a))``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The logic for a dotted lookup is in :meth:`object.__getattribute__`.  Here is
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | a pure Python equivalent:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 23:56:58 -05:00
										 |  |  |     def find_name_in_mro(cls, name, default):
 | 
					
						
							|  |  |  |         "Emulate _PyType_Lookup() in Objects/typeobject.c"
 | 
					
						
							|  |  |  |         for base in cls.__mro__:
 | 
					
						
							|  |  |  |             if name in vars(base):
 | 
					
						
							|  |  |  |                 return vars(base)[name]
 | 
					
						
							|  |  |  |         return default
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |     def object_getattribute(obj, name):
 | 
					
						
							|  |  |  |         "Emulate PyObject_GenericGetAttr() in Objects/object.c"
 | 
					
						
							|  |  |  |         null = object()
 | 
					
						
							|  |  |  |         objtype = type(obj)
 | 
					
						
							| 
									
										
										
										
											2022-08-18 23:56:58 -05:00
										 |  |  |         cls_var = find_name_in_mro(objtype, name, null)
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  |         descr_get = getattr(type(cls_var), '__get__', null)
 | 
					
						
							|  |  |  |         if descr_get is not null:
 | 
					
						
							|  |  |  |             if (hasattr(type(cls_var), '__set__')
 | 
					
						
							|  |  |  |                 or hasattr(type(cls_var), '__delete__')):
 | 
					
						
							|  |  |  |                 return descr_get(cls_var, obj, objtype)     # data descriptor
 | 
					
						
							|  |  |  |         if hasattr(obj, '__dict__') and name in vars(obj):
 | 
					
						
							|  |  |  |             return vars(obj)[name]                          # instance variable
 | 
					
						
							|  |  |  |         if descr_get is not null:
 | 
					
						
							|  |  |  |             return descr_get(cls_var, obj, objtype)         # non-data descriptor
 | 
					
						
							|  |  |  |         if cls_var is not null:
 | 
					
						
							|  |  |  |             return cls_var                                  # class variable
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  |         raise AttributeError(name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Test the fidelity of object_getattribute() by comparing it with the
 | 
					
						
							|  |  |  |     # normal object.__getattribute__().  The former will be accessed by
 | 
					
						
							|  |  |  |     # square brackets and the latter by the dot operator.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Object:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __getitem__(obj, name):
 | 
					
						
							|  |  |  |             try:
 | 
					
						
							|  |  |  |                 return object_getattribute(obj, name)
 | 
					
						
							|  |  |  |             except AttributeError:
 | 
					
						
							|  |  |  |                 if not hasattr(type(obj), '__getattr__'):
 | 
					
						
							|  |  |  |                     raise
 | 
					
						
							|  |  |  |             return type(obj).__getattr__(obj, name)             # __getattr__
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class DualOperator(Object):
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         x = 10
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, z):
 | 
					
						
							|  |  |  |             self.z = z
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def p2(self):
 | 
					
						
							|  |  |  |             return 2 * self.x
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def p3(self):
 | 
					
						
							|  |  |  |             return 3 * self.x
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def m5(self, y):
 | 
					
						
							|  |  |  |             return 5 * y
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def m7(self, y):
 | 
					
						
							|  |  |  |             return 7 * y
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __getattr__(self, name):
 | 
					
						
							|  |  |  |             return ('getattr_hook', self, name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class DualOperatorWithSlots:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         __getitem__ = Object.__getitem__
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         __slots__ = ['z']
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         x = 15
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, z):
 | 
					
						
							|  |  |  |             self.z = z
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def p2(self):
 | 
					
						
							|  |  |  |             return 2 * self.x
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def m5(self, y):
 | 
					
						
							|  |  |  |             return 5 * y
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __getattr__(self, name):
 | 
					
						
							|  |  |  |             return ('getattr_hook', self, name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 23:56:58 -05:00
										 |  |  |     class D1:
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             return type(self), obj, objtype
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class U1:
 | 
					
						
							|  |  |  |         x = D1()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class U2(U1):
 | 
					
						
							|  |  |  |         pass
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> a = DualOperator(11)
 | 
					
						
							|  |  |  |     >>> vars(a).update(p3 = '_p3', m7 = '_m7')
 | 
					
						
							|  |  |  |     >>> a.x == a['x'] == 10
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.z == a['z'] == 11
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.p2 == a['p2'] == 20
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.p3 == a['p3'] == 30
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.m5(100) == a.m5(100) == 500
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.m7 == a['m7'] == '_m7'
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> a.g == a['g'] == ('getattr_hook', a, 'g')
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> b = DualOperatorWithSlots(22)
 | 
					
						
							|  |  |  |     >>> b.x == b['x'] == 15
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> b.z == b['z'] == 22
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> b.p2 == b['p2'] == 30
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> b.m5(200) == b['m5'](200) == 1000
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> b.g == b['g'] == ('getattr_hook', b, 'g')
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 23:56:58 -05:00
										 |  |  |     >>> u2 = U2()
 | 
					
						
							|  |  |  |     >>> object_getattribute(u2, 'x') == u2.x == (D1, u2, U2)
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-19 23:02:30 -06:00
										 |  |  | Note, there is no :meth:`__getattr__` hook in the :meth:`__getattribute__`
 | 
					
						
							|  |  |  | code.  That is why calling :meth:`__getattribute__` directly or with
 | 
					
						
							|  |  |  | ``super().__getattribute__`` will bypass :meth:`__getattr__` entirely.
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-19 23:02:30 -06:00
										 |  |  | Instead, it is the dot operator and the :func:`getattr` function that are
 | 
					
						
							|  |  |  | responsible for invoking :meth:`__getattr__` whenever :meth:`__getattribute__`
 | 
					
						
							|  |  |  | raises an :exc:`AttributeError`.  Their logic is encapsulated in a helper
 | 
					
						
							|  |  |  | function:
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def getattr_hook(obj, name):
 | 
					
						
							|  |  |  |         "Emulate slot_tp_getattr_hook() in Objects/typeobject.c"
 | 
					
						
							|  |  |  |         try:
 | 
					
						
							|  |  |  |             return obj.__getattribute__(name)
 | 
					
						
							|  |  |  |         except AttributeError:
 | 
					
						
							|  |  |  |             if not hasattr(type(obj), '__getattr__'):
 | 
					
						
							|  |  |  |                 raise
 | 
					
						
							|  |  |  |         return type(obj).__getattr__(obj, name)             # __getattr__
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> class ClassWithGetAttr:
 | 
					
						
							|  |  |  |     ...     x = 123
 | 
					
						
							|  |  |  |     ...     def __getattr__(self, attr):
 | 
					
						
							|  |  |  |     ...         return attr.upper()
 | 
					
						
							|  |  |  |     ...
 | 
					
						
							|  |  |  |     >>> cw = ClassWithGetAttr()
 | 
					
						
							|  |  |  |     >>> cw.y = 456
 | 
					
						
							|  |  |  |     >>> getattr_hook(cw, 'x')
 | 
					
						
							|  |  |  |     123
 | 
					
						
							|  |  |  |     >>> getattr_hook(cw, 'y')
 | 
					
						
							|  |  |  |     456
 | 
					
						
							|  |  |  |     >>> getattr_hook(cw, 'z')
 | 
					
						
							|  |  |  |     'Z'
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> class ClassWithoutGetAttr:
 | 
					
						
							|  |  |  |     ...     x = 123
 | 
					
						
							|  |  |  |     ...
 | 
					
						
							|  |  |  |     >>> cwo = ClassWithoutGetAttr()
 | 
					
						
							|  |  |  |     >>> cwo.y = 456
 | 
					
						
							|  |  |  |     >>> getattr_hook(cwo, 'x')
 | 
					
						
							|  |  |  |     123
 | 
					
						
							|  |  |  |     >>> getattr_hook(cwo, 'y')
 | 
					
						
							|  |  |  |     456
 | 
					
						
							|  |  |  |     >>> getattr_hook(cwo, 'z')
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     AttributeError: 'ClassWithoutGetAttr' object has no attribute 'z'
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Invocation from a class
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | -----------------------
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | The logic for a dotted lookup such as ``A.x`` is in
 | 
					
						
							|  |  |  | :meth:`type.__getattribute__`.  The steps are similar to those for
 | 
					
						
							|  |  |  | :meth:`object.__getattribute__` but the instance dictionary lookup is replaced
 | 
					
						
							|  |  |  | by a search through the class's :term:`method resolution order`.
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | If a descriptor is found, it is invoked with ``desc.__get__(None, A)``.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | The full C implementation can be found in :c:func:`type_getattro()` and
 | 
					
						
							|  |  |  | :c:func:`_PyType_Lookup()` in :source:`Objects/typeobject.c`.
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Invocation from super
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | ---------------------
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | The logic for super's dotted lookup is in the :meth:`__getattribute__` method for
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | object returned by :class:`super()`.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | A dotted lookup such as ``super(A, obj).m`` searches ``obj.__class__.__mro__``
 | 
					
						
							|  |  |  | for the base class ``B`` immediately following ``A`` and then returns
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | ``B.__dict__['m'].__get__(obj, A)``.  If not a descriptor, ``m`` is returned
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | unchanged.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | The full C implementation can be found in :c:func:`super_getattro()` in
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | :source:`Objects/typeobject.c`.  A pure Python equivalent can be found in
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | `Guido's Tutorial
 | 
					
						
							|  |  |  | <https://www.python.org/download/releases/2.2.3/descrintro/#cooperation>`_.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Summary of invocation logic
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | ---------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The mechanism for descriptors is embedded in the :meth:`__getattribute__()`
 | 
					
						
							|  |  |  | methods for :class:`object`, :class:`type`, and :func:`super`.
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Automatic name notification
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | ---------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Sometimes it is desirable for a descriptor to know what class variable name it
 | 
					
						
							|  |  |  | was assigned to.  When a new class is created, the :class:`type` metaclass
 | 
					
						
							|  |  |  | scans the dictionary of the new class.  If any of the entries are descriptors
 | 
					
						
							|  |  |  | and if they define :meth:`__set_name__`, that method is called with two
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | arguments.  The *owner* is the class where the descriptor is used, and the
 | 
					
						
							|  |  |  | *name* is the class variable the descriptor was assigned to.
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | The implementation details are in :c:func:`type_new()` and
 | 
					
						
							|  |  |  | :c:func:`set_names()` in :source:`Objects/typeobject.c`.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Since the update logic is in :meth:`type.__new__`, notifications only take
 | 
					
						
							|  |  |  | place at the time of class creation.  If descriptors are added to the class
 | 
					
						
							|  |  |  | afterwards, :meth:`__set_name__` will need to be called manually.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | ORM example
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | -----------
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  | The following code is a simplified skeleton showing how data descriptors could
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | be used to implement an `object relational mapping
 | 
					
						
							|  |  |  | <https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping>`_.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | The essential idea is that the data is stored in an external database.  The
 | 
					
						
							|  |  |  | Python instances only hold keys to the database's tables.  Descriptors take
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | care of lookups or updates:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |     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}=?;'
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |             return conn.execute(self.fetch, [obj.key]).fetchone()[0]
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             conn.execute(self.store, [value, obj.key])
 | 
					
						
							|  |  |  |             conn.commit()
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | We can use the :class:`Field` class to define `models
 | 
					
						
							|  |  |  | <https://en.wikipedia.org/wiki/Database_model>`_ that describe the schema for
 | 
					
						
							|  |  |  | each table in a database:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |     class Movie:
 | 
					
						
							|  |  |  |         table = 'Movies'                    # Table name
 | 
					
						
							|  |  |  |         key = 'title'                       # Primary key
 | 
					
						
							|  |  |  |         director = Field()
 | 
					
						
							|  |  |  |         year = Field()
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |         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
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | To use the models, first connect to the database::
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> import sqlite3
 | 
					
						
							|  |  |  |     >>> conn = sqlite3.connect('entertainment.db')
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | An interactive session shows how data is retrieved from the database and how
 | 
					
						
							|  |  |  | it can be updated:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testsetup::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     song_data = [
 | 
					
						
							|  |  |  |         ('Country Roads', 'John Denver', 1972),
 | 
					
						
							|  |  |  |         ('Me and Bobby McGee', 'Janice Joplin', 1971),
 | 
					
						
							|  |  |  |         ('Coal Miners Daughter', 'Loretta Lynn', 1970),
 | 
					
						
							|  |  |  |     ]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     movie_data = [
 | 
					
						
							|  |  |  |         ('Star Wars', 'George Lucas', 1977),
 | 
					
						
							|  |  |  |         ('Jaws', 'Steven Spielberg', 1975),
 | 
					
						
							|  |  |  |         ('Aliens', 'James Cameron', 1986),
 | 
					
						
							|  |  |  |     ]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import sqlite3
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     conn = sqlite3.connect(':memory:')
 | 
					
						
							|  |  |  |     conn.execute('CREATE TABLE Music (title text, artist text, year integer);')
 | 
					
						
							|  |  |  |     conn.execute('CREATE INDEX MusicNdx ON Music (title);')
 | 
					
						
							|  |  |  |     conn.executemany('INSERT INTO Music VALUES (?, ?, ?);', song_data)
 | 
					
						
							|  |  |  |     conn.execute('CREATE TABLE Movies (title text, director text, year integer);')
 | 
					
						
							|  |  |  |     conn.execute('CREATE INDEX MovieNdx ON Music (title);')
 | 
					
						
							|  |  |  |     conn.executemany('INSERT INTO Movies VALUES (?, ?, ?);', movie_data)
 | 
					
						
							|  |  |  |     conn.commit()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |     >>> 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'
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-15 17:44:28 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | Pure Python Equivalents
 | 
					
						
							|  |  |  | ^^^^^^^^^^^^^^^^^^^^^^^
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | The descriptor protocol is simple and offers exciting possibilities.  Several
 | 
					
						
							| 
									
										
										
										
											2020-11-01 09:10:06 -08:00
										 |  |  | use cases are so common that they have been prepackaged into built-in tools.
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Properties, bound methods, static methods, class methods, and \_\_slots\_\_ are
 | 
					
						
							|  |  |  | all based on the descriptor protocol.
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Properties
 | 
					
						
							|  |  |  | ----------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Calling :func:`property` is a succinct way of building a data descriptor that
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | triggers a function call upon access to an attribute.  Its signature is::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  |     property(fget=None, fset=None, fdel=None, doc=None) -> property
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | The documentation shows a typical use to define a managed attribute ``x``:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class C:
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |         def getx(self): return self.__x
 | 
					
						
							|  |  |  |         def setx(self, value): self.__x = value
 | 
					
						
							|  |  |  |         def delx(self): del self.__x
 | 
					
						
							|  |  |  |         x = property(getx, setx, delx, "I'm the 'x' property.")
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 20:02:46 -07:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> C.x.__doc__
 | 
					
						
							|  |  |  |     "I'm the 'x' property."
 | 
					
						
							|  |  |  |     >>> c.x = 2.71828
 | 
					
						
							|  |  |  |     >>> c.x
 | 
					
						
							|  |  |  |     2.71828
 | 
					
						
							|  |  |  |     >>> del c.x
 | 
					
						
							|  |  |  |     >>> c.x
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |       ...
 | 
					
						
							|  |  |  |     AttributeError: 'C' object has no attribute '_C__x'
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | To see how :func:`property` is implemented in terms of the descriptor protocol,
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | here is a pure Python equivalent:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class Property:
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |         "Emulate PyProperty_Type() in Objects/descrobject.c"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, fget=None, fset=None, fdel=None, doc=None):
 | 
					
						
							|  |  |  |             self.fget = fget
 | 
					
						
							|  |  |  |             self.fset = fset
 | 
					
						
							|  |  |  |             self.fdel = fdel
 | 
					
						
							| 
									
										
										
										
											2013-03-10 09:41:18 -07:00
										 |  |  |             if doc is None and fget is not None:
 | 
					
						
							|  |  |  |                 doc = fget.__doc__
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             self.__doc__ = doc
 | 
					
						
							| 
									
										
										
										
											2020-12-30 11:51:24 +02:00
										 |  |  |             self._name = ''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set_name__(self, owner, name):
 | 
					
						
							|  |  |  |             self._name = name
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             if obj is None:
 | 
					
						
							|  |  |  |                 return self
 | 
					
						
							|  |  |  |             if self.fget is None:
 | 
					
						
							| 
									
										
										
										
											2022-02-16 10:07:34 +03:00
										 |  |  |                 raise AttributeError(f"property '{self._name}' has no getter")
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             return self.fget(obj)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             if self.fset is None:
 | 
					
						
							| 
									
										
										
										
											2022-02-16 10:07:34 +03:00
										 |  |  |                 raise AttributeError(f"property '{self._name}' has no setter")
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             self.fset(obj, value)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __delete__(self, obj):
 | 
					
						
							|  |  |  |             if self.fdel is None:
 | 
					
						
							| 
									
										
										
										
											2022-02-16 10:07:34 +03:00
										 |  |  |                 raise AttributeError(f"property '{self._name}' has no deleter")
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             self.fdel(obj)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-03-10 09:41:18 -07:00
										 |  |  |         def getter(self, fget):
 | 
					
						
							| 
									
										
										
										
											2020-12-30 11:51:24 +02:00
										 |  |  |             prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
 | 
					
						
							|  |  |  |             prop._name = self._name
 | 
					
						
							|  |  |  |             return prop
 | 
					
						
							| 
									
										
										
										
											2013-03-10 09:41:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def setter(self, fset):
 | 
					
						
							| 
									
										
										
										
											2020-12-30 11:51:24 +02:00
										 |  |  |             prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
 | 
					
						
							|  |  |  |             prop._name = self._name
 | 
					
						
							|  |  |  |             return prop
 | 
					
						
							| 
									
										
										
										
											2013-03-10 09:41:18 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def deleter(self, fdel):
 | 
					
						
							| 
									
										
										
										
											2020-12-30 11:51:24 +02:00
										 |  |  |             prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
 | 
					
						
							|  |  |  |             prop._name = self._name
 | 
					
						
							|  |  |  |             return prop
 | 
					
						
							| 
									
										
										
										
											2013-03-10 09:41:18 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Verify the Property() emulation
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class CC:
 | 
					
						
							|  |  |  |         def getx(self):
 | 
					
						
							|  |  |  |             return self.__x
 | 
					
						
							|  |  |  |         def setx(self, value):
 | 
					
						
							|  |  |  |             self.__x = value
 | 
					
						
							|  |  |  |         def delx(self):
 | 
					
						
							|  |  |  |             del self.__x
 | 
					
						
							|  |  |  |         x = Property(getx, setx, delx, "I'm the 'x' property.")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Now do it again but use the decorator style
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class CCC:
 | 
					
						
							|  |  |  |         @Property
 | 
					
						
							|  |  |  |         def x(self):
 | 
					
						
							|  |  |  |             return self.__x
 | 
					
						
							|  |  |  |         @x.setter
 | 
					
						
							|  |  |  |         def x(self, value):
 | 
					
						
							|  |  |  |             self.__x = value
 | 
					
						
							|  |  |  |         @x.deleter
 | 
					
						
							|  |  |  |         def x(self):
 | 
					
						
							|  |  |  |             del self.__x
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> cc = CC()
 | 
					
						
							|  |  |  |     >>> hasattr(cc, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  |     >>> cc.x = 33
 | 
					
						
							|  |  |  |     >>> cc.x
 | 
					
						
							|  |  |  |     33
 | 
					
						
							|  |  |  |     >>> del cc.x
 | 
					
						
							|  |  |  |     >>> hasattr(cc, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> ccc = CCC()
 | 
					
						
							|  |  |  |     >>> hasattr(ccc, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  |     >>> ccc.x = 333
 | 
					
						
							|  |  |  |     >>> ccc.x == 333
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> del ccc.x
 | 
					
						
							|  |  |  |     >>> hasattr(ccc, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | The :func:`property` builtin helps whenever a user interface has granted
 | 
					
						
							|  |  |  | attribute access and then subsequent changes require the intervention of a
 | 
					
						
							|  |  |  | method.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For instance, a spreadsheet class may grant access to a cell value through
 | 
					
						
							|  |  |  | ``Cell('b10').value``. Subsequent improvements to the program require the cell
 | 
					
						
							|  |  |  | to be recalculated on every access; however, the programmer does not want to
 | 
					
						
							|  |  |  | affect existing client code accessing the attribute directly.  The solution is
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | to wrap access to the value attribute in a property data descriptor:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class Cell:
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         ...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def value(self):
 | 
					
						
							| 
									
										
										
										
											2017-06-23 11:54:35 +08:00
										 |  |  |             "Recalculate the cell before returning value"
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             self.recalc()
 | 
					
						
							| 
									
										
										
										
											2017-06-23 11:54:35 +08:00
										 |  |  |             return self._value
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | Either the built-in :func:`property` or our :func:`Property` equivalent would
 | 
					
						
							|  |  |  | work in this example.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Functions and methods
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | ---------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Python's object oriented features are built upon a function based environment.
 | 
					
						
							|  |  |  | Using non-data descriptors, the two are merged seamlessly.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 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.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Methods can be created manually with :class:`types.MethodType` which is
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | roughly equivalent to:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |     class MethodType:
 | 
					
						
							| 
									
										
										
										
											2021-04-01 11:03:33 -06:00
										 |  |  |         "Emulate PyMethod_Type in Objects/classobject.c"
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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)
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | To support automatic creation of methods, functions include the
 | 
					
						
							|  |  |  | :meth:`__get__` method for binding methods during attribute access.  This
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  | means that functions are non-data descriptors that return bound methods
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | during dotted lookup from an instance.  Here's how it works:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class Function:
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         ...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             "Simulate func_descr_get() in Objects/funcobject.c"
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  |             if obj is None:
 | 
					
						
							|  |  |  |                 return self
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |             return MethodType(self, obj)
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | Running the following class in the interpreter shows how the function
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | descriptor works in practice:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     class D:
 | 
					
						
							|  |  |  |         def f(self, x):
 | 
					
						
							|  |  |  |              return x
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | The function has a :term:`qualified name` attribute to support introspection:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> 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::
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> D.__dict__['f']
 | 
					
						
							|  |  |  |     <function D.f at 0x00C45070>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | Dotted access from a class calls :meth:`__get__` which just returns the
 | 
					
						
							|  |  |  | underlying function unchanged::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  |     >>> D.f
 | 
					
						
							|  |  |  |     <function D.f at 0x00C45070>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | The interesting behavior occurs during dotted access from an instance.  The
 | 
					
						
							|  |  |  | dotted lookup calls :meth:`__get__` which returns a bound method object::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> d = D()
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  |     >>> d.f
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |     <bound method D.f of <__main__.D object at 0x00B18C90>>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | Internally, the bound method stores the underlying function and the bound
 | 
					
						
							|  |  |  | instance::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  |     >>> d.f.__func__
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     <function D.f at 0x00C45070>
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-25 01:05:49 -07:00
										 |  |  |     >>> d.f.__self__
 | 
					
						
							|  |  |  |     <__main__.D object at 0x1012e1f98>
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | If you have ever wondered where *self* comes from in regular methods or where
 | 
					
						
							|  |  |  | *cls* comes from in class methods, this is it!
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | Kinds of methods
 | 
					
						
							|  |  |  | ----------------
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Non-data descriptors provide a simple mechanism for variations on the usual
 | 
					
						
							|  |  |  | patterns of binding functions into methods.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | To recap, functions have a :meth:`__get__` method so that they can be converted
 | 
					
						
							| 
									
										
										
										
											2015-11-02 14:10:23 +02:00
										 |  |  | to a method when accessed as attributes.  The non-data descriptor transforms an
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | ``obj.f(*args)`` call into ``f(obj, *args)``.  Calling ``cls.f(*args)``
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | becomes ``f(*args)``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This chart summarizes the binding and its two most useful variants:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       +-----------------+----------------------+------------------+
 | 
					
						
							|  |  |  |       | Transformation  | Called from an       | Called from a    |
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |       |                 | object               | class            |
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |       +=================+======================+==================+
 | 
					
						
							|  |  |  |       | function        | f(obj, \*args)       | f(\*args)        |
 | 
					
						
							|  |  |  |       +-----------------+----------------------+------------------+
 | 
					
						
							|  |  |  |       | staticmethod    | f(\*args)            | f(\*args)        |
 | 
					
						
							|  |  |  |       +-----------------+----------------------+------------------+
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |       | classmethod     | f(type(obj), \*args) | f(cls, \*args)   |
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |       +-----------------+----------------------+------------------+
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-13 13:46:32 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Static methods
 | 
					
						
							|  |  |  | --------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | Static methods return the underlying function without changes.  Calling either
 | 
					
						
							|  |  |  | ``c.f`` or ``C.f`` is the equivalent of a direct lookup into
 | 
					
						
							|  |  |  | ``object.__getattribute__(c, "f")`` or ``object.__getattribute__(C, "f")``. As a
 | 
					
						
							|  |  |  | result, the function becomes identically accessible from either an object or a
 | 
					
						
							|  |  |  | class.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Good candidates for static methods are methods that do not reference the
 | 
					
						
							|  |  |  | ``self`` variable.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For instance, a statistics package may include a container class for
 | 
					
						
							|  |  |  | experimental data.  The class provides normal methods for computing the average,
 | 
					
						
							|  |  |  | mean, median, and other descriptive statistics that depend on the data. However,
 | 
					
						
							|  |  |  | there may be useful functions which are conceptually related but do not depend
 | 
					
						
							|  |  |  | on the data.  For instance, ``erf(x)`` is handy conversion routine that comes up
 | 
					
						
							|  |  |  | 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``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 20:34:39 -07:00
										 |  |  | Since static methods return the underlying function with no changes, the
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | example calls are unexciting:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     class E:
 | 
					
						
							|  |  |  |         @staticmethod
 | 
					
						
							|  |  |  |         def f(x):
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  |             return x * 10
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 08:25:55 +05:30
										 |  |  |     >>> E.f(3)
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  |     30
 | 
					
						
							| 
									
										
										
										
											2019-03-20 08:25:55 +05:30
										 |  |  |     >>> E().f(3)
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  |     30
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Using the non-data descriptor protocol, a pure Python version of
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | :func:`staticmethod` would look like this:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class StaticMethod:
 | 
					
						
							| 
									
										
										
										
											2016-05-10 12:01:23 +03:00
										 |  |  |         "Emulate PyStaticMethod_Type() in Objects/funcobject.c"
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-10 12:01:23 +03:00
										 |  |  |         def __init__(self, f):
 | 
					
						
							|  |  |  |             self.f = f
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-10 12:01:23 +03:00
										 |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             return self.f
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-19 12:43:49 -06:00
										 |  |  |         def __call__(self, *args, **kwds):
 | 
					
						
							|  |  |  |             return self.f(*args, **kwds)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class E_sim:
 | 
					
						
							|  |  |  |         @StaticMethod
 | 
					
						
							|  |  |  |         def f(x):
 | 
					
						
							|  |  |  |             return x * 10
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-19 12:43:49 -06:00
										 |  |  |     wrapped_ord = StaticMethod(ord)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> E_sim.f(3)
 | 
					
						
							|  |  |  |     30
 | 
					
						
							|  |  |  |     >>> E_sim().f(3)
 | 
					
						
							|  |  |  |     30
 | 
					
						
							| 
									
										
										
										
											2021-11-19 12:43:49 -06:00
										 |  |  |     >>> wrapped_ord('A')
 | 
					
						
							|  |  |  |     65
 | 
					
						
							| 
									
										
										
										
											2021-04-03 13:07:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Class methods
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | -------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | Unlike static methods, class methods prepend the class reference to the
 | 
					
						
							|  |  |  | argument list before calling the function.  This format is the same
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | for whether the caller is an object or a class:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     class F:
 | 
					
						
							|  |  |  |         @classmethod
 | 
					
						
							|  |  |  |         def f(cls, x):
 | 
					
						
							|  |  |  |             return cls.__name__, x
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> F.f(3)
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     ('F', 3)
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     >>> F().f(3)
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |     ('F', 3)
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | This behavior is useful whenever the method only needs to have a class
 | 
					
						
							| 
									
										
										
										
											2020-11-25 14:12:17 +00:00
										 |  |  | reference and does not rely on data stored in a specific instance.  One use for
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  | class methods is to create alternate class constructors.  For example, the
 | 
					
						
							|  |  |  | classmethod :func:`dict.fromkeys` creates a new dictionary from a list of
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | keys.  The pure Python equivalent is:
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     class Dict(dict):
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         @classmethod
 | 
					
						
							|  |  |  |         def fromkeys(cls, iterable, value=None):
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             "Emulate dict_fromkeys() in Objects/dictobject.c"
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |             d = cls()
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  |             for key in iterable:
 | 
					
						
							|  |  |  |                 d[key] = value
 | 
					
						
							|  |  |  |             return d
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | Now a new dictionary of unique keys can be constructed like this:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-25 01:54:24 -08:00
										 |  |  |     >>> d = Dict.fromkeys('abracadabra')
 | 
					
						
							|  |  |  |     >>> type(d) is Dict
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> d
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     {'a': None, 'b': None, 'r': None, 'c': None, 'd': None}
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | Using the non-data descriptor protocol, a pure Python version of
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | :func:`classmethod` would look like this:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-10 13:35:52 +03:00
										 |  |  |     class ClassMethod:
 | 
					
						
							| 
									
										
										
										
											2016-05-10 12:01:23 +03:00
										 |  |  |         "Emulate PyClassMethod_Type() in Objects/funcobject.c"
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-10 12:01:23 +03:00
										 |  |  |         def __init__(self, f):
 | 
					
						
							|  |  |  |             self.f = f
 | 
					
						
							| 
									
										
										
										
											2010-05-19 21:39:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 12:55:39 -07:00
										 |  |  |         def __get__(self, obj, cls=None):
 | 
					
						
							|  |  |  |             if cls is None:
 | 
					
						
							|  |  |  |                 cls = type(obj)
 | 
					
						
							| 
									
										
										
										
											2021-04-22 17:53:36 -07:00
										 |  |  |             if hasattr(type(self.f), '__get__'):
 | 
					
						
							| 
									
										
										
										
											2022-05-06 02:57:53 -05:00
										 |  |  |                 # This code path was added in Python 3.9
 | 
					
						
							|  |  |  |                 # and was deprecated in Python 3.11.
 | 
					
						
							| 
									
										
										
										
											2021-11-19 12:43:49 -06:00
										 |  |  |                 return self.f.__get__(cls, cls)
 | 
					
						
							| 
									
										
										
										
											2020-10-25 07:12:50 -07:00
										 |  |  |             return MethodType(self.f, cls)
 | 
					
						
							| 
									
										
										
										
											2020-10-23 18:37:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. testcode::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Verify the emulation works
 | 
					
						
							|  |  |  |     class T:
 | 
					
						
							|  |  |  |         @ClassMethod
 | 
					
						
							|  |  |  |         def cm(cls, x, y):
 | 
					
						
							|  |  |  |             return (cls, x, y)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 17:53:36 -07:00
										 |  |  |         @ClassMethod
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def __doc__(cls):
 | 
					
						
							|  |  |  |             return f'A doc for {cls.__name__!r}'
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> T.cm(11, 22)
 | 
					
						
							|  |  |  |     (<class 'T'>, 11, 22)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Also call it from an instance
 | 
					
						
							|  |  |  |     >>> t = T()
 | 
					
						
							|  |  |  |     >>> t.cm(11, 22)
 | 
					
						
							|  |  |  |     (<class 'T'>, 11, 22)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 17:53:36 -07:00
										 |  |  |     # Check the alternate path for chained descriptors
 | 
					
						
							|  |  |  |     >>> T.__doc__
 | 
					
						
							|  |  |  |     "A doc for 'T'"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 20:02:46 -07:00
										 |  |  | The code path for ``hasattr(type(self.f), '__get__')`` was added in
 | 
					
						
							|  |  |  | Python 3.9 and makes it possible for :func:`classmethod` to support
 | 
					
						
							|  |  |  | chained decorators.  For example, a classmethod and property could be
 | 
					
						
							| 
									
										
										
										
											2022-05-06 02:57:53 -05:00
										 |  |  | chained together.  In Python 3.11, this functionality was deprecated.
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-10-23 18:37:27 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class G:
 | 
					
						
							|  |  |  |         @classmethod
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def __doc__(cls):
 | 
					
						
							|  |  |  |             return f'A doc for {cls.__name__!r}'
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> G.__doc__
 | 
					
						
							|  |  |  |     "A doc for 'G'"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 20:15:50 -08:00
										 |  |  | Member objects and __slots__
 | 
					
						
							|  |  |  | ----------------------------
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | When a class defines ``__slots__``, it replaces instance dictionaries with a
 | 
					
						
							|  |  |  | fixed-length array of slot values.  From a user point of view that has
 | 
					
						
							|  |  |  | several effects:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1. Provides immediate detection of bugs due to misspelled attribute
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | assignments.  Only attribute names specified in ``__slots__`` are allowed:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         class Vehicle:
 | 
					
						
							|  |  |  |             __slots__ = ('id_number', 'make', 'model')
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |         >>> auto = Vehicle()
 | 
					
						
							|  |  |  |         >>> auto.id_nubmer = 'VYE483814LQEX'
 | 
					
						
							|  |  |  |         Traceback (most recent call last):
 | 
					
						
							|  |  |  |             ...
 | 
					
						
							|  |  |  |         AttributeError: 'Vehicle' object has no attribute 'id_nubmer'
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2. Helps create immutable objects where descriptors manage access to private
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | attributes stored in ``__slots__``:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class Immutable:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 01:30:17 -08:00
										 |  |  |         __slots__ = ('_dept', '_name')          # Replace the instance dictionary
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, dept, name):
 | 
					
						
							|  |  |  |             self._dept = dept                   # Store to private attribute
 | 
					
						
							|  |  |  |             self._name = name                   # Store to private attribute
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property                               # Read-only descriptor
 | 
					
						
							|  |  |  |         def dept(self):
 | 
					
						
							|  |  |  |             return self._dept
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @property
 | 
					
						
							|  |  |  |         def name(self):                         # Read-only descriptor
 | 
					
						
							|  |  |  |             return self._name
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     >>> mark = Immutable('Botany', 'Mark Watney')
 | 
					
						
							|  |  |  |     >>> mark.dept
 | 
					
						
							|  |  |  |     'Botany'
 | 
					
						
							|  |  |  |     >>> mark.dept = 'Space Pirate'
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							| 
									
										
										
										
											2022-02-16 10:07:34 +03:00
										 |  |  |     AttributeError: property 'dept' of 'Immutable' object has no setter
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     >>> mark.location = 'Mars'
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     AttributeError: 'Immutable' object has no attribute 'location'
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 3. Saves memory.  On a 64-bit Linux build, an instance with two attributes
 | 
					
						
							|  |  |  | takes 48 bytes with ``__slots__`` and 152 bytes without.  This `flyweight
 | 
					
						
							|  |  |  | design pattern <https://en.wikipedia.org/wiki/Flyweight_pattern>`_ likely only
 | 
					
						
							|  |  |  | matters when a large number of instances are going to be created.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 22:05:42 -08:00
										 |  |  | 4. Improves speed.  Reading instance variables is 35% faster with
 | 
					
						
							|  |  |  | ``__slots__`` (as measured with Python 3.10 on an Apple M1 processor).
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 5. Blocks tools like :func:`functools.cached_property` which require an
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | instance dictionary to function correctly:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     from functools import cached_property
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class CP:
 | 
					
						
							|  |  |  |         __slots__ = ()                          # Eliminates the instance dict
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @cached_property                        # Requires an instance dict
 | 
					
						
							|  |  |  |         def pi(self):
 | 
					
						
							|  |  |  |             return 4 * sum((-1.0)**n / (2.0*n + 1.0)
 | 
					
						
							|  |  |  |                            for n in reversed(range(100_000)))
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |     >>> CP().pi
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |       ...
 | 
					
						
							|  |  |  |     TypeError: No '__dict__' attribute on 'CP' instance to cache 'pi' property.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | It is not possible to create an exact drop-in pure Python version of
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | ``__slots__`` because it requires direct access to C structures and control
 | 
					
						
							|  |  |  | over object memory allocation.  However, we can build a mostly faithful
 | 
					
						
							|  |  |  | simulation where the actual C structure for slots is emulated by a private
 | 
					
						
							|  |  |  | ``_slotvalues`` list.  Reads and writes to that private structure are managed
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | by member descriptors:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |     null = object()
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |     class Member:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, name, clsname, offset):
 | 
					
						
							|  |  |  |             'Emulate PyMemberDef in Include/structmember.h'
 | 
					
						
							|  |  |  |             # Also see descr_new() in Objects/descrobject.c
 | 
					
						
							|  |  |  |             self.name = name
 | 
					
						
							|  |  |  |             self.clsname = clsname
 | 
					
						
							|  |  |  |             self.offset = offset
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __get__(self, obj, objtype=None):
 | 
					
						
							|  |  |  |             'Emulate member_get() in Objects/descrobject.c'
 | 
					
						
							|  |  |  |             # Also see PyMember_GetOne() in Python/structmember.c
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |             if obj is None:
 | 
					
						
							|  |  |  |                 return self
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |             value = obj._slotvalues[self.offset]
 | 
					
						
							|  |  |  |             if value is null:
 | 
					
						
							|  |  |  |                 raise AttributeError(self.name)
 | 
					
						
							|  |  |  |             return value
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __set__(self, obj, value):
 | 
					
						
							|  |  |  |             'Emulate member_set() in Objects/descrobject.c'
 | 
					
						
							|  |  |  |             obj._slotvalues[self.offset] = value
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |         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
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |         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
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | variables:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class Type(type):
 | 
					
						
							|  |  |  |         'Simulate how the type metaclass adds member objects for slots'
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |         def __new__(mcls, clsname, bases, mapping, **kwargs):
 | 
					
						
							| 
									
										
										
										
											2022-01-20 09:17:15 +01:00
										 |  |  |             'Emulate type_new() in Objects/typeobject.c'
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |             # type_new() calls PyTypeReady() which calls add_methods()
 | 
					
						
							|  |  |  |             slot_names = mapping.get('slot_names', [])
 | 
					
						
							|  |  |  |             for offset, name in enumerate(slot_names):
 | 
					
						
							|  |  |  |                 mapping[name] = Member(name, clsname, offset)
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |             return type.__new__(mcls, clsname, bases, mapping, **kwargs)
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  | 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
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | Python:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |     class Object:
 | 
					
						
							|  |  |  |         'Simulate how object.__new__() allocates memory for __slots__'
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |         def __new__(cls, *args, **kwargs):
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |             'Emulate object_new() in Objects/typeobject.c'
 | 
					
						
							|  |  |  |             inst = super().__new__(cls)
 | 
					
						
							|  |  |  |             if hasattr(cls, 'slot_names'):
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |                 empty_slots = [null] * len(cls.slot_names)
 | 
					
						
							|  |  |  |                 object.__setattr__(inst, '_slotvalues', empty_slots)
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |             return inst
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |         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(
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |                     f'{cls.__name__!r} object has no attribute {name!r}'
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |                 )
 | 
					
						
							|  |  |  |             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(
 | 
					
						
							| 
									
										
										
										
											2022-10-09 03:54:21 +02:00
										 |  |  |                     f'{cls.__name__!r} object has no attribute {name!r}'
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |                 )
 | 
					
						
							|  |  |  |             super().__delattr__(name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | To use the simulation in a real class, just inherit from :class:`Object` and
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | set the :term:`metaclass` to :class:`Type`:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. testcode::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class H(Object, metaclass=Type):
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |         'Instance variables stored in slots'
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         slot_names = ['x', 'y']
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, x, y):
 | 
					
						
							|  |  |  |             self.x = x
 | 
					
						
							|  |  |  |             self.y = y
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | At this point, the metaclass has loaded member objects for *x* and *y*::
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  |     >>> from pprint import pp
 | 
					
						
							|  |  |  |     >>> pp(dict(vars(H)))
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |     {'__module__': '__main__',
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |      '__doc__': 'Instance variables stored in slots',
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  |      'slot_names': ['x', 'y'],
 | 
					
						
							|  |  |  |      '__init__': <function H.__init__ at 0x7fb5d302f9d0>,
 | 
					
						
							|  |  |  |      'x': <Member 'x' of 'H'>,
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |      'y': <Member 'y' of 'H'>}
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | .. doctest::
 | 
					
						
							|  |  |  |     :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # We test this separately because the preceding section is not
 | 
					
						
							|  |  |  |     # doctestable due to the hex memory address for the __init__ function
 | 
					
						
							|  |  |  |     >>> isinstance(vars(H)['x'], Member)
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  |     >>> isinstance(vars(H)['y'], Member)
 | 
					
						
							|  |  |  |     True
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | When instances are created, they have a ``slot_values`` list where the
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | attributes are stored:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     >>> h = H(10, 20)
 | 
					
						
							|  |  |  |     >>> vars(h)
 | 
					
						
							|  |  |  |     {'_slotvalues': [10, 20]}
 | 
					
						
							|  |  |  |     >>> h.x = 55
 | 
					
						
							|  |  |  |     >>> vars(h)
 | 
					
						
							|  |  |  |     {'_slotvalues': [55, 20]}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | Misspelled or unassigned attributes will raise an exception:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							| 
									
										
										
										
											2020-11-01 18:02:37 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 10:56:59 -08:00
										 |  |  |     >>> h.xz
 | 
					
						
							|  |  |  |     Traceback (most recent call last):
 | 
					
						
							|  |  |  |         ...
 | 
					
						
							|  |  |  |     AttributeError: 'H' object has no attribute 'xz'
 | 
					
						
							| 
									
										
										
										
											2020-11-24 20:57:02 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | .. doctest::
 | 
					
						
							|  |  |  |    :hide: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Examples for deleted attributes are not shown because this section
 | 
					
						
							|  |  |  |     # is already a bit lengthy.  We still test that code here.
 | 
					
						
							|  |  |  |     >>> del h.x
 | 
					
						
							|  |  |  |     >>> hasattr(h, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Also test the code for uninitialized slots
 | 
					
						
							|  |  |  |     >>> class HU(Object, metaclass=Type):
 | 
					
						
							|  |  |  |     ...     slot_names = ['x', 'y']
 | 
					
						
							|  |  |  |     ...
 | 
					
						
							|  |  |  |     >>> hu = HU()
 | 
					
						
							|  |  |  |     >>> hasattr(hu, 'x')
 | 
					
						
							|  |  |  |     False
 | 
					
						
							|  |  |  |     >>> hasattr(hu, 'y')
 | 
					
						
							|  |  |  |     False
 |