17db96d56Sopenharmony_ci.. _descriptorhowto: 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ci====================== 47db96d56Sopenharmony_ciDescriptor HowTo Guide 57db96d56Sopenharmony_ci====================== 67db96d56Sopenharmony_ci 77db96d56Sopenharmony_ci:Author: Raymond Hettinger 87db96d56Sopenharmony_ci:Contact: <python at rcn dot com> 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ci.. Contents:: 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci 137db96d56Sopenharmony_ci:term:`Descriptors <descriptor>` let objects customize attribute lookup, 147db96d56Sopenharmony_cistorage, and deletion. 157db96d56Sopenharmony_ci 167db96d56Sopenharmony_ciThis guide has four major sections: 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci1) The "primer" gives a basic overview, moving gently from simple examples, 197db96d56Sopenharmony_ci adding one feature at a time. Start here if you're new to descriptors. 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ci2) The second section shows a complete, practical descriptor example. If you 227db96d56Sopenharmony_ci already know the basics, start there. 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci3) The third section provides a more technical tutorial that goes into the 257db96d56Sopenharmony_ci detailed mechanics of how descriptors work. Most people don't need this 267db96d56Sopenharmony_ci level of detail. 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_ci4) The last section has pure Python equivalents for built-in descriptors that 297db96d56Sopenharmony_ci are written in C. Read this if you're curious about how functions turn 307db96d56Sopenharmony_ci into bound methods or about the implementation of common tools like 317db96d56Sopenharmony_ci :func:`classmethod`, :func:`staticmethod`, :func:`property`, and 327db96d56Sopenharmony_ci :term:`__slots__`. 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ciPrimer 367db96d56Sopenharmony_ci^^^^^^ 377db96d56Sopenharmony_ci 387db96d56Sopenharmony_ciIn this primer, we start with the most basic possible example and then we'll 397db96d56Sopenharmony_ciadd new capabilities one by one. 407db96d56Sopenharmony_ci 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ciSimple example: A descriptor that returns a constant 437db96d56Sopenharmony_ci---------------------------------------------------- 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ciThe :class:`Ten` class is a descriptor whose :meth:`__get__` method always 467db96d56Sopenharmony_cireturns the constant ``10``: 477db96d56Sopenharmony_ci 487db96d56Sopenharmony_ci.. testcode:: 497db96d56Sopenharmony_ci 507db96d56Sopenharmony_ci class Ten: 517db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 527db96d56Sopenharmony_ci return 10 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ciTo use the descriptor, it must be stored as a class variable in another class: 557db96d56Sopenharmony_ci 567db96d56Sopenharmony_ci.. testcode:: 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ci class A: 597db96d56Sopenharmony_ci x = 5 # Regular class attribute 607db96d56Sopenharmony_ci y = Ten() # Descriptor instance 617db96d56Sopenharmony_ci 627db96d56Sopenharmony_ciAn interactive session shows the difference between normal attribute lookup 637db96d56Sopenharmony_ciand descriptor lookup: 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci.. doctest:: 667db96d56Sopenharmony_ci 677db96d56Sopenharmony_ci >>> a = A() # Make an instance of class A 687db96d56Sopenharmony_ci >>> a.x # Normal attribute lookup 697db96d56Sopenharmony_ci 5 707db96d56Sopenharmony_ci >>> a.y # Descriptor lookup 717db96d56Sopenharmony_ci 10 727db96d56Sopenharmony_ci 737db96d56Sopenharmony_ciIn the ``a.x`` attribute lookup, the dot operator finds ``'x': 5`` 747db96d56Sopenharmony_ciin the class dictionary. In the ``a.y`` lookup, the dot operator 757db96d56Sopenharmony_cifinds a descriptor instance, recognized by its ``__get__`` method. 767db96d56Sopenharmony_ciCalling that method returns ``10``. 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ciNote that the value ``10`` is not stored in either the class dictionary or the 797db96d56Sopenharmony_ciinstance dictionary. Instead, the value ``10`` is computed on demand. 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ciThis example shows how a simple descriptor works, but it isn't very useful. 827db96d56Sopenharmony_ciFor retrieving constants, normal attribute lookup would be better. 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ciIn the next section, we'll create something more useful, a dynamic lookup. 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ci 877db96d56Sopenharmony_ciDynamic lookups 887db96d56Sopenharmony_ci--------------- 897db96d56Sopenharmony_ci 907db96d56Sopenharmony_ciInteresting descriptors typically run computations instead of returning 917db96d56Sopenharmony_ciconstants: 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci.. testcode:: 947db96d56Sopenharmony_ci 957db96d56Sopenharmony_ci import os 967db96d56Sopenharmony_ci 977db96d56Sopenharmony_ci class DirectorySize: 987db96d56Sopenharmony_ci 997db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 1007db96d56Sopenharmony_ci return len(os.listdir(obj.dirname)) 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci class Directory: 1037db96d56Sopenharmony_ci 1047db96d56Sopenharmony_ci size = DirectorySize() # Descriptor instance 1057db96d56Sopenharmony_ci 1067db96d56Sopenharmony_ci def __init__(self, dirname): 1077db96d56Sopenharmony_ci self.dirname = dirname # Regular instance attribute 1087db96d56Sopenharmony_ci 1097db96d56Sopenharmony_ciAn interactive session shows that the lookup is dynamic — it computes 1107db96d56Sopenharmony_cidifferent, updated answers each time:: 1117db96d56Sopenharmony_ci 1127db96d56Sopenharmony_ci >>> s = Directory('songs') 1137db96d56Sopenharmony_ci >>> g = Directory('games') 1147db96d56Sopenharmony_ci >>> s.size # The songs directory has twenty files 1157db96d56Sopenharmony_ci 20 1167db96d56Sopenharmony_ci >>> g.size # The games directory has three files 1177db96d56Sopenharmony_ci 3 1187db96d56Sopenharmony_ci >>> os.remove('games/chess') # Delete a game 1197db96d56Sopenharmony_ci >>> g.size # File count is automatically updated 1207db96d56Sopenharmony_ci 2 1217db96d56Sopenharmony_ci 1227db96d56Sopenharmony_ciBesides showing how descriptors can run computations, this example also 1237db96d56Sopenharmony_cireveals the purpose of the parameters to :meth:`__get__`. The *self* 1247db96d56Sopenharmony_ciparameter is *size*, an instance of *DirectorySize*. The *obj* parameter is 1257db96d56Sopenharmony_cieither *g* or *s*, an instance of *Directory*. It is the *obj* parameter that 1267db96d56Sopenharmony_cilets the :meth:`__get__` method learn the target directory. The *objtype* 1277db96d56Sopenharmony_ciparameter is the class *Directory*. 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci 1307db96d56Sopenharmony_ciManaged attributes 1317db96d56Sopenharmony_ci------------------ 1327db96d56Sopenharmony_ci 1337db96d56Sopenharmony_ciA popular use for descriptors is managing access to instance data. The 1347db96d56Sopenharmony_cidescriptor is assigned to a public attribute in the class dictionary while the 1357db96d56Sopenharmony_ciactual data is stored as a private attribute in the instance dictionary. The 1367db96d56Sopenharmony_cidescriptor's :meth:`__get__` and :meth:`__set__` methods are triggered when 1377db96d56Sopenharmony_cithe public attribute is accessed. 1387db96d56Sopenharmony_ci 1397db96d56Sopenharmony_ciIn the following example, *age* is the public attribute and *_age* is the 1407db96d56Sopenharmony_ciprivate attribute. When the public attribute is accessed, the descriptor logs 1417db96d56Sopenharmony_cithe lookup or update: 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci.. testcode:: 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ci import logging 1467db96d56Sopenharmony_ci 1477db96d56Sopenharmony_ci logging.basicConfig(level=logging.INFO) 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci class LoggedAgeAccess: 1507db96d56Sopenharmony_ci 1517db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 1527db96d56Sopenharmony_ci value = obj._age 1537db96d56Sopenharmony_ci logging.info('Accessing %r giving %r', 'age', value) 1547db96d56Sopenharmony_ci return value 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_ci def __set__(self, obj, value): 1577db96d56Sopenharmony_ci logging.info('Updating %r to %r', 'age', value) 1587db96d56Sopenharmony_ci obj._age = value 1597db96d56Sopenharmony_ci 1607db96d56Sopenharmony_ci class Person: 1617db96d56Sopenharmony_ci 1627db96d56Sopenharmony_ci age = LoggedAgeAccess() # Descriptor instance 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_ci def __init__(self, name, age): 1657db96d56Sopenharmony_ci self.name = name # Regular instance attribute 1667db96d56Sopenharmony_ci self.age = age # Calls __set__() 1677db96d56Sopenharmony_ci 1687db96d56Sopenharmony_ci def birthday(self): 1697db96d56Sopenharmony_ci self.age += 1 # Calls both __get__() and __set__() 1707db96d56Sopenharmony_ci 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ciAn interactive session shows that all access to the managed attribute *age* is 1737db96d56Sopenharmony_cilogged, but that the regular attribute *name* is not logged: 1747db96d56Sopenharmony_ci 1757db96d56Sopenharmony_ci.. testcode:: 1767db96d56Sopenharmony_ci :hide: 1777db96d56Sopenharmony_ci 1787db96d56Sopenharmony_ci import logging, sys 1797db96d56Sopenharmony_ci logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True) 1807db96d56Sopenharmony_ci 1817db96d56Sopenharmony_ci.. doctest:: 1827db96d56Sopenharmony_ci 1837db96d56Sopenharmony_ci >>> mary = Person('Mary M', 30) # The initial age update is logged 1847db96d56Sopenharmony_ci INFO:root:Updating 'age' to 30 1857db96d56Sopenharmony_ci >>> dave = Person('David D', 40) 1867db96d56Sopenharmony_ci INFO:root:Updating 'age' to 40 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ci >>> vars(mary) # The actual data is in a private attribute 1897db96d56Sopenharmony_ci {'name': 'Mary M', '_age': 30} 1907db96d56Sopenharmony_ci >>> vars(dave) 1917db96d56Sopenharmony_ci {'name': 'David D', '_age': 40} 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ci >>> mary.age # Access the data and log the lookup 1947db96d56Sopenharmony_ci INFO:root:Accessing 'age' giving 30 1957db96d56Sopenharmony_ci 30 1967db96d56Sopenharmony_ci >>> mary.birthday() # Updates are logged as well 1977db96d56Sopenharmony_ci INFO:root:Accessing 'age' giving 30 1987db96d56Sopenharmony_ci INFO:root:Updating 'age' to 31 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci >>> dave.name # Regular attribute lookup isn't logged 2017db96d56Sopenharmony_ci 'David D' 2027db96d56Sopenharmony_ci >>> dave.age # Only the managed attribute is logged 2037db96d56Sopenharmony_ci INFO:root:Accessing 'age' giving 40 2047db96d56Sopenharmony_ci 40 2057db96d56Sopenharmony_ci 2067db96d56Sopenharmony_ciOne major issue with this example is that the private name *_age* is hardwired in 2077db96d56Sopenharmony_cithe *LoggedAgeAccess* class. That means that each instance can only have one 2087db96d56Sopenharmony_cilogged attribute and that its name is unchangeable. In the next example, 2097db96d56Sopenharmony_ciwe'll fix that problem. 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ci 2127db96d56Sopenharmony_ciCustomized names 2137db96d56Sopenharmony_ci---------------- 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ciWhen a class uses descriptors, it can inform each descriptor about which 2167db96d56Sopenharmony_civariable name was used. 2177db96d56Sopenharmony_ci 2187db96d56Sopenharmony_ciIn this example, the :class:`Person` class has two descriptor instances, 2197db96d56Sopenharmony_ci*name* and *age*. When the :class:`Person` class is defined, it makes a 2207db96d56Sopenharmony_cicallback to :meth:`__set_name__` in *LoggedAccess* so that the field names can 2217db96d56Sopenharmony_cibe recorded, giving each descriptor its own *public_name* and *private_name*: 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci.. testcode:: 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ci import logging 2267db96d56Sopenharmony_ci 2277db96d56Sopenharmony_ci logging.basicConfig(level=logging.INFO) 2287db96d56Sopenharmony_ci 2297db96d56Sopenharmony_ci class LoggedAccess: 2307db96d56Sopenharmony_ci 2317db96d56Sopenharmony_ci def __set_name__(self, owner, name): 2327db96d56Sopenharmony_ci self.public_name = name 2337db96d56Sopenharmony_ci self.private_name = '_' + name 2347db96d56Sopenharmony_ci 2357db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 2367db96d56Sopenharmony_ci value = getattr(obj, self.private_name) 2377db96d56Sopenharmony_ci logging.info('Accessing %r giving %r', self.public_name, value) 2387db96d56Sopenharmony_ci return value 2397db96d56Sopenharmony_ci 2407db96d56Sopenharmony_ci def __set__(self, obj, value): 2417db96d56Sopenharmony_ci logging.info('Updating %r to %r', self.public_name, value) 2427db96d56Sopenharmony_ci setattr(obj, self.private_name, value) 2437db96d56Sopenharmony_ci 2447db96d56Sopenharmony_ci class Person: 2457db96d56Sopenharmony_ci 2467db96d56Sopenharmony_ci name = LoggedAccess() # First descriptor instance 2477db96d56Sopenharmony_ci age = LoggedAccess() # Second descriptor instance 2487db96d56Sopenharmony_ci 2497db96d56Sopenharmony_ci def __init__(self, name, age): 2507db96d56Sopenharmony_ci self.name = name # Calls the first descriptor 2517db96d56Sopenharmony_ci self.age = age # Calls the second descriptor 2527db96d56Sopenharmony_ci 2537db96d56Sopenharmony_ci def birthday(self): 2547db96d56Sopenharmony_ci self.age += 1 2557db96d56Sopenharmony_ci 2567db96d56Sopenharmony_ciAn interactive session shows that the :class:`Person` class has called 2577db96d56Sopenharmony_ci:meth:`__set_name__` so that the field names would be recorded. Here 2587db96d56Sopenharmony_ciwe call :func:`vars` to look up the descriptor without triggering it: 2597db96d56Sopenharmony_ci 2607db96d56Sopenharmony_ci.. doctest:: 2617db96d56Sopenharmony_ci 2627db96d56Sopenharmony_ci >>> vars(vars(Person)['name']) 2637db96d56Sopenharmony_ci {'public_name': 'name', 'private_name': '_name'} 2647db96d56Sopenharmony_ci >>> vars(vars(Person)['age']) 2657db96d56Sopenharmony_ci {'public_name': 'age', 'private_name': '_age'} 2667db96d56Sopenharmony_ci 2677db96d56Sopenharmony_ciThe new class now logs access to both *name* and *age*: 2687db96d56Sopenharmony_ci 2697db96d56Sopenharmony_ci.. testcode:: 2707db96d56Sopenharmony_ci :hide: 2717db96d56Sopenharmony_ci 2727db96d56Sopenharmony_ci import logging, sys 2737db96d56Sopenharmony_ci logging.basicConfig(level=logging.INFO, stream=sys.stdout, force=True) 2747db96d56Sopenharmony_ci 2757db96d56Sopenharmony_ci.. doctest:: 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci >>> pete = Person('Peter P', 10) 2787db96d56Sopenharmony_ci INFO:root:Updating 'name' to 'Peter P' 2797db96d56Sopenharmony_ci INFO:root:Updating 'age' to 10 2807db96d56Sopenharmony_ci >>> kate = Person('Catherine C', 20) 2817db96d56Sopenharmony_ci INFO:root:Updating 'name' to 'Catherine C' 2827db96d56Sopenharmony_ci INFO:root:Updating 'age' to 20 2837db96d56Sopenharmony_ci 2847db96d56Sopenharmony_ciThe two *Person* instances contain only the private names: 2857db96d56Sopenharmony_ci 2867db96d56Sopenharmony_ci.. doctest:: 2877db96d56Sopenharmony_ci 2887db96d56Sopenharmony_ci >>> vars(pete) 2897db96d56Sopenharmony_ci {'_name': 'Peter P', '_age': 10} 2907db96d56Sopenharmony_ci >>> vars(kate) 2917db96d56Sopenharmony_ci {'_name': 'Catherine C', '_age': 20} 2927db96d56Sopenharmony_ci 2937db96d56Sopenharmony_ci 2947db96d56Sopenharmony_ciClosing thoughts 2957db96d56Sopenharmony_ci---------------- 2967db96d56Sopenharmony_ci 2977db96d56Sopenharmony_ciA :term:`descriptor` is what we call any object that defines :meth:`__get__`, 2987db96d56Sopenharmony_ci:meth:`__set__`, or :meth:`__delete__`. 2997db96d56Sopenharmony_ci 3007db96d56Sopenharmony_ciOptionally, descriptors can have a :meth:`__set_name__` method. This is only 3017db96d56Sopenharmony_ciused in cases where a descriptor needs to know either the class where it was 3027db96d56Sopenharmony_cicreated or the name of class variable it was assigned to. (This method, if 3037db96d56Sopenharmony_cipresent, is called even if the class is not a descriptor.) 3047db96d56Sopenharmony_ci 3057db96d56Sopenharmony_ciDescriptors get invoked by the dot operator during attribute lookup. If a 3067db96d56Sopenharmony_cidescriptor is accessed indirectly with ``vars(some_class)[descriptor_name]``, 3077db96d56Sopenharmony_cithe descriptor instance is returned without invoking it. 3087db96d56Sopenharmony_ci 3097db96d56Sopenharmony_ciDescriptors only work when used as class variables. When put in instances, 3107db96d56Sopenharmony_cithey have no effect. 3117db96d56Sopenharmony_ci 3127db96d56Sopenharmony_ciThe main motivation for descriptors is to provide a hook allowing objects 3137db96d56Sopenharmony_cistored in class variables to control what happens during attribute lookup. 3147db96d56Sopenharmony_ci 3157db96d56Sopenharmony_ciTraditionally, the calling class controls what happens during lookup. 3167db96d56Sopenharmony_ciDescriptors invert that relationship and allow the data being looked-up to 3177db96d56Sopenharmony_cihave a say in the matter. 3187db96d56Sopenharmony_ci 3197db96d56Sopenharmony_ciDescriptors are used throughout the language. It is how functions turn into 3207db96d56Sopenharmony_cibound methods. Common tools like :func:`classmethod`, :func:`staticmethod`, 3217db96d56Sopenharmony_ci:func:`property`, and :func:`functools.cached_property` are all implemented as 3227db96d56Sopenharmony_cidescriptors. 3237db96d56Sopenharmony_ci 3247db96d56Sopenharmony_ci 3257db96d56Sopenharmony_ciComplete Practical Example 3267db96d56Sopenharmony_ci^^^^^^^^^^^^^^^^^^^^^^^^^^ 3277db96d56Sopenharmony_ci 3287db96d56Sopenharmony_ciIn this example, we create a practical and powerful tool for locating 3297db96d56Sopenharmony_cinotoriously hard to find data corruption bugs. 3307db96d56Sopenharmony_ci 3317db96d56Sopenharmony_ci 3327db96d56Sopenharmony_ciValidator class 3337db96d56Sopenharmony_ci--------------- 3347db96d56Sopenharmony_ci 3357db96d56Sopenharmony_ciA validator is a descriptor for managed attribute access. Prior to storing 3367db96d56Sopenharmony_ciany data, it verifies that the new value meets various type and range 3377db96d56Sopenharmony_cirestrictions. If those restrictions aren't met, it raises an exception to 3387db96d56Sopenharmony_ciprevent data corruption at its source. 3397db96d56Sopenharmony_ci 3407db96d56Sopenharmony_ciThis :class:`Validator` class is both an :term:`abstract base class` and a 3417db96d56Sopenharmony_cimanaged attribute descriptor: 3427db96d56Sopenharmony_ci 3437db96d56Sopenharmony_ci.. testcode:: 3447db96d56Sopenharmony_ci 3457db96d56Sopenharmony_ci from abc import ABC, abstractmethod 3467db96d56Sopenharmony_ci 3477db96d56Sopenharmony_ci class Validator(ABC): 3487db96d56Sopenharmony_ci 3497db96d56Sopenharmony_ci def __set_name__(self, owner, name): 3507db96d56Sopenharmony_ci self.private_name = '_' + name 3517db96d56Sopenharmony_ci 3527db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 3537db96d56Sopenharmony_ci return getattr(obj, self.private_name) 3547db96d56Sopenharmony_ci 3557db96d56Sopenharmony_ci def __set__(self, obj, value): 3567db96d56Sopenharmony_ci self.validate(value) 3577db96d56Sopenharmony_ci setattr(obj, self.private_name, value) 3587db96d56Sopenharmony_ci 3597db96d56Sopenharmony_ci @abstractmethod 3607db96d56Sopenharmony_ci def validate(self, value): 3617db96d56Sopenharmony_ci pass 3627db96d56Sopenharmony_ci 3637db96d56Sopenharmony_ciCustom validators need to inherit from :class:`Validator` and must supply a 3647db96d56Sopenharmony_ci:meth:`validate` method to test various restrictions as needed. 3657db96d56Sopenharmony_ci 3667db96d56Sopenharmony_ci 3677db96d56Sopenharmony_ciCustom validators 3687db96d56Sopenharmony_ci----------------- 3697db96d56Sopenharmony_ci 3707db96d56Sopenharmony_ciHere are three practical data validation utilities: 3717db96d56Sopenharmony_ci 3727db96d56Sopenharmony_ci1) :class:`OneOf` verifies that a value is one of a restricted set of options. 3737db96d56Sopenharmony_ci 3747db96d56Sopenharmony_ci2) :class:`Number` verifies that a value is either an :class:`int` or 3757db96d56Sopenharmony_ci :class:`float`. Optionally, it verifies that a value is between a given 3767db96d56Sopenharmony_ci minimum or maximum. 3777db96d56Sopenharmony_ci 3787db96d56Sopenharmony_ci3) :class:`String` verifies that a value is a :class:`str`. Optionally, it 3797db96d56Sopenharmony_ci validates a given minimum or maximum length. It can validate a 3807db96d56Sopenharmony_ci user-defined `predicate 3817db96d56Sopenharmony_ci <https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)>`_ as well. 3827db96d56Sopenharmony_ci 3837db96d56Sopenharmony_ci.. testcode:: 3847db96d56Sopenharmony_ci 3857db96d56Sopenharmony_ci class OneOf(Validator): 3867db96d56Sopenharmony_ci 3877db96d56Sopenharmony_ci def __init__(self, *options): 3887db96d56Sopenharmony_ci self.options = set(options) 3897db96d56Sopenharmony_ci 3907db96d56Sopenharmony_ci def validate(self, value): 3917db96d56Sopenharmony_ci if value not in self.options: 3927db96d56Sopenharmony_ci raise ValueError(f'Expected {value!r} to be one of {self.options!r}') 3937db96d56Sopenharmony_ci 3947db96d56Sopenharmony_ci class Number(Validator): 3957db96d56Sopenharmony_ci 3967db96d56Sopenharmony_ci def __init__(self, minvalue=None, maxvalue=None): 3977db96d56Sopenharmony_ci self.minvalue = minvalue 3987db96d56Sopenharmony_ci self.maxvalue = maxvalue 3997db96d56Sopenharmony_ci 4007db96d56Sopenharmony_ci def validate(self, value): 4017db96d56Sopenharmony_ci if not isinstance(value, (int, float)): 4027db96d56Sopenharmony_ci raise TypeError(f'Expected {value!r} to be an int or float') 4037db96d56Sopenharmony_ci if self.minvalue is not None and value < self.minvalue: 4047db96d56Sopenharmony_ci raise ValueError( 4057db96d56Sopenharmony_ci f'Expected {value!r} to be at least {self.minvalue!r}' 4067db96d56Sopenharmony_ci ) 4077db96d56Sopenharmony_ci if self.maxvalue is not None and value > self.maxvalue: 4087db96d56Sopenharmony_ci raise ValueError( 4097db96d56Sopenharmony_ci f'Expected {value!r} to be no more than {self.maxvalue!r}' 4107db96d56Sopenharmony_ci ) 4117db96d56Sopenharmony_ci 4127db96d56Sopenharmony_ci class String(Validator): 4137db96d56Sopenharmony_ci 4147db96d56Sopenharmony_ci def __init__(self, minsize=None, maxsize=None, predicate=None): 4157db96d56Sopenharmony_ci self.minsize = minsize 4167db96d56Sopenharmony_ci self.maxsize = maxsize 4177db96d56Sopenharmony_ci self.predicate = predicate 4187db96d56Sopenharmony_ci 4197db96d56Sopenharmony_ci def validate(self, value): 4207db96d56Sopenharmony_ci if not isinstance(value, str): 4217db96d56Sopenharmony_ci raise TypeError(f'Expected {value!r} to be an str') 4227db96d56Sopenharmony_ci if self.minsize is not None and len(value) < self.minsize: 4237db96d56Sopenharmony_ci raise ValueError( 4247db96d56Sopenharmony_ci f'Expected {value!r} to be no smaller than {self.minsize!r}' 4257db96d56Sopenharmony_ci ) 4267db96d56Sopenharmony_ci if self.maxsize is not None and len(value) > self.maxsize: 4277db96d56Sopenharmony_ci raise ValueError( 4287db96d56Sopenharmony_ci f'Expected {value!r} to be no bigger than {self.maxsize!r}' 4297db96d56Sopenharmony_ci ) 4307db96d56Sopenharmony_ci if self.predicate is not None and not self.predicate(value): 4317db96d56Sopenharmony_ci raise ValueError( 4327db96d56Sopenharmony_ci f'Expected {self.predicate} to be true for {value!r}' 4337db96d56Sopenharmony_ci ) 4347db96d56Sopenharmony_ci 4357db96d56Sopenharmony_ci 4367db96d56Sopenharmony_ciPractical application 4377db96d56Sopenharmony_ci--------------------- 4387db96d56Sopenharmony_ci 4397db96d56Sopenharmony_ciHere's how the data validators can be used in a real class: 4407db96d56Sopenharmony_ci 4417db96d56Sopenharmony_ci.. testcode:: 4427db96d56Sopenharmony_ci 4437db96d56Sopenharmony_ci class Component: 4447db96d56Sopenharmony_ci 4457db96d56Sopenharmony_ci name = String(minsize=3, maxsize=10, predicate=str.isupper) 4467db96d56Sopenharmony_ci kind = OneOf('wood', 'metal', 'plastic') 4477db96d56Sopenharmony_ci quantity = Number(minvalue=0) 4487db96d56Sopenharmony_ci 4497db96d56Sopenharmony_ci def __init__(self, name, kind, quantity): 4507db96d56Sopenharmony_ci self.name = name 4517db96d56Sopenharmony_ci self.kind = kind 4527db96d56Sopenharmony_ci self.quantity = quantity 4537db96d56Sopenharmony_ci 4547db96d56Sopenharmony_ciThe descriptors prevent invalid instances from being created: 4557db96d56Sopenharmony_ci 4567db96d56Sopenharmony_ci.. doctest:: 4577db96d56Sopenharmony_ci 4587db96d56Sopenharmony_ci >>> Component('Widget', 'metal', 5) # Blocked: 'Widget' is not all uppercase 4597db96d56Sopenharmony_ci Traceback (most recent call last): 4607db96d56Sopenharmony_ci ... 4617db96d56Sopenharmony_ci ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' 4627db96d56Sopenharmony_ci 4637db96d56Sopenharmony_ci >>> Component('WIDGET', 'metle', 5) # Blocked: 'metle' is misspelled 4647db96d56Sopenharmony_ci Traceback (most recent call last): 4657db96d56Sopenharmony_ci ... 4667db96d56Sopenharmony_ci ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'} 4677db96d56Sopenharmony_ci 4687db96d56Sopenharmony_ci >>> Component('WIDGET', 'metal', -5) # Blocked: -5 is negative 4697db96d56Sopenharmony_ci Traceback (most recent call last): 4707db96d56Sopenharmony_ci ... 4717db96d56Sopenharmony_ci ValueError: Expected -5 to be at least 0 4727db96d56Sopenharmony_ci >>> Component('WIDGET', 'metal', 'V') # Blocked: 'V' isn't a number 4737db96d56Sopenharmony_ci Traceback (most recent call last): 4747db96d56Sopenharmony_ci ... 4757db96d56Sopenharmony_ci TypeError: Expected 'V' to be an int or float 4767db96d56Sopenharmony_ci 4777db96d56Sopenharmony_ci >>> c = Component('WIDGET', 'metal', 5) # Allowed: The inputs are valid 4787db96d56Sopenharmony_ci 4797db96d56Sopenharmony_ci 4807db96d56Sopenharmony_ciTechnical Tutorial 4817db96d56Sopenharmony_ci^^^^^^^^^^^^^^^^^^ 4827db96d56Sopenharmony_ci 4837db96d56Sopenharmony_ciWhat follows is a more technical tutorial for the mechanics and details of how 4847db96d56Sopenharmony_cidescriptors work. 4857db96d56Sopenharmony_ci 4867db96d56Sopenharmony_ci 4877db96d56Sopenharmony_ciAbstract 4887db96d56Sopenharmony_ci-------- 4897db96d56Sopenharmony_ci 4907db96d56Sopenharmony_ciDefines descriptors, summarizes the protocol, and shows how descriptors are 4917db96d56Sopenharmony_cicalled. Provides an example showing how object relational mappings work. 4927db96d56Sopenharmony_ci 4937db96d56Sopenharmony_ciLearning about descriptors not only provides access to a larger toolset, it 4947db96d56Sopenharmony_cicreates a deeper understanding of how Python works. 4957db96d56Sopenharmony_ci 4967db96d56Sopenharmony_ci 4977db96d56Sopenharmony_ciDefinition and introduction 4987db96d56Sopenharmony_ci--------------------------- 4997db96d56Sopenharmony_ci 5007db96d56Sopenharmony_ciIn general, a descriptor is an attribute value that has one of the methods in 5017db96d56Sopenharmony_cithe descriptor protocol. Those methods are :meth:`__get__`, :meth:`__set__`, 5027db96d56Sopenharmony_ciand :meth:`__delete__`. If any of those methods are defined for an 5037db96d56Sopenharmony_ciattribute, it is said to be a :term:`descriptor`. 5047db96d56Sopenharmony_ci 5057db96d56Sopenharmony_ciThe default behavior for attribute access is to get, set, or delete the 5067db96d56Sopenharmony_ciattribute from an object's dictionary. For instance, ``a.x`` has a lookup chain 5077db96d56Sopenharmony_cistarting with ``a.__dict__['x']``, then ``type(a).__dict__['x']``, and 5087db96d56Sopenharmony_cicontinuing through the method resolution order of ``type(a)``. If the 5097db96d56Sopenharmony_cilooked-up value is an object defining one of the descriptor methods, then Python 5107db96d56Sopenharmony_cimay override the default behavior and invoke the descriptor method instead. 5117db96d56Sopenharmony_ciWhere this occurs in the precedence chain depends on which descriptor methods 5127db96d56Sopenharmony_ciwere defined. 5137db96d56Sopenharmony_ci 5147db96d56Sopenharmony_ciDescriptors are a powerful, general purpose protocol. They are the mechanism 5157db96d56Sopenharmony_cibehind properties, methods, static methods, class methods, and 5167db96d56Sopenharmony_ci:func:`super()`. They are used throughout Python itself. Descriptors 5177db96d56Sopenharmony_cisimplify the underlying C code and offer a flexible set of new tools for 5187db96d56Sopenharmony_cieveryday Python programs. 5197db96d56Sopenharmony_ci 5207db96d56Sopenharmony_ci 5217db96d56Sopenharmony_ciDescriptor protocol 5227db96d56Sopenharmony_ci------------------- 5237db96d56Sopenharmony_ci 5247db96d56Sopenharmony_ci``descr.__get__(self, obj, type=None) -> value`` 5257db96d56Sopenharmony_ci 5267db96d56Sopenharmony_ci``descr.__set__(self, obj, value) -> None`` 5277db96d56Sopenharmony_ci 5287db96d56Sopenharmony_ci``descr.__delete__(self, obj) -> None`` 5297db96d56Sopenharmony_ci 5307db96d56Sopenharmony_ciThat is all there is to it. Define any of these methods and an object is 5317db96d56Sopenharmony_ciconsidered a descriptor and can override default behavior upon being looked up 5327db96d56Sopenharmony_cias an attribute. 5337db96d56Sopenharmony_ci 5347db96d56Sopenharmony_ciIf an object defines :meth:`__set__` or :meth:`__delete__`, it is considered 5357db96d56Sopenharmony_cia data descriptor. Descriptors that only define :meth:`__get__` are called 5367db96d56Sopenharmony_cinon-data descriptors (they are often used for methods but other uses are 5377db96d56Sopenharmony_cipossible). 5387db96d56Sopenharmony_ci 5397db96d56Sopenharmony_ciData and non-data descriptors differ in how overrides are calculated with 5407db96d56Sopenharmony_cirespect to entries in an instance's dictionary. If an instance's dictionary 5417db96d56Sopenharmony_cihas an entry with the same name as a data descriptor, the data descriptor 5427db96d56Sopenharmony_citakes precedence. If an instance's dictionary has an entry with the same 5437db96d56Sopenharmony_ciname as a non-data descriptor, the dictionary entry takes precedence. 5447db96d56Sopenharmony_ci 5457db96d56Sopenharmony_ciTo make a read-only data descriptor, define both :meth:`__get__` and 5467db96d56Sopenharmony_ci:meth:`__set__` with the :meth:`__set__` raising an :exc:`AttributeError` when 5477db96d56Sopenharmony_cicalled. Defining the :meth:`__set__` method with an exception raising 5487db96d56Sopenharmony_ciplaceholder is enough to make it a data descriptor. 5497db96d56Sopenharmony_ci 5507db96d56Sopenharmony_ci 5517db96d56Sopenharmony_ciOverview of descriptor invocation 5527db96d56Sopenharmony_ci--------------------------------- 5537db96d56Sopenharmony_ci 5547db96d56Sopenharmony_ciA descriptor can be called directly with ``desc.__get__(obj)`` or 5557db96d56Sopenharmony_ci``desc.__get__(None, cls)``. 5567db96d56Sopenharmony_ci 5577db96d56Sopenharmony_ciBut it is more common for a descriptor to be invoked automatically from 5587db96d56Sopenharmony_ciattribute access. 5597db96d56Sopenharmony_ci 5607db96d56Sopenharmony_ciThe expression ``obj.x`` looks up the attribute ``x`` in the chain of 5617db96d56Sopenharmony_cinamespaces for ``obj``. If the search finds a descriptor outside of the 5627db96d56Sopenharmony_ciinstance ``__dict__``, its :meth:`__get__` method is invoked according to the 5637db96d56Sopenharmony_ciprecedence rules listed below. 5647db96d56Sopenharmony_ci 5657db96d56Sopenharmony_ciThe details of invocation depend on whether ``obj`` is an object, class, or 5667db96d56Sopenharmony_ciinstance of super. 5677db96d56Sopenharmony_ci 5687db96d56Sopenharmony_ci 5697db96d56Sopenharmony_ciInvocation from an instance 5707db96d56Sopenharmony_ci--------------------------- 5717db96d56Sopenharmony_ci 5727db96d56Sopenharmony_ciInstance lookup scans through a chain of namespaces giving data descriptors 5737db96d56Sopenharmony_cithe highest priority, followed by instance variables, then non-data 5747db96d56Sopenharmony_cidescriptors, then class variables, and lastly :meth:`__getattr__` if it is 5757db96d56Sopenharmony_ciprovided. 5767db96d56Sopenharmony_ci 5777db96d56Sopenharmony_ciIf a descriptor is found for ``a.x``, then it is invoked with: 5787db96d56Sopenharmony_ci``desc.__get__(a, type(a))``. 5797db96d56Sopenharmony_ci 5807db96d56Sopenharmony_ciThe logic for a dotted lookup is in :meth:`object.__getattribute__`. Here is 5817db96d56Sopenharmony_cia pure Python equivalent: 5827db96d56Sopenharmony_ci 5837db96d56Sopenharmony_ci.. testcode:: 5847db96d56Sopenharmony_ci 5857db96d56Sopenharmony_ci def find_name_in_mro(cls, name, default): 5867db96d56Sopenharmony_ci "Emulate _PyType_Lookup() in Objects/typeobject.c" 5877db96d56Sopenharmony_ci for base in cls.__mro__: 5887db96d56Sopenharmony_ci if name in vars(base): 5897db96d56Sopenharmony_ci return vars(base)[name] 5907db96d56Sopenharmony_ci return default 5917db96d56Sopenharmony_ci 5927db96d56Sopenharmony_ci def object_getattribute(obj, name): 5937db96d56Sopenharmony_ci "Emulate PyObject_GenericGetAttr() in Objects/object.c" 5947db96d56Sopenharmony_ci null = object() 5957db96d56Sopenharmony_ci objtype = type(obj) 5967db96d56Sopenharmony_ci cls_var = find_name_in_mro(objtype, name, null) 5977db96d56Sopenharmony_ci descr_get = getattr(type(cls_var), '__get__', null) 5987db96d56Sopenharmony_ci if descr_get is not null: 5997db96d56Sopenharmony_ci if (hasattr(type(cls_var), '__set__') 6007db96d56Sopenharmony_ci or hasattr(type(cls_var), '__delete__')): 6017db96d56Sopenharmony_ci return descr_get(cls_var, obj, objtype) # data descriptor 6027db96d56Sopenharmony_ci if hasattr(obj, '__dict__') and name in vars(obj): 6037db96d56Sopenharmony_ci return vars(obj)[name] # instance variable 6047db96d56Sopenharmony_ci if descr_get is not null: 6057db96d56Sopenharmony_ci return descr_get(cls_var, obj, objtype) # non-data descriptor 6067db96d56Sopenharmony_ci if cls_var is not null: 6077db96d56Sopenharmony_ci return cls_var # class variable 6087db96d56Sopenharmony_ci raise AttributeError(name) 6097db96d56Sopenharmony_ci 6107db96d56Sopenharmony_ci 6117db96d56Sopenharmony_ci.. testcode:: 6127db96d56Sopenharmony_ci :hide: 6137db96d56Sopenharmony_ci 6147db96d56Sopenharmony_ci # Test the fidelity of object_getattribute() by comparing it with the 6157db96d56Sopenharmony_ci # normal object.__getattribute__(). The former will be accessed by 6167db96d56Sopenharmony_ci # square brackets and the latter by the dot operator. 6177db96d56Sopenharmony_ci 6187db96d56Sopenharmony_ci class Object: 6197db96d56Sopenharmony_ci 6207db96d56Sopenharmony_ci def __getitem__(obj, name): 6217db96d56Sopenharmony_ci try: 6227db96d56Sopenharmony_ci return object_getattribute(obj, name) 6237db96d56Sopenharmony_ci except AttributeError: 6247db96d56Sopenharmony_ci if not hasattr(type(obj), '__getattr__'): 6257db96d56Sopenharmony_ci raise 6267db96d56Sopenharmony_ci return type(obj).__getattr__(obj, name) # __getattr__ 6277db96d56Sopenharmony_ci 6287db96d56Sopenharmony_ci class DualOperator(Object): 6297db96d56Sopenharmony_ci 6307db96d56Sopenharmony_ci x = 10 6317db96d56Sopenharmony_ci 6327db96d56Sopenharmony_ci def __init__(self, z): 6337db96d56Sopenharmony_ci self.z = z 6347db96d56Sopenharmony_ci 6357db96d56Sopenharmony_ci @property 6367db96d56Sopenharmony_ci def p2(self): 6377db96d56Sopenharmony_ci return 2 * self.x 6387db96d56Sopenharmony_ci 6397db96d56Sopenharmony_ci @property 6407db96d56Sopenharmony_ci def p3(self): 6417db96d56Sopenharmony_ci return 3 * self.x 6427db96d56Sopenharmony_ci 6437db96d56Sopenharmony_ci def m5(self, y): 6447db96d56Sopenharmony_ci return 5 * y 6457db96d56Sopenharmony_ci 6467db96d56Sopenharmony_ci def m7(self, y): 6477db96d56Sopenharmony_ci return 7 * y 6487db96d56Sopenharmony_ci 6497db96d56Sopenharmony_ci def __getattr__(self, name): 6507db96d56Sopenharmony_ci return ('getattr_hook', self, name) 6517db96d56Sopenharmony_ci 6527db96d56Sopenharmony_ci class DualOperatorWithSlots: 6537db96d56Sopenharmony_ci 6547db96d56Sopenharmony_ci __getitem__ = Object.__getitem__ 6557db96d56Sopenharmony_ci 6567db96d56Sopenharmony_ci __slots__ = ['z'] 6577db96d56Sopenharmony_ci 6587db96d56Sopenharmony_ci x = 15 6597db96d56Sopenharmony_ci 6607db96d56Sopenharmony_ci def __init__(self, z): 6617db96d56Sopenharmony_ci self.z = z 6627db96d56Sopenharmony_ci 6637db96d56Sopenharmony_ci @property 6647db96d56Sopenharmony_ci def p2(self): 6657db96d56Sopenharmony_ci return 2 * self.x 6667db96d56Sopenharmony_ci 6677db96d56Sopenharmony_ci def m5(self, y): 6687db96d56Sopenharmony_ci return 5 * y 6697db96d56Sopenharmony_ci 6707db96d56Sopenharmony_ci def __getattr__(self, name): 6717db96d56Sopenharmony_ci return ('getattr_hook', self, name) 6727db96d56Sopenharmony_ci 6737db96d56Sopenharmony_ci class D1: 6747db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 6757db96d56Sopenharmony_ci return type(self), obj, objtype 6767db96d56Sopenharmony_ci 6777db96d56Sopenharmony_ci class U1: 6787db96d56Sopenharmony_ci x = D1() 6797db96d56Sopenharmony_ci 6807db96d56Sopenharmony_ci class U2(U1): 6817db96d56Sopenharmony_ci pass 6827db96d56Sopenharmony_ci 6837db96d56Sopenharmony_ci.. doctest:: 6847db96d56Sopenharmony_ci :hide: 6857db96d56Sopenharmony_ci 6867db96d56Sopenharmony_ci >>> a = DualOperator(11) 6877db96d56Sopenharmony_ci >>> vars(a).update(p3 = '_p3', m7 = '_m7') 6887db96d56Sopenharmony_ci >>> a.x == a['x'] == 10 6897db96d56Sopenharmony_ci True 6907db96d56Sopenharmony_ci >>> a.z == a['z'] == 11 6917db96d56Sopenharmony_ci True 6927db96d56Sopenharmony_ci >>> a.p2 == a['p2'] == 20 6937db96d56Sopenharmony_ci True 6947db96d56Sopenharmony_ci >>> a.p3 == a['p3'] == 30 6957db96d56Sopenharmony_ci True 6967db96d56Sopenharmony_ci >>> a.m5(100) == a.m5(100) == 500 6977db96d56Sopenharmony_ci True 6987db96d56Sopenharmony_ci >>> a.m7 == a['m7'] == '_m7' 6997db96d56Sopenharmony_ci True 7007db96d56Sopenharmony_ci >>> a.g == a['g'] == ('getattr_hook', a, 'g') 7017db96d56Sopenharmony_ci True 7027db96d56Sopenharmony_ci 7037db96d56Sopenharmony_ci >>> b = DualOperatorWithSlots(22) 7047db96d56Sopenharmony_ci >>> b.x == b['x'] == 15 7057db96d56Sopenharmony_ci True 7067db96d56Sopenharmony_ci >>> b.z == b['z'] == 22 7077db96d56Sopenharmony_ci True 7087db96d56Sopenharmony_ci >>> b.p2 == b['p2'] == 30 7097db96d56Sopenharmony_ci True 7107db96d56Sopenharmony_ci >>> b.m5(200) == b['m5'](200) == 1000 7117db96d56Sopenharmony_ci True 7127db96d56Sopenharmony_ci >>> b.g == b['g'] == ('getattr_hook', b, 'g') 7137db96d56Sopenharmony_ci True 7147db96d56Sopenharmony_ci 7157db96d56Sopenharmony_ci >>> u2 = U2() 7167db96d56Sopenharmony_ci >>> object_getattribute(u2, 'x') == u2.x == (D1, u2, U2) 7177db96d56Sopenharmony_ci True 7187db96d56Sopenharmony_ci 7197db96d56Sopenharmony_ciNote, there is no :meth:`__getattr__` hook in the :meth:`__getattribute__` 7207db96d56Sopenharmony_cicode. That is why calling :meth:`__getattribute__` directly or with 7217db96d56Sopenharmony_ci``super().__getattribute__`` will bypass :meth:`__getattr__` entirely. 7227db96d56Sopenharmony_ci 7237db96d56Sopenharmony_ciInstead, it is the dot operator and the :func:`getattr` function that are 7247db96d56Sopenharmony_ciresponsible for invoking :meth:`__getattr__` whenever :meth:`__getattribute__` 7257db96d56Sopenharmony_ciraises an :exc:`AttributeError`. Their logic is encapsulated in a helper 7267db96d56Sopenharmony_cifunction: 7277db96d56Sopenharmony_ci 7287db96d56Sopenharmony_ci.. testcode:: 7297db96d56Sopenharmony_ci 7307db96d56Sopenharmony_ci def getattr_hook(obj, name): 7317db96d56Sopenharmony_ci "Emulate slot_tp_getattr_hook() in Objects/typeobject.c" 7327db96d56Sopenharmony_ci try: 7337db96d56Sopenharmony_ci return obj.__getattribute__(name) 7347db96d56Sopenharmony_ci except AttributeError: 7357db96d56Sopenharmony_ci if not hasattr(type(obj), '__getattr__'): 7367db96d56Sopenharmony_ci raise 7377db96d56Sopenharmony_ci return type(obj).__getattr__(obj, name) # __getattr__ 7387db96d56Sopenharmony_ci 7397db96d56Sopenharmony_ci.. doctest:: 7407db96d56Sopenharmony_ci :hide: 7417db96d56Sopenharmony_ci 7427db96d56Sopenharmony_ci 7437db96d56Sopenharmony_ci >>> class ClassWithGetAttr: 7447db96d56Sopenharmony_ci ... x = 123 7457db96d56Sopenharmony_ci ... def __getattr__(self, attr): 7467db96d56Sopenharmony_ci ... return attr.upper() 7477db96d56Sopenharmony_ci ... 7487db96d56Sopenharmony_ci >>> cw = ClassWithGetAttr() 7497db96d56Sopenharmony_ci >>> cw.y = 456 7507db96d56Sopenharmony_ci >>> getattr_hook(cw, 'x') 7517db96d56Sopenharmony_ci 123 7527db96d56Sopenharmony_ci >>> getattr_hook(cw, 'y') 7537db96d56Sopenharmony_ci 456 7547db96d56Sopenharmony_ci >>> getattr_hook(cw, 'z') 7557db96d56Sopenharmony_ci 'Z' 7567db96d56Sopenharmony_ci 7577db96d56Sopenharmony_ci >>> class ClassWithoutGetAttr: 7587db96d56Sopenharmony_ci ... x = 123 7597db96d56Sopenharmony_ci ... 7607db96d56Sopenharmony_ci >>> cwo = ClassWithoutGetAttr() 7617db96d56Sopenharmony_ci >>> cwo.y = 456 7627db96d56Sopenharmony_ci >>> getattr_hook(cwo, 'x') 7637db96d56Sopenharmony_ci 123 7647db96d56Sopenharmony_ci >>> getattr_hook(cwo, 'y') 7657db96d56Sopenharmony_ci 456 7667db96d56Sopenharmony_ci >>> getattr_hook(cwo, 'z') 7677db96d56Sopenharmony_ci Traceback (most recent call last): 7687db96d56Sopenharmony_ci ... 7697db96d56Sopenharmony_ci AttributeError: 'ClassWithoutGetAttr' object has no attribute 'z' 7707db96d56Sopenharmony_ci 7717db96d56Sopenharmony_ci 7727db96d56Sopenharmony_ciInvocation from a class 7737db96d56Sopenharmony_ci----------------------- 7747db96d56Sopenharmony_ci 7757db96d56Sopenharmony_ciThe logic for a dotted lookup such as ``A.x`` is in 7767db96d56Sopenharmony_ci:meth:`type.__getattribute__`. The steps are similar to those for 7777db96d56Sopenharmony_ci:meth:`object.__getattribute__` but the instance dictionary lookup is replaced 7787db96d56Sopenharmony_ciby a search through the class's :term:`method resolution order`. 7797db96d56Sopenharmony_ci 7807db96d56Sopenharmony_ciIf a descriptor is found, it is invoked with ``desc.__get__(None, A)``. 7817db96d56Sopenharmony_ci 7827db96d56Sopenharmony_ciThe full C implementation can be found in :c:func:`type_getattro()` and 7837db96d56Sopenharmony_ci:c:func:`_PyType_Lookup()` in :source:`Objects/typeobject.c`. 7847db96d56Sopenharmony_ci 7857db96d56Sopenharmony_ci 7867db96d56Sopenharmony_ciInvocation from super 7877db96d56Sopenharmony_ci--------------------- 7887db96d56Sopenharmony_ci 7897db96d56Sopenharmony_ciThe logic for super's dotted lookup is in the :meth:`__getattribute__` method for 7907db96d56Sopenharmony_ciobject returned by :class:`super()`. 7917db96d56Sopenharmony_ci 7927db96d56Sopenharmony_ciA dotted lookup such as ``super(A, obj).m`` searches ``obj.__class__.__mro__`` 7937db96d56Sopenharmony_cifor the base class ``B`` immediately following ``A`` and then returns 7947db96d56Sopenharmony_ci``B.__dict__['m'].__get__(obj, A)``. If not a descriptor, ``m`` is returned 7957db96d56Sopenharmony_ciunchanged. 7967db96d56Sopenharmony_ci 7977db96d56Sopenharmony_ciThe full C implementation can be found in :c:func:`super_getattro()` in 7987db96d56Sopenharmony_ci:source:`Objects/typeobject.c`. A pure Python equivalent can be found in 7997db96d56Sopenharmony_ci`Guido's Tutorial 8007db96d56Sopenharmony_ci<https://www.python.org/download/releases/2.2.3/descrintro/#cooperation>`_. 8017db96d56Sopenharmony_ci 8027db96d56Sopenharmony_ci 8037db96d56Sopenharmony_ciSummary of invocation logic 8047db96d56Sopenharmony_ci--------------------------- 8057db96d56Sopenharmony_ci 8067db96d56Sopenharmony_ciThe mechanism for descriptors is embedded in the :meth:`__getattribute__()` 8077db96d56Sopenharmony_cimethods for :class:`object`, :class:`type`, and :func:`super`. 8087db96d56Sopenharmony_ci 8097db96d56Sopenharmony_ciThe important points to remember are: 8107db96d56Sopenharmony_ci 8117db96d56Sopenharmony_ci* Descriptors are invoked by the :meth:`__getattribute__` method. 8127db96d56Sopenharmony_ci 8137db96d56Sopenharmony_ci* Classes inherit this machinery from :class:`object`, :class:`type`, or 8147db96d56Sopenharmony_ci :func:`super`. 8157db96d56Sopenharmony_ci 8167db96d56Sopenharmony_ci* Overriding :meth:`__getattribute__` prevents automatic descriptor calls 8177db96d56Sopenharmony_ci because all the descriptor logic is in that method. 8187db96d56Sopenharmony_ci 8197db96d56Sopenharmony_ci* :meth:`object.__getattribute__` and :meth:`type.__getattribute__` make 8207db96d56Sopenharmony_ci different calls to :meth:`__get__`. The first includes the instance and may 8217db96d56Sopenharmony_ci include the class. The second puts in ``None`` for the instance and always 8227db96d56Sopenharmony_ci includes the class. 8237db96d56Sopenharmony_ci 8247db96d56Sopenharmony_ci* Data descriptors always override instance dictionaries. 8257db96d56Sopenharmony_ci 8267db96d56Sopenharmony_ci* Non-data descriptors may be overridden by instance dictionaries. 8277db96d56Sopenharmony_ci 8287db96d56Sopenharmony_ci 8297db96d56Sopenharmony_ciAutomatic name notification 8307db96d56Sopenharmony_ci--------------------------- 8317db96d56Sopenharmony_ci 8327db96d56Sopenharmony_ciSometimes it is desirable for a descriptor to know what class variable name it 8337db96d56Sopenharmony_ciwas assigned to. When a new class is created, the :class:`type` metaclass 8347db96d56Sopenharmony_ciscans the dictionary of the new class. If any of the entries are descriptors 8357db96d56Sopenharmony_ciand if they define :meth:`__set_name__`, that method is called with two 8367db96d56Sopenharmony_ciarguments. The *owner* is the class where the descriptor is used, and the 8377db96d56Sopenharmony_ci*name* is the class variable the descriptor was assigned to. 8387db96d56Sopenharmony_ci 8397db96d56Sopenharmony_ciThe implementation details are in :c:func:`type_new()` and 8407db96d56Sopenharmony_ci:c:func:`set_names()` in :source:`Objects/typeobject.c`. 8417db96d56Sopenharmony_ci 8427db96d56Sopenharmony_ciSince the update logic is in :meth:`type.__new__`, notifications only take 8437db96d56Sopenharmony_ciplace at the time of class creation. If descriptors are added to the class 8447db96d56Sopenharmony_ciafterwards, :meth:`__set_name__` will need to be called manually. 8457db96d56Sopenharmony_ci 8467db96d56Sopenharmony_ci 8477db96d56Sopenharmony_ciORM example 8487db96d56Sopenharmony_ci----------- 8497db96d56Sopenharmony_ci 8507db96d56Sopenharmony_ciThe following code is a simplified skeleton showing how data descriptors could 8517db96d56Sopenharmony_cibe used to implement an `object relational mapping 8527db96d56Sopenharmony_ci<https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping>`_. 8537db96d56Sopenharmony_ci 8547db96d56Sopenharmony_ciThe essential idea is that the data is stored in an external database. The 8557db96d56Sopenharmony_ciPython instances only hold keys to the database's tables. Descriptors take 8567db96d56Sopenharmony_cicare of lookups or updates: 8577db96d56Sopenharmony_ci 8587db96d56Sopenharmony_ci.. testcode:: 8597db96d56Sopenharmony_ci 8607db96d56Sopenharmony_ci class Field: 8617db96d56Sopenharmony_ci 8627db96d56Sopenharmony_ci def __set_name__(self, owner, name): 8637db96d56Sopenharmony_ci self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;' 8647db96d56Sopenharmony_ci self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;' 8657db96d56Sopenharmony_ci 8667db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 8677db96d56Sopenharmony_ci return conn.execute(self.fetch, [obj.key]).fetchone()[0] 8687db96d56Sopenharmony_ci 8697db96d56Sopenharmony_ci def __set__(self, obj, value): 8707db96d56Sopenharmony_ci conn.execute(self.store, [value, obj.key]) 8717db96d56Sopenharmony_ci conn.commit() 8727db96d56Sopenharmony_ci 8737db96d56Sopenharmony_ciWe can use the :class:`Field` class to define `models 8747db96d56Sopenharmony_ci<https://en.wikipedia.org/wiki/Database_model>`_ that describe the schema for 8757db96d56Sopenharmony_cieach table in a database: 8767db96d56Sopenharmony_ci 8777db96d56Sopenharmony_ci.. testcode:: 8787db96d56Sopenharmony_ci 8797db96d56Sopenharmony_ci class Movie: 8807db96d56Sopenharmony_ci table = 'Movies' # Table name 8817db96d56Sopenharmony_ci key = 'title' # Primary key 8827db96d56Sopenharmony_ci director = Field() 8837db96d56Sopenharmony_ci year = Field() 8847db96d56Sopenharmony_ci 8857db96d56Sopenharmony_ci def __init__(self, key): 8867db96d56Sopenharmony_ci self.key = key 8877db96d56Sopenharmony_ci 8887db96d56Sopenharmony_ci class Song: 8897db96d56Sopenharmony_ci table = 'Music' 8907db96d56Sopenharmony_ci key = 'title' 8917db96d56Sopenharmony_ci artist = Field() 8927db96d56Sopenharmony_ci year = Field() 8937db96d56Sopenharmony_ci genre = Field() 8947db96d56Sopenharmony_ci 8957db96d56Sopenharmony_ci def __init__(self, key): 8967db96d56Sopenharmony_ci self.key = key 8977db96d56Sopenharmony_ci 8987db96d56Sopenharmony_ciTo use the models, first connect to the database:: 8997db96d56Sopenharmony_ci 9007db96d56Sopenharmony_ci >>> import sqlite3 9017db96d56Sopenharmony_ci >>> conn = sqlite3.connect('entertainment.db') 9027db96d56Sopenharmony_ci 9037db96d56Sopenharmony_ciAn interactive session shows how data is retrieved from the database and how 9047db96d56Sopenharmony_ciit can be updated: 9057db96d56Sopenharmony_ci 9067db96d56Sopenharmony_ci.. testsetup:: 9077db96d56Sopenharmony_ci 9087db96d56Sopenharmony_ci song_data = [ 9097db96d56Sopenharmony_ci ('Country Roads', 'John Denver', 1972), 9107db96d56Sopenharmony_ci ('Me and Bobby McGee', 'Janice Joplin', 1971), 9117db96d56Sopenharmony_ci ('Coal Miners Daughter', 'Loretta Lynn', 1970), 9127db96d56Sopenharmony_ci ] 9137db96d56Sopenharmony_ci 9147db96d56Sopenharmony_ci movie_data = [ 9157db96d56Sopenharmony_ci ('Star Wars', 'George Lucas', 1977), 9167db96d56Sopenharmony_ci ('Jaws', 'Steven Spielberg', 1975), 9177db96d56Sopenharmony_ci ('Aliens', 'James Cameron', 1986), 9187db96d56Sopenharmony_ci ] 9197db96d56Sopenharmony_ci 9207db96d56Sopenharmony_ci import sqlite3 9217db96d56Sopenharmony_ci 9227db96d56Sopenharmony_ci conn = sqlite3.connect(':memory:') 9237db96d56Sopenharmony_ci conn.execute('CREATE TABLE Music (title text, artist text, year integer);') 9247db96d56Sopenharmony_ci conn.execute('CREATE INDEX MusicNdx ON Music (title);') 9257db96d56Sopenharmony_ci conn.executemany('INSERT INTO Music VALUES (?, ?, ?);', song_data) 9267db96d56Sopenharmony_ci conn.execute('CREATE TABLE Movies (title text, director text, year integer);') 9277db96d56Sopenharmony_ci conn.execute('CREATE INDEX MovieNdx ON Music (title);') 9287db96d56Sopenharmony_ci conn.executemany('INSERT INTO Movies VALUES (?, ?, ?);', movie_data) 9297db96d56Sopenharmony_ci conn.commit() 9307db96d56Sopenharmony_ci 9317db96d56Sopenharmony_ci.. doctest:: 9327db96d56Sopenharmony_ci 9337db96d56Sopenharmony_ci >>> Movie('Star Wars').director 9347db96d56Sopenharmony_ci 'George Lucas' 9357db96d56Sopenharmony_ci >>> jaws = Movie('Jaws') 9367db96d56Sopenharmony_ci >>> f'Released in {jaws.year} by {jaws.director}' 9377db96d56Sopenharmony_ci 'Released in 1975 by Steven Spielberg' 9387db96d56Sopenharmony_ci 9397db96d56Sopenharmony_ci >>> Song('Country Roads').artist 9407db96d56Sopenharmony_ci 'John Denver' 9417db96d56Sopenharmony_ci 9427db96d56Sopenharmony_ci >>> Movie('Star Wars').director = 'J.J. Abrams' 9437db96d56Sopenharmony_ci >>> Movie('Star Wars').director 9447db96d56Sopenharmony_ci 'J.J. Abrams' 9457db96d56Sopenharmony_ci 9467db96d56Sopenharmony_ci 9477db96d56Sopenharmony_ciPure Python Equivalents 9487db96d56Sopenharmony_ci^^^^^^^^^^^^^^^^^^^^^^^ 9497db96d56Sopenharmony_ci 9507db96d56Sopenharmony_ciThe descriptor protocol is simple and offers exciting possibilities. Several 9517db96d56Sopenharmony_ciuse cases are so common that they have been prepackaged into built-in tools. 9527db96d56Sopenharmony_ciProperties, bound methods, static methods, class methods, and \_\_slots\_\_ are 9537db96d56Sopenharmony_ciall based on the descriptor protocol. 9547db96d56Sopenharmony_ci 9557db96d56Sopenharmony_ci 9567db96d56Sopenharmony_ciProperties 9577db96d56Sopenharmony_ci---------- 9587db96d56Sopenharmony_ci 9597db96d56Sopenharmony_ciCalling :func:`property` is a succinct way of building a data descriptor that 9607db96d56Sopenharmony_citriggers a function call upon access to an attribute. Its signature is:: 9617db96d56Sopenharmony_ci 9627db96d56Sopenharmony_ci property(fget=None, fset=None, fdel=None, doc=None) -> property 9637db96d56Sopenharmony_ci 9647db96d56Sopenharmony_ciThe documentation shows a typical use to define a managed attribute ``x``: 9657db96d56Sopenharmony_ci 9667db96d56Sopenharmony_ci.. testcode:: 9677db96d56Sopenharmony_ci 9687db96d56Sopenharmony_ci class C: 9697db96d56Sopenharmony_ci def getx(self): return self.__x 9707db96d56Sopenharmony_ci def setx(self, value): self.__x = value 9717db96d56Sopenharmony_ci def delx(self): del self.__x 9727db96d56Sopenharmony_ci x = property(getx, setx, delx, "I'm the 'x' property.") 9737db96d56Sopenharmony_ci 9747db96d56Sopenharmony_ci.. doctest:: 9757db96d56Sopenharmony_ci :hide: 9767db96d56Sopenharmony_ci 9777db96d56Sopenharmony_ci >>> C.x.__doc__ 9787db96d56Sopenharmony_ci "I'm the 'x' property." 9797db96d56Sopenharmony_ci >>> c.x = 2.71828 9807db96d56Sopenharmony_ci >>> c.x 9817db96d56Sopenharmony_ci 2.71828 9827db96d56Sopenharmony_ci >>> del c.x 9837db96d56Sopenharmony_ci >>> c.x 9847db96d56Sopenharmony_ci Traceback (most recent call last): 9857db96d56Sopenharmony_ci ... 9867db96d56Sopenharmony_ci AttributeError: 'C' object has no attribute '_C__x' 9877db96d56Sopenharmony_ci 9887db96d56Sopenharmony_ciTo see how :func:`property` is implemented in terms of the descriptor protocol, 9897db96d56Sopenharmony_cihere is a pure Python equivalent: 9907db96d56Sopenharmony_ci 9917db96d56Sopenharmony_ci.. testcode:: 9927db96d56Sopenharmony_ci 9937db96d56Sopenharmony_ci class Property: 9947db96d56Sopenharmony_ci "Emulate PyProperty_Type() in Objects/descrobject.c" 9957db96d56Sopenharmony_ci 9967db96d56Sopenharmony_ci def __init__(self, fget=None, fset=None, fdel=None, doc=None): 9977db96d56Sopenharmony_ci self.fget = fget 9987db96d56Sopenharmony_ci self.fset = fset 9997db96d56Sopenharmony_ci self.fdel = fdel 10007db96d56Sopenharmony_ci if doc is None and fget is not None: 10017db96d56Sopenharmony_ci doc = fget.__doc__ 10027db96d56Sopenharmony_ci self.__doc__ = doc 10037db96d56Sopenharmony_ci self._name = '' 10047db96d56Sopenharmony_ci 10057db96d56Sopenharmony_ci def __set_name__(self, owner, name): 10067db96d56Sopenharmony_ci self._name = name 10077db96d56Sopenharmony_ci 10087db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 10097db96d56Sopenharmony_ci if obj is None: 10107db96d56Sopenharmony_ci return self 10117db96d56Sopenharmony_ci if self.fget is None: 10127db96d56Sopenharmony_ci raise AttributeError(f"property '{self._name}' has no getter") 10137db96d56Sopenharmony_ci return self.fget(obj) 10147db96d56Sopenharmony_ci 10157db96d56Sopenharmony_ci def __set__(self, obj, value): 10167db96d56Sopenharmony_ci if self.fset is None: 10177db96d56Sopenharmony_ci raise AttributeError(f"property '{self._name}' has no setter") 10187db96d56Sopenharmony_ci self.fset(obj, value) 10197db96d56Sopenharmony_ci 10207db96d56Sopenharmony_ci def __delete__(self, obj): 10217db96d56Sopenharmony_ci if self.fdel is None: 10227db96d56Sopenharmony_ci raise AttributeError(f"property '{self._name}' has no deleter") 10237db96d56Sopenharmony_ci self.fdel(obj) 10247db96d56Sopenharmony_ci 10257db96d56Sopenharmony_ci def getter(self, fget): 10267db96d56Sopenharmony_ci prop = type(self)(fget, self.fset, self.fdel, self.__doc__) 10277db96d56Sopenharmony_ci prop._name = self._name 10287db96d56Sopenharmony_ci return prop 10297db96d56Sopenharmony_ci 10307db96d56Sopenharmony_ci def setter(self, fset): 10317db96d56Sopenharmony_ci prop = type(self)(self.fget, fset, self.fdel, self.__doc__) 10327db96d56Sopenharmony_ci prop._name = self._name 10337db96d56Sopenharmony_ci return prop 10347db96d56Sopenharmony_ci 10357db96d56Sopenharmony_ci def deleter(self, fdel): 10367db96d56Sopenharmony_ci prop = type(self)(self.fget, self.fset, fdel, self.__doc__) 10377db96d56Sopenharmony_ci prop._name = self._name 10387db96d56Sopenharmony_ci return prop 10397db96d56Sopenharmony_ci 10407db96d56Sopenharmony_ci.. testcode:: 10417db96d56Sopenharmony_ci :hide: 10427db96d56Sopenharmony_ci 10437db96d56Sopenharmony_ci # Verify the Property() emulation 10447db96d56Sopenharmony_ci 10457db96d56Sopenharmony_ci class CC: 10467db96d56Sopenharmony_ci def getx(self): 10477db96d56Sopenharmony_ci return self.__x 10487db96d56Sopenharmony_ci def setx(self, value): 10497db96d56Sopenharmony_ci self.__x = value 10507db96d56Sopenharmony_ci def delx(self): 10517db96d56Sopenharmony_ci del self.__x 10527db96d56Sopenharmony_ci x = Property(getx, setx, delx, "I'm the 'x' property.") 10537db96d56Sopenharmony_ci 10547db96d56Sopenharmony_ci # Now do it again but use the decorator style 10557db96d56Sopenharmony_ci 10567db96d56Sopenharmony_ci class CCC: 10577db96d56Sopenharmony_ci @Property 10587db96d56Sopenharmony_ci def x(self): 10597db96d56Sopenharmony_ci return self.__x 10607db96d56Sopenharmony_ci @x.setter 10617db96d56Sopenharmony_ci def x(self, value): 10627db96d56Sopenharmony_ci self.__x = value 10637db96d56Sopenharmony_ci @x.deleter 10647db96d56Sopenharmony_ci def x(self): 10657db96d56Sopenharmony_ci del self.__x 10667db96d56Sopenharmony_ci 10677db96d56Sopenharmony_ci 10687db96d56Sopenharmony_ci.. doctest:: 10697db96d56Sopenharmony_ci :hide: 10707db96d56Sopenharmony_ci 10717db96d56Sopenharmony_ci >>> cc = CC() 10727db96d56Sopenharmony_ci >>> hasattr(cc, 'x') 10737db96d56Sopenharmony_ci False 10747db96d56Sopenharmony_ci >>> cc.x = 33 10757db96d56Sopenharmony_ci >>> cc.x 10767db96d56Sopenharmony_ci 33 10777db96d56Sopenharmony_ci >>> del cc.x 10787db96d56Sopenharmony_ci >>> hasattr(cc, 'x') 10797db96d56Sopenharmony_ci False 10807db96d56Sopenharmony_ci 10817db96d56Sopenharmony_ci >>> ccc = CCC() 10827db96d56Sopenharmony_ci >>> hasattr(ccc, 'x') 10837db96d56Sopenharmony_ci False 10847db96d56Sopenharmony_ci >>> ccc.x = 333 10857db96d56Sopenharmony_ci >>> ccc.x == 333 10867db96d56Sopenharmony_ci True 10877db96d56Sopenharmony_ci >>> del ccc.x 10887db96d56Sopenharmony_ci >>> hasattr(ccc, 'x') 10897db96d56Sopenharmony_ci False 10907db96d56Sopenharmony_ci 10917db96d56Sopenharmony_ciThe :func:`property` builtin helps whenever a user interface has granted 10927db96d56Sopenharmony_ciattribute access and then subsequent changes require the intervention of a 10937db96d56Sopenharmony_cimethod. 10947db96d56Sopenharmony_ci 10957db96d56Sopenharmony_ciFor instance, a spreadsheet class may grant access to a cell value through 10967db96d56Sopenharmony_ci``Cell('b10').value``. Subsequent improvements to the program require the cell 10977db96d56Sopenharmony_cito be recalculated on every access; however, the programmer does not want to 10987db96d56Sopenharmony_ciaffect existing client code accessing the attribute directly. The solution is 10997db96d56Sopenharmony_cito wrap access to the value attribute in a property data descriptor: 11007db96d56Sopenharmony_ci 11017db96d56Sopenharmony_ci.. testcode:: 11027db96d56Sopenharmony_ci 11037db96d56Sopenharmony_ci class Cell: 11047db96d56Sopenharmony_ci ... 11057db96d56Sopenharmony_ci 11067db96d56Sopenharmony_ci @property 11077db96d56Sopenharmony_ci def value(self): 11087db96d56Sopenharmony_ci "Recalculate the cell before returning value" 11097db96d56Sopenharmony_ci self.recalc() 11107db96d56Sopenharmony_ci return self._value 11117db96d56Sopenharmony_ci 11127db96d56Sopenharmony_ciEither the built-in :func:`property` or our :func:`Property` equivalent would 11137db96d56Sopenharmony_ciwork in this example. 11147db96d56Sopenharmony_ci 11157db96d56Sopenharmony_ci 11167db96d56Sopenharmony_ciFunctions and methods 11177db96d56Sopenharmony_ci--------------------- 11187db96d56Sopenharmony_ci 11197db96d56Sopenharmony_ciPython's object oriented features are built upon a function based environment. 11207db96d56Sopenharmony_ciUsing non-data descriptors, the two are merged seamlessly. 11217db96d56Sopenharmony_ci 11227db96d56Sopenharmony_ciFunctions stored in class dictionaries get turned into methods when invoked. 11237db96d56Sopenharmony_ciMethods only differ from regular functions in that the object instance is 11247db96d56Sopenharmony_ciprepended to the other arguments. By convention, the instance is called 11257db96d56Sopenharmony_ci*self* but could be called *this* or any other variable name. 11267db96d56Sopenharmony_ci 11277db96d56Sopenharmony_ciMethods can be created manually with :class:`types.MethodType` which is 11287db96d56Sopenharmony_ciroughly equivalent to: 11297db96d56Sopenharmony_ci 11307db96d56Sopenharmony_ci.. testcode:: 11317db96d56Sopenharmony_ci 11327db96d56Sopenharmony_ci class MethodType: 11337db96d56Sopenharmony_ci "Emulate PyMethod_Type in Objects/classobject.c" 11347db96d56Sopenharmony_ci 11357db96d56Sopenharmony_ci def __init__(self, func, obj): 11367db96d56Sopenharmony_ci self.__func__ = func 11377db96d56Sopenharmony_ci self.__self__ = obj 11387db96d56Sopenharmony_ci 11397db96d56Sopenharmony_ci def __call__(self, *args, **kwargs): 11407db96d56Sopenharmony_ci func = self.__func__ 11417db96d56Sopenharmony_ci obj = self.__self__ 11427db96d56Sopenharmony_ci return func(obj, *args, **kwargs) 11437db96d56Sopenharmony_ci 11447db96d56Sopenharmony_ciTo support automatic creation of methods, functions include the 11457db96d56Sopenharmony_ci:meth:`__get__` method for binding methods during attribute access. This 11467db96d56Sopenharmony_cimeans that functions are non-data descriptors that return bound methods 11477db96d56Sopenharmony_ciduring dotted lookup from an instance. Here's how it works: 11487db96d56Sopenharmony_ci 11497db96d56Sopenharmony_ci.. testcode:: 11507db96d56Sopenharmony_ci 11517db96d56Sopenharmony_ci class Function: 11527db96d56Sopenharmony_ci ... 11537db96d56Sopenharmony_ci 11547db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 11557db96d56Sopenharmony_ci "Simulate func_descr_get() in Objects/funcobject.c" 11567db96d56Sopenharmony_ci if obj is None: 11577db96d56Sopenharmony_ci return self 11587db96d56Sopenharmony_ci return MethodType(self, obj) 11597db96d56Sopenharmony_ci 11607db96d56Sopenharmony_ciRunning the following class in the interpreter shows how the function 11617db96d56Sopenharmony_cidescriptor works in practice: 11627db96d56Sopenharmony_ci 11637db96d56Sopenharmony_ci.. testcode:: 11647db96d56Sopenharmony_ci 11657db96d56Sopenharmony_ci class D: 11667db96d56Sopenharmony_ci def f(self, x): 11677db96d56Sopenharmony_ci return x 11687db96d56Sopenharmony_ci 11697db96d56Sopenharmony_ciThe function has a :term:`qualified name` attribute to support introspection: 11707db96d56Sopenharmony_ci 11717db96d56Sopenharmony_ci.. doctest:: 11727db96d56Sopenharmony_ci 11737db96d56Sopenharmony_ci >>> D.f.__qualname__ 11747db96d56Sopenharmony_ci 'D.f' 11757db96d56Sopenharmony_ci 11767db96d56Sopenharmony_ciAccessing the function through the class dictionary does not invoke 11777db96d56Sopenharmony_ci:meth:`__get__`. Instead, it just returns the underlying function object:: 11787db96d56Sopenharmony_ci 11797db96d56Sopenharmony_ci >>> D.__dict__['f'] 11807db96d56Sopenharmony_ci <function D.f at 0x00C45070> 11817db96d56Sopenharmony_ci 11827db96d56Sopenharmony_ciDotted access from a class calls :meth:`__get__` which just returns the 11837db96d56Sopenharmony_ciunderlying function unchanged:: 11847db96d56Sopenharmony_ci 11857db96d56Sopenharmony_ci >>> D.f 11867db96d56Sopenharmony_ci <function D.f at 0x00C45070> 11877db96d56Sopenharmony_ci 11887db96d56Sopenharmony_ciThe interesting behavior occurs during dotted access from an instance. The 11897db96d56Sopenharmony_cidotted lookup calls :meth:`__get__` which returns a bound method object:: 11907db96d56Sopenharmony_ci 11917db96d56Sopenharmony_ci >>> d = D() 11927db96d56Sopenharmony_ci >>> d.f 11937db96d56Sopenharmony_ci <bound method D.f of <__main__.D object at 0x00B18C90>> 11947db96d56Sopenharmony_ci 11957db96d56Sopenharmony_ciInternally, the bound method stores the underlying function and the bound 11967db96d56Sopenharmony_ciinstance:: 11977db96d56Sopenharmony_ci 11987db96d56Sopenharmony_ci >>> d.f.__func__ 11997db96d56Sopenharmony_ci <function D.f at 0x00C45070> 12007db96d56Sopenharmony_ci 12017db96d56Sopenharmony_ci >>> d.f.__self__ 12027db96d56Sopenharmony_ci <__main__.D object at 0x1012e1f98> 12037db96d56Sopenharmony_ci 12047db96d56Sopenharmony_ciIf you have ever wondered where *self* comes from in regular methods or where 12057db96d56Sopenharmony_ci*cls* comes from in class methods, this is it! 12067db96d56Sopenharmony_ci 12077db96d56Sopenharmony_ci 12087db96d56Sopenharmony_ciKinds of methods 12097db96d56Sopenharmony_ci---------------- 12107db96d56Sopenharmony_ci 12117db96d56Sopenharmony_ciNon-data descriptors provide a simple mechanism for variations on the usual 12127db96d56Sopenharmony_cipatterns of binding functions into methods. 12137db96d56Sopenharmony_ci 12147db96d56Sopenharmony_ciTo recap, functions have a :meth:`__get__` method so that they can be converted 12157db96d56Sopenharmony_cito a method when accessed as attributes. The non-data descriptor transforms an 12167db96d56Sopenharmony_ci``obj.f(*args)`` call into ``f(obj, *args)``. Calling ``cls.f(*args)`` 12177db96d56Sopenharmony_cibecomes ``f(*args)``. 12187db96d56Sopenharmony_ci 12197db96d56Sopenharmony_ciThis chart summarizes the binding and its two most useful variants: 12207db96d56Sopenharmony_ci 12217db96d56Sopenharmony_ci +-----------------+----------------------+------------------+ 12227db96d56Sopenharmony_ci | Transformation | Called from an | Called from a | 12237db96d56Sopenharmony_ci | | object | class | 12247db96d56Sopenharmony_ci +=================+======================+==================+ 12257db96d56Sopenharmony_ci | function | f(obj, \*args) | f(\*args) | 12267db96d56Sopenharmony_ci +-----------------+----------------------+------------------+ 12277db96d56Sopenharmony_ci | staticmethod | f(\*args) | f(\*args) | 12287db96d56Sopenharmony_ci +-----------------+----------------------+------------------+ 12297db96d56Sopenharmony_ci | classmethod | f(type(obj), \*args) | f(cls, \*args) | 12307db96d56Sopenharmony_ci +-----------------+----------------------+------------------+ 12317db96d56Sopenharmony_ci 12327db96d56Sopenharmony_ci 12337db96d56Sopenharmony_ciStatic methods 12347db96d56Sopenharmony_ci-------------- 12357db96d56Sopenharmony_ci 12367db96d56Sopenharmony_ciStatic methods return the underlying function without changes. Calling either 12377db96d56Sopenharmony_ci``c.f`` or ``C.f`` is the equivalent of a direct lookup into 12387db96d56Sopenharmony_ci``object.__getattribute__(c, "f")`` or ``object.__getattribute__(C, "f")``. As a 12397db96d56Sopenharmony_ciresult, the function becomes identically accessible from either an object or a 12407db96d56Sopenharmony_ciclass. 12417db96d56Sopenharmony_ci 12427db96d56Sopenharmony_ciGood candidates for static methods are methods that do not reference the 12437db96d56Sopenharmony_ci``self`` variable. 12447db96d56Sopenharmony_ci 12457db96d56Sopenharmony_ciFor instance, a statistics package may include a container class for 12467db96d56Sopenharmony_ciexperimental data. The class provides normal methods for computing the average, 12477db96d56Sopenharmony_cimean, median, and other descriptive statistics that depend on the data. However, 12487db96d56Sopenharmony_cithere may be useful functions which are conceptually related but do not depend 12497db96d56Sopenharmony_cion the data. For instance, ``erf(x)`` is handy conversion routine that comes up 12507db96d56Sopenharmony_ciin statistical work but does not directly depend on a particular dataset. 12517db96d56Sopenharmony_ciIt can be called either from an object or the class: ``s.erf(1.5) --> .9332`` or 12527db96d56Sopenharmony_ci``Sample.erf(1.5) --> .9332``. 12537db96d56Sopenharmony_ci 12547db96d56Sopenharmony_ciSince static methods return the underlying function with no changes, the 12557db96d56Sopenharmony_ciexample calls are unexciting: 12567db96d56Sopenharmony_ci 12577db96d56Sopenharmony_ci.. testcode:: 12587db96d56Sopenharmony_ci 12597db96d56Sopenharmony_ci class E: 12607db96d56Sopenharmony_ci @staticmethod 12617db96d56Sopenharmony_ci def f(x): 12627db96d56Sopenharmony_ci return x * 10 12637db96d56Sopenharmony_ci 12647db96d56Sopenharmony_ci.. doctest:: 12657db96d56Sopenharmony_ci 12667db96d56Sopenharmony_ci >>> E.f(3) 12677db96d56Sopenharmony_ci 30 12687db96d56Sopenharmony_ci >>> E().f(3) 12697db96d56Sopenharmony_ci 30 12707db96d56Sopenharmony_ci 12717db96d56Sopenharmony_ciUsing the non-data descriptor protocol, a pure Python version of 12727db96d56Sopenharmony_ci:func:`staticmethod` would look like this: 12737db96d56Sopenharmony_ci 12747db96d56Sopenharmony_ci.. testcode:: 12757db96d56Sopenharmony_ci 12767db96d56Sopenharmony_ci import functools 12777db96d56Sopenharmony_ci 12787db96d56Sopenharmony_ci class StaticMethod: 12797db96d56Sopenharmony_ci "Emulate PyStaticMethod_Type() in Objects/funcobject.c" 12807db96d56Sopenharmony_ci 12817db96d56Sopenharmony_ci def __init__(self, f): 12827db96d56Sopenharmony_ci self.f = f 12837db96d56Sopenharmony_ci functools.update_wrapper(self, f) 12847db96d56Sopenharmony_ci 12857db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 12867db96d56Sopenharmony_ci return self.f 12877db96d56Sopenharmony_ci 12887db96d56Sopenharmony_ci def __call__(self, *args, **kwds): 12897db96d56Sopenharmony_ci return self.f(*args, **kwds) 12907db96d56Sopenharmony_ci 12917db96d56Sopenharmony_ciThe :func:`functools.update_wrapper` call adds a ``__wrapped__`` attribute 12927db96d56Sopenharmony_cithat refers to the underlying function. Also it carries forward 12937db96d56Sopenharmony_cithe attributes necessary to make the wrapper look like the wrapped 12947db96d56Sopenharmony_cifunction: ``__name__``, ``__qualname__``, ``__doc__``, and ``__annotations__``. 12957db96d56Sopenharmony_ci 12967db96d56Sopenharmony_ci.. testcode:: 12977db96d56Sopenharmony_ci :hide: 12987db96d56Sopenharmony_ci 12997db96d56Sopenharmony_ci class E_sim: 13007db96d56Sopenharmony_ci @StaticMethod 13017db96d56Sopenharmony_ci def f(x: int) -> str: 13027db96d56Sopenharmony_ci "Simple function example" 13037db96d56Sopenharmony_ci return "!" * x 13047db96d56Sopenharmony_ci 13057db96d56Sopenharmony_ci wrapped_ord = StaticMethod(ord) 13067db96d56Sopenharmony_ci 13077db96d56Sopenharmony_ci.. doctest:: 13087db96d56Sopenharmony_ci :hide: 13097db96d56Sopenharmony_ci 13107db96d56Sopenharmony_ci >>> E_sim.f(3) 13117db96d56Sopenharmony_ci '!!!' 13127db96d56Sopenharmony_ci >>> E_sim().f(3) 13137db96d56Sopenharmony_ci '!!!' 13147db96d56Sopenharmony_ci 13157db96d56Sopenharmony_ci >>> sm = vars(E_sim)['f'] 13167db96d56Sopenharmony_ci >>> type(sm).__name__ 13177db96d56Sopenharmony_ci 'StaticMethod' 13187db96d56Sopenharmony_ci >>> f = E_sim.f 13197db96d56Sopenharmony_ci >>> type(f).__name__ 13207db96d56Sopenharmony_ci 'function' 13217db96d56Sopenharmony_ci >>> sm.__name__ 13227db96d56Sopenharmony_ci 'f' 13237db96d56Sopenharmony_ci >>> f.__name__ 13247db96d56Sopenharmony_ci 'f' 13257db96d56Sopenharmony_ci >>> sm.__qualname__ 13267db96d56Sopenharmony_ci 'E_sim.f' 13277db96d56Sopenharmony_ci >>> f.__qualname__ 13287db96d56Sopenharmony_ci 'E_sim.f' 13297db96d56Sopenharmony_ci >>> sm.__doc__ 13307db96d56Sopenharmony_ci 'Simple function example' 13317db96d56Sopenharmony_ci >>> f.__doc__ 13327db96d56Sopenharmony_ci 'Simple function example' 13337db96d56Sopenharmony_ci >>> sm.__annotations__ 13347db96d56Sopenharmony_ci {'x': <class 'int'>, 'return': <class 'str'>} 13357db96d56Sopenharmony_ci >>> f.__annotations__ 13367db96d56Sopenharmony_ci {'x': <class 'int'>, 'return': <class 'str'>} 13377db96d56Sopenharmony_ci >>> sm.__module__ == f.__module__ 13387db96d56Sopenharmony_ci True 13397db96d56Sopenharmony_ci >>> sm(3) 13407db96d56Sopenharmony_ci '!!!' 13417db96d56Sopenharmony_ci >>> f(3) 13427db96d56Sopenharmony_ci '!!!' 13437db96d56Sopenharmony_ci 13447db96d56Sopenharmony_ci >>> wrapped_ord('A') 13457db96d56Sopenharmony_ci 65 13467db96d56Sopenharmony_ci >>> wrapped_ord.__module__ == ord.__module__ 13477db96d56Sopenharmony_ci True 13487db96d56Sopenharmony_ci >>> wrapped_ord.__wrapped__ == ord 13497db96d56Sopenharmony_ci True 13507db96d56Sopenharmony_ci >>> wrapped_ord.__name__ == ord.__name__ 13517db96d56Sopenharmony_ci True 13527db96d56Sopenharmony_ci >>> wrapped_ord.__qualname__ == ord.__qualname__ 13537db96d56Sopenharmony_ci True 13547db96d56Sopenharmony_ci >>> wrapped_ord.__doc__ == ord.__doc__ 13557db96d56Sopenharmony_ci True 13567db96d56Sopenharmony_ci 13577db96d56Sopenharmony_ci 13587db96d56Sopenharmony_ciClass methods 13597db96d56Sopenharmony_ci------------- 13607db96d56Sopenharmony_ci 13617db96d56Sopenharmony_ciUnlike static methods, class methods prepend the class reference to the 13627db96d56Sopenharmony_ciargument list before calling the function. This format is the same 13637db96d56Sopenharmony_cifor whether the caller is an object or a class: 13647db96d56Sopenharmony_ci 13657db96d56Sopenharmony_ci.. testcode:: 13667db96d56Sopenharmony_ci 13677db96d56Sopenharmony_ci class F: 13687db96d56Sopenharmony_ci @classmethod 13697db96d56Sopenharmony_ci def f(cls, x): 13707db96d56Sopenharmony_ci return cls.__name__, x 13717db96d56Sopenharmony_ci 13727db96d56Sopenharmony_ci.. doctest:: 13737db96d56Sopenharmony_ci 13747db96d56Sopenharmony_ci >>> F.f(3) 13757db96d56Sopenharmony_ci ('F', 3) 13767db96d56Sopenharmony_ci >>> F().f(3) 13777db96d56Sopenharmony_ci ('F', 3) 13787db96d56Sopenharmony_ci 13797db96d56Sopenharmony_ciThis behavior is useful whenever the method only needs to have a class 13807db96d56Sopenharmony_cireference and does not rely on data stored in a specific instance. One use for 13817db96d56Sopenharmony_ciclass methods is to create alternate class constructors. For example, the 13827db96d56Sopenharmony_ciclassmethod :func:`dict.fromkeys` creates a new dictionary from a list of 13837db96d56Sopenharmony_cikeys. The pure Python equivalent is: 13847db96d56Sopenharmony_ci 13857db96d56Sopenharmony_ci.. testcode:: 13867db96d56Sopenharmony_ci 13877db96d56Sopenharmony_ci class Dict(dict): 13887db96d56Sopenharmony_ci @classmethod 13897db96d56Sopenharmony_ci def fromkeys(cls, iterable, value=None): 13907db96d56Sopenharmony_ci "Emulate dict_fromkeys() in Objects/dictobject.c" 13917db96d56Sopenharmony_ci d = cls() 13927db96d56Sopenharmony_ci for key in iterable: 13937db96d56Sopenharmony_ci d[key] = value 13947db96d56Sopenharmony_ci return d 13957db96d56Sopenharmony_ci 13967db96d56Sopenharmony_ciNow a new dictionary of unique keys can be constructed like this: 13977db96d56Sopenharmony_ci 13987db96d56Sopenharmony_ci.. doctest:: 13997db96d56Sopenharmony_ci 14007db96d56Sopenharmony_ci >>> d = Dict.fromkeys('abracadabra') 14017db96d56Sopenharmony_ci >>> type(d) is Dict 14027db96d56Sopenharmony_ci True 14037db96d56Sopenharmony_ci >>> d 14047db96d56Sopenharmony_ci {'a': None, 'b': None, 'r': None, 'c': None, 'd': None} 14057db96d56Sopenharmony_ci 14067db96d56Sopenharmony_ciUsing the non-data descriptor protocol, a pure Python version of 14077db96d56Sopenharmony_ci:func:`classmethod` would look like this: 14087db96d56Sopenharmony_ci 14097db96d56Sopenharmony_ci.. testcode:: 14107db96d56Sopenharmony_ci 14117db96d56Sopenharmony_ci import functools 14127db96d56Sopenharmony_ci 14137db96d56Sopenharmony_ci class ClassMethod: 14147db96d56Sopenharmony_ci "Emulate PyClassMethod_Type() in Objects/funcobject.c" 14157db96d56Sopenharmony_ci 14167db96d56Sopenharmony_ci def __init__(self, f): 14177db96d56Sopenharmony_ci self.f = f 14187db96d56Sopenharmony_ci functools.update_wrapper(self, f) 14197db96d56Sopenharmony_ci 14207db96d56Sopenharmony_ci def __get__(self, obj, cls=None): 14217db96d56Sopenharmony_ci if cls is None: 14227db96d56Sopenharmony_ci cls = type(obj) 14237db96d56Sopenharmony_ci if hasattr(type(self.f), '__get__'): 14247db96d56Sopenharmony_ci # This code path was added in Python 3.9 14257db96d56Sopenharmony_ci # and was deprecated in Python 3.11. 14267db96d56Sopenharmony_ci return self.f.__get__(cls, cls) 14277db96d56Sopenharmony_ci return MethodType(self.f, cls) 14287db96d56Sopenharmony_ci 14297db96d56Sopenharmony_ci.. testcode:: 14307db96d56Sopenharmony_ci :hide: 14317db96d56Sopenharmony_ci 14327db96d56Sopenharmony_ci # Verify the emulation works 14337db96d56Sopenharmony_ci class T: 14347db96d56Sopenharmony_ci @ClassMethod 14357db96d56Sopenharmony_ci def cm(cls, x: int, y: str) -> tuple[str, int, str]: 14367db96d56Sopenharmony_ci "Class method that returns a tuple" 14377db96d56Sopenharmony_ci return (cls.__name__, x, y) 14387db96d56Sopenharmony_ci 14397db96d56Sopenharmony_ci @ClassMethod 14407db96d56Sopenharmony_ci @property 14417db96d56Sopenharmony_ci def __doc__(cls): 14427db96d56Sopenharmony_ci return f'A doc for {cls.__name__!r}' 14437db96d56Sopenharmony_ci 14447db96d56Sopenharmony_ci 14457db96d56Sopenharmony_ci.. doctest:: 14467db96d56Sopenharmony_ci :hide: 14477db96d56Sopenharmony_ci 14487db96d56Sopenharmony_ci >>> T.cm(11, 22) 14497db96d56Sopenharmony_ci ('T', 11, 22) 14507db96d56Sopenharmony_ci 14517db96d56Sopenharmony_ci # Also call it from an instance 14527db96d56Sopenharmony_ci >>> t = T() 14537db96d56Sopenharmony_ci >>> t.cm(11, 22) 14547db96d56Sopenharmony_ci ('T', 11, 22) 14557db96d56Sopenharmony_ci 14567db96d56Sopenharmony_ci # Check the alternate path for chained descriptors 14577db96d56Sopenharmony_ci >>> T.__doc__ 14587db96d56Sopenharmony_ci "A doc for 'T'" 14597db96d56Sopenharmony_ci 14607db96d56Sopenharmony_ci # Verify that T uses our emulation 14617db96d56Sopenharmony_ci >>> type(vars(T)['cm']).__name__ 14627db96d56Sopenharmony_ci 'ClassMethod' 14637db96d56Sopenharmony_ci 14647db96d56Sopenharmony_ci # Verify that update_wrapper() correctly copied attributes 14657db96d56Sopenharmony_ci >>> T.cm.__name__ 14667db96d56Sopenharmony_ci 'cm' 14677db96d56Sopenharmony_ci >>> T.cm.__qualname__ 14687db96d56Sopenharmony_ci 'T.cm' 14697db96d56Sopenharmony_ci >>> T.cm.__doc__ 14707db96d56Sopenharmony_ci 'Class method that returns a tuple' 14717db96d56Sopenharmony_ci >>> T.cm.__annotations__ 14727db96d56Sopenharmony_ci {'x': <class 'int'>, 'y': <class 'str'>, 'return': tuple[str, int, str]} 14737db96d56Sopenharmony_ci 14747db96d56Sopenharmony_ci # Verify that __wrapped__ was added and works correctly 14757db96d56Sopenharmony_ci >>> f = vars(T)['cm'].__wrapped__ 14767db96d56Sopenharmony_ci >>> type(f).__name__ 14777db96d56Sopenharmony_ci 'function' 14787db96d56Sopenharmony_ci >>> f.__name__ 14797db96d56Sopenharmony_ci 'cm' 14807db96d56Sopenharmony_ci >>> f(T, 11, 22) 14817db96d56Sopenharmony_ci ('T', 11, 22) 14827db96d56Sopenharmony_ci 14837db96d56Sopenharmony_ci 14847db96d56Sopenharmony_ciThe code path for ``hasattr(type(self.f), '__get__')`` was added in 14857db96d56Sopenharmony_ciPython 3.9 and makes it possible for :func:`classmethod` to support 14867db96d56Sopenharmony_cichained decorators. For example, a classmethod and property could be 14877db96d56Sopenharmony_cichained together. In Python 3.11, this functionality was deprecated. 14887db96d56Sopenharmony_ci 14897db96d56Sopenharmony_ci.. testcode:: 14907db96d56Sopenharmony_ci 14917db96d56Sopenharmony_ci class G: 14927db96d56Sopenharmony_ci @classmethod 14937db96d56Sopenharmony_ci @property 14947db96d56Sopenharmony_ci def __doc__(cls): 14957db96d56Sopenharmony_ci return f'A doc for {cls.__name__!r}' 14967db96d56Sopenharmony_ci 14977db96d56Sopenharmony_ci.. doctest:: 14987db96d56Sopenharmony_ci 14997db96d56Sopenharmony_ci >>> G.__doc__ 15007db96d56Sopenharmony_ci "A doc for 'G'" 15017db96d56Sopenharmony_ci 15027db96d56Sopenharmony_ciThe :func:`functools.update_wrapper` call in ``ClassMethod`` adds a 15037db96d56Sopenharmony_ci``__wrapped__`` attribute that refers to the underlying function. Also 15047db96d56Sopenharmony_ciit carries forward the attributes necessary to make the wrapper look 15057db96d56Sopenharmony_cilike the wrapped function: ``__name__``, ``__qualname__``, ``__doc__``, 15067db96d56Sopenharmony_ciand ``__annotations__``. 15077db96d56Sopenharmony_ci 15087db96d56Sopenharmony_ci 15097db96d56Sopenharmony_ciMember objects and __slots__ 15107db96d56Sopenharmony_ci---------------------------- 15117db96d56Sopenharmony_ci 15127db96d56Sopenharmony_ciWhen a class defines ``__slots__``, it replaces instance dictionaries with a 15137db96d56Sopenharmony_cifixed-length array of slot values. From a user point of view that has 15147db96d56Sopenharmony_ciseveral effects: 15157db96d56Sopenharmony_ci 15167db96d56Sopenharmony_ci1. Provides immediate detection of bugs due to misspelled attribute 15177db96d56Sopenharmony_ciassignments. Only attribute names specified in ``__slots__`` are allowed: 15187db96d56Sopenharmony_ci 15197db96d56Sopenharmony_ci.. testcode:: 15207db96d56Sopenharmony_ci 15217db96d56Sopenharmony_ci class Vehicle: 15227db96d56Sopenharmony_ci __slots__ = ('id_number', 'make', 'model') 15237db96d56Sopenharmony_ci 15247db96d56Sopenharmony_ci.. doctest:: 15257db96d56Sopenharmony_ci 15267db96d56Sopenharmony_ci >>> auto = Vehicle() 15277db96d56Sopenharmony_ci >>> auto.id_nubmer = 'VYE483814LQEX' 15287db96d56Sopenharmony_ci Traceback (most recent call last): 15297db96d56Sopenharmony_ci ... 15307db96d56Sopenharmony_ci AttributeError: 'Vehicle' object has no attribute 'id_nubmer' 15317db96d56Sopenharmony_ci 15327db96d56Sopenharmony_ci2. Helps create immutable objects where descriptors manage access to private 15337db96d56Sopenharmony_ciattributes stored in ``__slots__``: 15347db96d56Sopenharmony_ci 15357db96d56Sopenharmony_ci.. testcode:: 15367db96d56Sopenharmony_ci 15377db96d56Sopenharmony_ci class Immutable: 15387db96d56Sopenharmony_ci 15397db96d56Sopenharmony_ci __slots__ = ('_dept', '_name') # Replace the instance dictionary 15407db96d56Sopenharmony_ci 15417db96d56Sopenharmony_ci def __init__(self, dept, name): 15427db96d56Sopenharmony_ci self._dept = dept # Store to private attribute 15437db96d56Sopenharmony_ci self._name = name # Store to private attribute 15447db96d56Sopenharmony_ci 15457db96d56Sopenharmony_ci @property # Read-only descriptor 15467db96d56Sopenharmony_ci def dept(self): 15477db96d56Sopenharmony_ci return self._dept 15487db96d56Sopenharmony_ci 15497db96d56Sopenharmony_ci @property 15507db96d56Sopenharmony_ci def name(self): # Read-only descriptor 15517db96d56Sopenharmony_ci return self._name 15527db96d56Sopenharmony_ci 15537db96d56Sopenharmony_ci.. doctest:: 15547db96d56Sopenharmony_ci 15557db96d56Sopenharmony_ci >>> mark = Immutable('Botany', 'Mark Watney') 15567db96d56Sopenharmony_ci >>> mark.dept 15577db96d56Sopenharmony_ci 'Botany' 15587db96d56Sopenharmony_ci >>> mark.dept = 'Space Pirate' 15597db96d56Sopenharmony_ci Traceback (most recent call last): 15607db96d56Sopenharmony_ci ... 15617db96d56Sopenharmony_ci AttributeError: property 'dept' of 'Immutable' object has no setter 15627db96d56Sopenharmony_ci >>> mark.location = 'Mars' 15637db96d56Sopenharmony_ci Traceback (most recent call last): 15647db96d56Sopenharmony_ci ... 15657db96d56Sopenharmony_ci AttributeError: 'Immutable' object has no attribute 'location' 15667db96d56Sopenharmony_ci 15677db96d56Sopenharmony_ci3. Saves memory. On a 64-bit Linux build, an instance with two attributes 15687db96d56Sopenharmony_citakes 48 bytes with ``__slots__`` and 152 bytes without. This `flyweight 15697db96d56Sopenharmony_cidesign pattern <https://en.wikipedia.org/wiki/Flyweight_pattern>`_ likely only 15707db96d56Sopenharmony_cimatters when a large number of instances are going to be created. 15717db96d56Sopenharmony_ci 15727db96d56Sopenharmony_ci4. Improves speed. Reading instance variables is 35% faster with 15737db96d56Sopenharmony_ci``__slots__`` (as measured with Python 3.10 on an Apple M1 processor). 15747db96d56Sopenharmony_ci 15757db96d56Sopenharmony_ci5. Blocks tools like :func:`functools.cached_property` which require an 15767db96d56Sopenharmony_ciinstance dictionary to function correctly: 15777db96d56Sopenharmony_ci 15787db96d56Sopenharmony_ci.. testcode:: 15797db96d56Sopenharmony_ci 15807db96d56Sopenharmony_ci from functools import cached_property 15817db96d56Sopenharmony_ci 15827db96d56Sopenharmony_ci class CP: 15837db96d56Sopenharmony_ci __slots__ = () # Eliminates the instance dict 15847db96d56Sopenharmony_ci 15857db96d56Sopenharmony_ci @cached_property # Requires an instance dict 15867db96d56Sopenharmony_ci def pi(self): 15877db96d56Sopenharmony_ci return 4 * sum((-1.0)**n / (2.0*n + 1.0) 15887db96d56Sopenharmony_ci for n in reversed(range(100_000))) 15897db96d56Sopenharmony_ci 15907db96d56Sopenharmony_ci.. doctest:: 15917db96d56Sopenharmony_ci 15927db96d56Sopenharmony_ci >>> CP().pi 15937db96d56Sopenharmony_ci Traceback (most recent call last): 15947db96d56Sopenharmony_ci ... 15957db96d56Sopenharmony_ci TypeError: No '__dict__' attribute on 'CP' instance to cache 'pi' property. 15967db96d56Sopenharmony_ci 15977db96d56Sopenharmony_ciIt is not possible to create an exact drop-in pure Python version of 15987db96d56Sopenharmony_ci``__slots__`` because it requires direct access to C structures and control 15997db96d56Sopenharmony_ciover object memory allocation. However, we can build a mostly faithful 16007db96d56Sopenharmony_cisimulation where the actual C structure for slots is emulated by a private 16017db96d56Sopenharmony_ci``_slotvalues`` list. Reads and writes to that private structure are managed 16027db96d56Sopenharmony_ciby member descriptors: 16037db96d56Sopenharmony_ci 16047db96d56Sopenharmony_ci.. testcode:: 16057db96d56Sopenharmony_ci 16067db96d56Sopenharmony_ci null = object() 16077db96d56Sopenharmony_ci 16087db96d56Sopenharmony_ci class Member: 16097db96d56Sopenharmony_ci 16107db96d56Sopenharmony_ci def __init__(self, name, clsname, offset): 16117db96d56Sopenharmony_ci 'Emulate PyMemberDef in Include/structmember.h' 16127db96d56Sopenharmony_ci # Also see descr_new() in Objects/descrobject.c 16137db96d56Sopenharmony_ci self.name = name 16147db96d56Sopenharmony_ci self.clsname = clsname 16157db96d56Sopenharmony_ci self.offset = offset 16167db96d56Sopenharmony_ci 16177db96d56Sopenharmony_ci def __get__(self, obj, objtype=None): 16187db96d56Sopenharmony_ci 'Emulate member_get() in Objects/descrobject.c' 16197db96d56Sopenharmony_ci # Also see PyMember_GetOne() in Python/structmember.c 16207db96d56Sopenharmony_ci if obj is None: 16217db96d56Sopenharmony_ci return self 16227db96d56Sopenharmony_ci value = obj._slotvalues[self.offset] 16237db96d56Sopenharmony_ci if value is null: 16247db96d56Sopenharmony_ci raise AttributeError(self.name) 16257db96d56Sopenharmony_ci return value 16267db96d56Sopenharmony_ci 16277db96d56Sopenharmony_ci def __set__(self, obj, value): 16287db96d56Sopenharmony_ci 'Emulate member_set() in Objects/descrobject.c' 16297db96d56Sopenharmony_ci obj._slotvalues[self.offset] = value 16307db96d56Sopenharmony_ci 16317db96d56Sopenharmony_ci def __delete__(self, obj): 16327db96d56Sopenharmony_ci 'Emulate member_delete() in Objects/descrobject.c' 16337db96d56Sopenharmony_ci value = obj._slotvalues[self.offset] 16347db96d56Sopenharmony_ci if value is null: 16357db96d56Sopenharmony_ci raise AttributeError(self.name) 16367db96d56Sopenharmony_ci obj._slotvalues[self.offset] = null 16377db96d56Sopenharmony_ci 16387db96d56Sopenharmony_ci def __repr__(self): 16397db96d56Sopenharmony_ci 'Emulate member_repr() in Objects/descrobject.c' 16407db96d56Sopenharmony_ci return f'<Member {self.name!r} of {self.clsname!r}>' 16417db96d56Sopenharmony_ci 16427db96d56Sopenharmony_ciThe :meth:`type.__new__` method takes care of adding member objects to class 16437db96d56Sopenharmony_civariables: 16447db96d56Sopenharmony_ci 16457db96d56Sopenharmony_ci.. testcode:: 16467db96d56Sopenharmony_ci 16477db96d56Sopenharmony_ci class Type(type): 16487db96d56Sopenharmony_ci 'Simulate how the type metaclass adds member objects for slots' 16497db96d56Sopenharmony_ci 16507db96d56Sopenharmony_ci def __new__(mcls, clsname, bases, mapping, **kwargs): 16517db96d56Sopenharmony_ci 'Emulate type_new() in Objects/typeobject.c' 16527db96d56Sopenharmony_ci # type_new() calls PyTypeReady() which calls add_methods() 16537db96d56Sopenharmony_ci slot_names = mapping.get('slot_names', []) 16547db96d56Sopenharmony_ci for offset, name in enumerate(slot_names): 16557db96d56Sopenharmony_ci mapping[name] = Member(name, clsname, offset) 16567db96d56Sopenharmony_ci return type.__new__(mcls, clsname, bases, mapping, **kwargs) 16577db96d56Sopenharmony_ci 16587db96d56Sopenharmony_ciThe :meth:`object.__new__` method takes care of creating instances that have 16597db96d56Sopenharmony_cislots instead of an instance dictionary. Here is a rough simulation in pure 16607db96d56Sopenharmony_ciPython: 16617db96d56Sopenharmony_ci 16627db96d56Sopenharmony_ci.. testcode:: 16637db96d56Sopenharmony_ci 16647db96d56Sopenharmony_ci class Object: 16657db96d56Sopenharmony_ci 'Simulate how object.__new__() allocates memory for __slots__' 16667db96d56Sopenharmony_ci 16677db96d56Sopenharmony_ci def __new__(cls, *args, **kwargs): 16687db96d56Sopenharmony_ci 'Emulate object_new() in Objects/typeobject.c' 16697db96d56Sopenharmony_ci inst = super().__new__(cls) 16707db96d56Sopenharmony_ci if hasattr(cls, 'slot_names'): 16717db96d56Sopenharmony_ci empty_slots = [null] * len(cls.slot_names) 16727db96d56Sopenharmony_ci object.__setattr__(inst, '_slotvalues', empty_slots) 16737db96d56Sopenharmony_ci return inst 16747db96d56Sopenharmony_ci 16757db96d56Sopenharmony_ci def __setattr__(self, name, value): 16767db96d56Sopenharmony_ci 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c' 16777db96d56Sopenharmony_ci cls = type(self) 16787db96d56Sopenharmony_ci if hasattr(cls, 'slot_names') and name not in cls.slot_names: 16797db96d56Sopenharmony_ci raise AttributeError( 16807db96d56Sopenharmony_ci f'{cls.__name__!r} object has no attribute {name!r}' 16817db96d56Sopenharmony_ci ) 16827db96d56Sopenharmony_ci super().__setattr__(name, value) 16837db96d56Sopenharmony_ci 16847db96d56Sopenharmony_ci def __delattr__(self, name): 16857db96d56Sopenharmony_ci 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c' 16867db96d56Sopenharmony_ci cls = type(self) 16877db96d56Sopenharmony_ci if hasattr(cls, 'slot_names') and name not in cls.slot_names: 16887db96d56Sopenharmony_ci raise AttributeError( 16897db96d56Sopenharmony_ci f'{cls.__name__!r} object has no attribute {name!r}' 16907db96d56Sopenharmony_ci ) 16917db96d56Sopenharmony_ci super().__delattr__(name) 16927db96d56Sopenharmony_ci 16937db96d56Sopenharmony_ciTo use the simulation in a real class, just inherit from :class:`Object` and 16947db96d56Sopenharmony_ciset the :term:`metaclass` to :class:`Type`: 16957db96d56Sopenharmony_ci 16967db96d56Sopenharmony_ci.. testcode:: 16977db96d56Sopenharmony_ci 16987db96d56Sopenharmony_ci class H(Object, metaclass=Type): 16997db96d56Sopenharmony_ci 'Instance variables stored in slots' 17007db96d56Sopenharmony_ci 17017db96d56Sopenharmony_ci slot_names = ['x', 'y'] 17027db96d56Sopenharmony_ci 17037db96d56Sopenharmony_ci def __init__(self, x, y): 17047db96d56Sopenharmony_ci self.x = x 17057db96d56Sopenharmony_ci self.y = y 17067db96d56Sopenharmony_ci 17077db96d56Sopenharmony_ciAt this point, the metaclass has loaded member objects for *x* and *y*:: 17087db96d56Sopenharmony_ci 17097db96d56Sopenharmony_ci >>> from pprint import pp 17107db96d56Sopenharmony_ci >>> pp(dict(vars(H))) 17117db96d56Sopenharmony_ci {'__module__': '__main__', 17127db96d56Sopenharmony_ci '__doc__': 'Instance variables stored in slots', 17137db96d56Sopenharmony_ci 'slot_names': ['x', 'y'], 17147db96d56Sopenharmony_ci '__init__': <function H.__init__ at 0x7fb5d302f9d0>, 17157db96d56Sopenharmony_ci 'x': <Member 'x' of 'H'>, 17167db96d56Sopenharmony_ci 'y': <Member 'y' of 'H'>} 17177db96d56Sopenharmony_ci 17187db96d56Sopenharmony_ci.. doctest:: 17197db96d56Sopenharmony_ci :hide: 17207db96d56Sopenharmony_ci 17217db96d56Sopenharmony_ci # We test this separately because the preceding section is not 17227db96d56Sopenharmony_ci # doctestable due to the hex memory address for the __init__ function 17237db96d56Sopenharmony_ci >>> isinstance(vars(H)['x'], Member) 17247db96d56Sopenharmony_ci True 17257db96d56Sopenharmony_ci >>> isinstance(vars(H)['y'], Member) 17267db96d56Sopenharmony_ci True 17277db96d56Sopenharmony_ci 17287db96d56Sopenharmony_ciWhen instances are created, they have a ``slot_values`` list where the 17297db96d56Sopenharmony_ciattributes are stored: 17307db96d56Sopenharmony_ci 17317db96d56Sopenharmony_ci.. doctest:: 17327db96d56Sopenharmony_ci 17337db96d56Sopenharmony_ci >>> h = H(10, 20) 17347db96d56Sopenharmony_ci >>> vars(h) 17357db96d56Sopenharmony_ci {'_slotvalues': [10, 20]} 17367db96d56Sopenharmony_ci >>> h.x = 55 17377db96d56Sopenharmony_ci >>> vars(h) 17387db96d56Sopenharmony_ci {'_slotvalues': [55, 20]} 17397db96d56Sopenharmony_ci 17407db96d56Sopenharmony_ciMisspelled or unassigned attributes will raise an exception: 17417db96d56Sopenharmony_ci 17427db96d56Sopenharmony_ci.. doctest:: 17437db96d56Sopenharmony_ci 17447db96d56Sopenharmony_ci >>> h.xz 17457db96d56Sopenharmony_ci Traceback (most recent call last): 17467db96d56Sopenharmony_ci ... 17477db96d56Sopenharmony_ci AttributeError: 'H' object has no attribute 'xz' 17487db96d56Sopenharmony_ci 17497db96d56Sopenharmony_ci.. doctest:: 17507db96d56Sopenharmony_ci :hide: 17517db96d56Sopenharmony_ci 17527db96d56Sopenharmony_ci # Examples for deleted attributes are not shown because this section 17537db96d56Sopenharmony_ci # is already a bit lengthy. We still test that code here. 17547db96d56Sopenharmony_ci >>> del h.x 17557db96d56Sopenharmony_ci >>> hasattr(h, 'x') 17567db96d56Sopenharmony_ci False 17577db96d56Sopenharmony_ci 17587db96d56Sopenharmony_ci # Also test the code for uninitialized slots 17597db96d56Sopenharmony_ci >>> class HU(Object, metaclass=Type): 17607db96d56Sopenharmony_ci ... slot_names = ['x', 'y'] 17617db96d56Sopenharmony_ci ... 17627db96d56Sopenharmony_ci >>> hu = HU() 17637db96d56Sopenharmony_ci >>> hasattr(hu, 'x') 17647db96d56Sopenharmony_ci False 17657db96d56Sopenharmony_ci >>> hasattr(hu, 'y') 17667db96d56Sopenharmony_ci False 1767