Dynamic documentation for instances of classes - Utilities

Skip to content
Back to top
View this page
Edit this page

# Dynamic documentation for instances of classes

The functionality in this module allows to define specific docstrings of instances of a class, which are different from the class docstring. A typical use case is given by cached methods: the documentation of a cached method should not be the documentation of the class `CachedMethod`; it should be the documentation of the underlying method.

In order to use this, define a class docstring as usual. Also define a method `def _instancedoc_(self)` which should return the docstring of the instance `self`. Finally, add the decorator `@instancedoc` to the class.

Warning

Since the `__doc__` attribute is never inherited, the decorator `@instancedoc` must be added to all subclasses of the class defining `_instancedoc_`. Doing it on the base class is not sufficient.

EXAMPLES:

Sage

    sage: from sage.misc.instancedoc import instancedoc
    sage: @instancedoc
    ....: class X():
    ....:     "Class docstring"
    ....:     def _instancedoc_(self):
    ....:         return "Instance docstring"
    sage: X.__doc__
    'Class docstring'
    sage: X().__doc__
    'Instance docstring'

Python

    >>> from sage.all import *
    >>> from sage.misc.instancedoc import instancedoc
    >>> @instancedoc
    ... class X():
    ...     "Class docstring"
    ...     def _instancedoc_(self):
    ...         return "Instance docstring"
    >>> X.__doc__
    'Class docstring'
    >>> X().__doc__
    'Instance docstring'

For a Cython `cdef class`, a decorator cannot be used. Instead, call `instancedoc()` as a function after defining the class:

Sage

    sage: cython(                                                                       # needs sage.misc.cython
    ....: '''
    ....: from sage.misc.instancedoc import instancedoc
    ....: cdef class Y:
    ....:     "Class docstring"
    ....:     def _instancedoc_(self):
    ....:         return "Instance docstring"
    ....: instancedoc(Y)
    ....: ''')
    sage: Y.__doc__                                                                     # needs sage.misc.cython
    'File:...\nClass docstring'
    sage: Y().__doc__                                                                   # needs sage.misc.cython
    'Instance docstring'

[Python]

    >>> from sage.all import *
    >>> cython(                                                                       # needs sage.misc.cython
    ... '''
    ... from sage.misc.instancedoc import instancedoc
    ... cdef class Y:
    ...     "Class docstring"
    ...     def _instancedoc_(self):
    ...         return "Instance docstring"
    ... instancedoc(Y)
    ... ''')
    >>> Y.__doc__                                                                     # needs sage.misc.cython
    'File:...\nClass docstring'
    >>> Y().__doc__                                                                   # needs sage.misc.cython
    'Instance docstring'

One can still add a custom `__doc__` attribute on a particular instance:

Sage

    sage: obj = X()
    sage: obj.__doc__ = "Very special doc"
    sage: print(obj.__doc__)
    Very special doc

Python

    >>> from sage.all import *
    >>> obj = X()
    >>> obj.__doc__ = "Very special doc"
    >>> print(obj.__doc__)
    Very special doc

This normally does not work on extension types:

Sage

    sage: Y().__doc__ = "Very special doc"                                              # needs sage.misc.cython
    Traceback (most recent call last):
    ...
    AttributeError: attribute '__doc__' of 'Y' objects is not writable

[Python]

    >>> from sage.all import *
    >>> Y().__doc__ = "Very special doc"                                              # needs sage.misc.cython
    Traceback (most recent call last):
    ...
    AttributeError: attribute '__doc__' of 'Y' objects is not writable

This is an example involving a metaclass, where the instances are classes. In this case, the `_instancedoc_` from the metaclass is only used if the instance of the metaclass (the class) does not have a docstring:

Sage

    sage: @instancedoc
    ....: class Meta(type):
    ....:     "Metaclass doc"
    ....:     def _instancedoc_(self):
    ....:         return "Docstring for {}".format(self)
    sage: class T(metaclass=Meta):
    ....:     pass
    sage: print(T.__doc__)
    Docstring for <class '__main__.T'>
    sage: class U(metaclass=Meta):
    ....:     "Special doc for U"
    sage: print(U.__doc__)
    Special doc for U

Python

    >>> from sage.all import *
    >>> @instancedoc
    ... class Meta(type):
    ...     "Metaclass doc"
    ...     def _instancedoc_(self):
    ...         return "Docstring for {}".format(self)
    >>> class T(metaclass=Meta):
    ...     pass
    >>> print(T.__doc__)
    Docstring for <class '__main__.T'>
    >>> class U(metaclass=Meta):
    ...     "Special doc for U"
    >>> print(U.__doc__)
    Special doc for U

class sage.misc.instancedoc.InstanceDocDescriptor

  Bases: `object`

  Descriptor for dynamic documentation, to be installed as the `__doc__` attribute.

  INPUT:

  - `classdoc` – string; class documentation

  - `instancedoc` – (method) documentation for an instance

  - `attr` – string (default: `__doc__`); attribute name to use for custom docstring on the instance

  EXAMPLES:

  Sage

      sage: from sage.misc.instancedoc import InstanceDocDescriptor
      sage: def instancedoc(self):
      ....:     return "Instance doc"
      sage: docattr = InstanceDocDescriptor("Class doc", instancedoc)
      sage: class Z():
      ....:     __doc__ = InstanceDocDescriptor("Class doc", instancedoc)
      sage: Z.__doc__
      'Class doc'
      sage: Z().__doc__
      'Instance doc'

  Python

      >>> from sage.all import *
      >>> from sage.misc.instancedoc import InstanceDocDescriptor
      >>> def instancedoc(self):
      ...     return "Instance doc"
      >>> docattr = InstanceDocDescriptor("Class doc", instancedoc)
      >>> class Z():
      ...     __doc__ = InstanceDocDescriptor("Class doc", instancedoc)
      >>> Z.__doc__
      'Class doc'
      >>> Z().__doc__
      'Instance doc'

  We can still override the `__doc__` attribute of the instance:

  Sage

      sage: obj = Z()
      sage: obj.__doc__ = "Custom doc"
      sage: obj.__doc__
      'Custom doc'
      sage: del obj.__doc__
      sage: obj.__doc__
      'Instance doc'

  [Python]

      >>> from sage.all import *
      >>> obj = Z()
      >>> obj.__doc__ = "Custom doc"
      >>> obj.__doc__
      'Custom doc'
      >>> del obj.__doc__
      >>> obj.__doc__
      'Instance doc'

sage.misc.instancedoc.instancedoc(cls)

  Add support for `_instancedoc_` to the class `cls`.

  Typically, this will be used as decorator.

  INPUT:

  - `cls` – a new-style class

  OUTPUT: `cls`

  Warning

  `instancedoc` mutates the given class. So you are not supposed to use it as `newcls = instancedoc(cls)` because that would mutate `cls` (and `newcls` would be the same object as `cls`)