diff -r 1ccf0756a13d Doc/howto/descriptor.rst --- a/Doc/howto/descriptor.rst Sun Feb 10 23:59:46 2013 +0000 +++ b/Doc/howto/descriptor.rst Mon Feb 11 18:35:18 2013 +0100 @@ -50,11 +50,13 @@ Descriptor Protocol ------------------- -``descr.__get__(self, obj, type=None) --> value`` +.. code-block:: python -``descr.__set__(self, obj, value) --> None`` + descr.__get__(self, instance, owner=None) # --> Value -``descr.__delete__(self, obj) --> None`` + descr.__set__(self, instance, value) # --> None + + descr.__delete__(self, instance) # --> None That is all there is to it. Define any of these methods and an object is considered a descriptor and can override default behavior upon being looked up @@ -80,13 +82,15 @@ Invoking Descriptors -------------------- -A descriptor can be called directly by its method name. For example, +A common proctice is to return the descriptor (`self`) if the instance given to +:meth:`__get__` is `None`, so that accesses through the class work. A +descriptor can be called directly by its method name. For example, ``d.__get__(obj)``. Alternatively, it is more common for a descriptor to be invoked automatically -upon attribute access. For example, ``obj.d`` looks up ``d`` in the dictionary -of ``obj``. If ``d`` defines the method :meth:`__get__`, then ``d.__get__(obj)`` -is invoked according to the precedence rules listed below. +upon attribute access on instances. For example, ``obj.d`` looks up ``d`` in +the dictionary of ``obj``. If ``d`` defines the method :meth:`__get__`, then +``d.__get__(obj)`` is invoked according to the precedence rules listed below. The details of invocation depend on whether ``obj`` is an object or a class. Either way, descriptors only work for new style objects and classes. A class is @@ -102,10 +106,12 @@ For classes, the machinery is in :meth:`type.__getattribute__` which transforms ``B.x`` into ``B.__dict__['x'].__get__(None, B)``. In pure Python, it looks -like:: +like: + +.. code-block:: python def __getattribute__(self, key): - "Emulate type_getattro() in Objects/typeobject.c" + '''Emulate type_getattro() in Objects/typeobject.c''' v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) @@ -121,7 +127,7 @@ * data descriptors always override instance dictionaries. * non-data descriptors may be overridden by instance dictionaries. -The object returned by ``super()`` also has a custom :meth:`__getattribute__` +The object returned by :meth:`super` also has a custom :meth:`__getattribute__` method for invoking descriptors. The call ``super(B, obj).m()`` searches ``obj.__class__.__mro__`` for the base class ``A`` immediately following ``B`` and then returns ``A.__dict__['m'].__get__(obj, A)``. If not a descriptor, @@ -151,29 +157,26 @@ The following code creates a class whose objects are data descriptors which print a message for each get or set. Overriding :meth:`__getattribute__` is alternate approach that could do this for every attribute. However, this -descriptor is useful for monitoring just a few chosen attributes:: +descriptor is useful for monitoring just a few chosen attributes: - class RevealAccess(object): - """A data descriptor that sets and returns values - normally and prints a message logging their access. - """ +.. code-block:: python - def __init__(self, initval=None, name='var'): - self.val = initval - self.name = name - - def __get__(self, obj, objtype): - print 'Retrieving', self.name - return self.val - - def __set__(self, obj, val): - print 'Updating' , self.name - self.val = val - + >>> class RevealAccess(object): + ... """A data descriptor that sets and returns values + ... normally and prints a message logging their access. + ... """ + ... def __init__(self, initval=None, name='var'): + ... self.val = initval + ... self.name = name + ... def __get__(self, instance, owner=None): + ... print 'Retrieving', self.name + ... return self.val + ... def __set__(self, instance, val): + ... print 'Updating' , self.name + ... self.val = val >>> class MyClass(object): - x = RevealAccess(10, 'var "x"') - y = 5 - + ... x = RevealAccess(10, 'var "x"') + ... y = 5 >>> m = MyClass() >>> m.x Retrieving var "x" @@ -191,28 +194,45 @@ Properties, bound and unbound methods, static methods, and class methods are all based on the descriptor protocol. +.. note:: + In this how-to, :meth:`property`, :meth:`classmethod` and + :meth:`staticmethod` are used like functions, but they can also be used as + decorators. See also their respective documentations. + Properties ---------- Calling :func:`property` is a succinct way of building a data descriptor that -triggers function calls upon access to an attribute. Its signature is:: +triggers function calls upon access to an attribute. Its signature is: - property(fget=None, fset=None, fdel=None, doc=None) -> property attribute +.. code-block:: python -The documentation shows a typical use to define a managed attribute ``x``:: + property(fget=None, fset=None, fdel=None, doc=None) # -> property attribute + +The documentation shows a typical use to define a managed attribute ``x``: + +.. code-block:: python class C(object): - def getx(self): return self.__x - def setx(self, value): self.__x = value - def delx(self): del self.__x + def getx(self): + return self.__x + + def setx(self, value): + self.__x = value + + def delx(self): + del self.__x + x = property(getx, setx, delx, "I'm the 'x' property.") To see how :func:`property` is implemented in terms of the descriptor protocol, -here is a pure Python equivalent:: +here is a pure Python equivalent: + +.. code-block:: python class Property(object): - "Emulate PyProperty_Type() in Objects/descrobject.c" + '''Emulate PyProperty_Type() in Objects/descrobject.c''' def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget @@ -220,22 +240,22 @@ self.fdel = fdel self.__doc__ = doc - def __get__(self, obj, objtype=None): - if obj is None: + def __get__(self, instance, owner=None): + if instance is None: return self if self.fget is None: raise AttributeError, "unreadable attribute" - return self.fget(obj) + return self.fget(instance) - def __set__(self, obj, value): + def __set__(self, instance, value): if self.fset is None: raise AttributeError, "can't set attribute" - self.fset(obj, value) + self.fset(instance, value) - def __delete__(self, obj): + def __delete__(self, instance): if self.fdel is None: raise AttributeError, "can't delete attribute" - self.fdel(obj) + self.fdel(instance) The :func:`property` builtin helps whenever a user interface has granted attribute access and then subsequent changes require the intervention of a @@ -245,10 +265,12 @@ ``Cell('b10').value``. Subsequent improvements to the program require the cell to be recalculated on every access; however, the programmer does not want to affect existing client code accessing the attribute directly. The solution is -to wrap access to the value attribute in a property data descriptor:: +to wrap access to the value attribute in a property data descriptor: + +.. code-block:: python class Cell(object): - . . . + # [...] def getvalue(self, obj): "Recalculate cell before returning value" self.recalc() @@ -273,20 +295,24 @@ binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class. In pure python, it works like -this:: +this: + +.. code-block:: python class Function(object): - . . . - def __get__(self, obj, objtype=None): - "Simulate func_descr_get() in Objects/funcobject.c" + # [...] + def __get__(self, instance, owner=None): + '''Simulate func_descr_get() in Objects/funcobject.c''' return types.MethodType(self, obj, objtype) -Running the interpreter shows how the function descriptor works in practice:: +Running the interpreter shows how the function descriptor works in practice: + +.. code-block:: python >>> class D(object): - def f(self, x): - return x - + ... def f(self, x): + ... return x + ... >>> d = D() >>> D.__dict__['f'] # Stored internally as a function @@ -353,39 +379,45 @@ ``Sample.erf(1.5) --> .9332``. Since staticmethods return the underlying function with no changes, the example -calls are unexciting:: +calls are unexciting: + +.. code-block:: python >>> class E(object): - def f(x): - print x - f = staticmethod(f) - - >>> print E.f(3) + ... def f(x): + ... print x + ... f = staticmethod(f) + ... + >>> E.f(3) 3 - >>> print E().f(3) + >>> E().f(3) 3 Using the non-data descriptor protocol, a pure Python version of -:func:`staticmethod` would look like this:: +:func:`staticmethod` would look like this: + +.. code-block:: python class StaticMethod(object): - "Emulate PyStaticMethod_Type() in Objects/funcobject.c" + '''Emulate PyStaticMethod_Type() in Objects/funcobject.c''' - def __init__(self, f): - self.f = f + def __init__(self, f): + self.f = f - def __get__(self, obj, objtype=None): - return self.f + def __get__(self, instance, owner=None): + return self.f Unlike static methods, class methods prepend the class reference to the argument list before calling the function. This format is the same -for whether the caller is an object or a class:: +for whether the caller is an object or a class: + +.. code-block:: python >>> class E(object): - def f(klass, x): - return klass.__name__, x - f = classmethod(f) - + ... def f(klass, x): + ... return klass.__name__, x + ... f = classmethod(f) + ... >>> print E.f(3) ('E', 3) >>> print E().f(3) @@ -396,36 +428,41 @@ reference and does not care about any underlying data. One use for classmethods is to create alternate class constructors. In Python 2.3, the classmethod :func:`dict.fromkeys` creates a new dictionary from a list of keys. The pure -Python equivalent is:: +Python equivalent is: + +.. code-block:: python class Dict: - . . . + # [...] def fromkeys(klass, iterable, value=None): - "Emulate dict_fromkeys() in Objects/dictobject.c" + '''Emulate dict_fromkeys() in Objects/dictobject.c''' d = klass() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys) -Now a new dictionary of unique keys can be constructed like this:: +Now a new dictionary of unique keys can be constructed like this: + +.. code-block:: python >>> Dict.fromkeys('abracadabra') {'a': None, 'r': None, 'b': None, 'c': None, 'd': None} Using the non-data descriptor protocol, a pure Python version of -:func:`classmethod` would look like this:: +:func:`classmethod` would look like this: + +.. code-block:: python class ClassMethod(object): - "Emulate PyClassMethod_Type() in Objects/funcobject.c" + '''Emulate PyClassMethod_Type() in Objects/funcobject.c''' def __init__(self, f): self.f = f - def __get__(self, obj, klass=None): - if klass is None: - klass = type(obj) + def __get__(self, instance, owner=None): + if owner is None: + owner = type(instance) def newfunc(*args): - return self.f(klass, *args) + return self.f(owner, *args) return newfunc -