Ticket #8250: trac_8250-classcall-classget.patch

File trac_8250-classcall-classget.patch, 9.9 KB (added by nthiery, 10 years ago)

Documentation improvements after phone discussion with Florent

  • sage/misc/classcall_metaclass.py

    # HG changeset patch
    # User Nicolas M. Thiery <nthiery@users.sf.net>
    # Date 1266064209 -3600
    # Node ID 63adb1e3d3ffd3e4f1e818e2541d41598e09ec8a
    # Parent  b5a1201923f7553e9903b3671bdf2312d49c972a
    #8250: Extend ClasscallMetaclass to allow for binding behavior
    * * *
    Improved doc + spurious copy-paste removed.
    
    diff --git a/sage/misc/classcall_metaclass.py b/sage/misc/classcall_metaclass.py
    a b from nested_class import NestedClassMeta 
    1313
    1414class ClasscallMetaclass(NestedClassMetaclass):
    1515    """
    16     A (trivial) metaclass for customizing class calls via a static method
     16    A metaclass providing support for special methods for classes.
    1717
    18     Let ``cls`` be a class in this metaclass, and consider a call of the form:
     18    From the Section ``Special method names`` of the Python Reference
     19    Manual: 'a class ``cls`` can implement certain operations on its
     20    instances that are invoked by special syntax (such as arithmetic
     21    operations or subscripting and slicing) by defining methods with
     22    special names'. The purpose of this metaclass is to allow the
     23    class ``cls`` to implement analogues of those special methods for
     24    the operations on the class itself.
    1925
    20         ``cls(<some arguments>)``
     26    Currently, the following special methods are supported:
    2127
    22     If ``cls`` defines a method ``__classcall_private__``, then
    23     this results in a call to::
     28     - ``.__classcall__`` (and ``.__classcall_private__``) for
     29       customizing ``cls(...)`` (analogue of ``.__call__``).
    2430
    25      - ``cls.__classcall_private__(cls, <some arguments>)``
     31     - ``.__classget__`` for customizing the binding behavior in
     32       ``foo.cls`` (analogue of ``.__get__``).
    2633
    27     Otherwise, if ``cls`` has a method ``__classcall__``, then instead
    28     the following is called:
     34    See the documentation of :meth:`.__classcall__`` and of
     35    :meth:`.__classget`` for the description of the respective protocol.
    2936
    30      - ``cls.__classcall__(cls, <some arguments>)``
    31 
    32     If neither of these two methods are implemented, then the standard
    33     ``type.__call__(cls, <some arguments>)`` is called, which in turn
    34     uses :meth:`__new__` and :meth:`__init__` as usual (see Section
    35     "Basic Customization" in the Python Reference Manual).
    36 
    37     See ``sage.misc.classcall_metaclass.ClasscallMetaclass.__call__?``
    38     for an example.
    39 
    40     Typical applications include the implementation of factories or of
    41     unique representation (see :class:`UniqueRepresentation`). Such
    42     features are traditionaly implemented by either using a wrapper
    43     function, or fiddling with :meth:`__new__`.
    44 
    45     The benefit, compared with fiddling directly with :meth:`__new__`
    46     is a clear separation of the three distinct roles:
    47 
    48      - :meth:`cls.__classcall__`: what cls(<...>) does
    49      - :meth:`cls.__new__`: memory allocation for a *new* instance
    50      - :meth:`cls.__init__`: initialization of a newly created instance
    51 
    52     The benefit, compared with using a wrapper function, is that the
    53     user interface has a single handle for the class::
    54 
    55         sage: x = Partition([3,2,2])
    56         sage: isinstance(x, Partition)          # todo: not implemented
    57 
    58     instead of::
    59 
    60         sage: isinstance(x, sage.combinat.partition.Partition_class)
    61         True
    62 
    63     Another difference is that :meth:`__classcall__` is inherited by
    64     subclasses, which may be desirable, or not. If not, one should
    65     instead define the method :meth:`__classcall_private__` which will
    66     not be called for subclasses. Specifically, if a class ``cls``
    67     defines both methods ``__classcall__`` and
    68     ``__classcall_private__`` then, for any subclass ``sub`` of ``cls``:
    69 
    70      - ``cls(<args>)`` will call ``cls.__classcall_private__(cls, <args>)``
    71      - ``sub(<args>)`` will call ``cls.__classcall__(sub, <args>)``
     37    TODO: find a good name for this metaclass.
    7238
    7339    AUTHORS:
    7440
    75      - Nicolas M. Thiery (2009-04) first release
    76      - Florent Hivert (2010-01) added __classcall_private__
     41     - Nicolas M. Thiery (2009-10) first implementation of __classcall__ and __classget__
     42     - Florent Hivert (2010-01): implementation of __classcall_private__, doc
    7743    """
    7844
     45    def __get__(cls, instance, owner):
     46        """
     47        This method implements instance binding behavior for nested classes.
     48
     49        Suppose that a class ``Outer`` contains a nested class ``cls`` which
     50        is an instance of this metaclass. For any object ``obj`` of ``cls``,
     51        this method implements a instance binding behavior for ``obj.cls`` by
     52        delegating it to ``cls.__classget__(Outer, obj, owner)`` if available.
     53        Otherwise, ``obj.cls`` results in ``cls``, as usual.
     54
     55        Similarily, a class binding as in ``Outer.cls`` is delegated
     56        to ``cls.__classget__(Outer, None, owner)`` if available and
     57        to ``cls`` if not.
     58
     59        For technical details, and in particular the description of
     60        the ``owner`` argument, see the Section ``Implementing
     61        Descriptors`` in the Python reference manual.
     62
     63        EXAMPLES:
     64
     65        We show how to implement a nested class ``Outer.Inner`` with a
     66        binding behavior, as if it was a method of ``Outer``: namely,
     67        for ``obj`` an instance of ``Outer``, calling
     68        ``obj.Inner(...)`` is equivalent to ``Outer.Inner(obj, ...)``::
     69
     70            sage: import functools
     71            sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
     72            sage: class Outer:
     73            ...       class Inner(object):
     74            ...           __metaclass__ = ClasscallMetaclass
     75            ...           @staticmethod
     76            ...           def __classget__(cls, instance, owner):
     77            ...               print "calling __classget__(%s, %s, %s)"%(
     78            ...                          cls, instance, owner)
     79            ...               if instance is None:
     80            ...                   return cls
     81            ...               return functools.partial(cls, instance)
     82            ...           def __init__(self, instance):
     83            ...               self.instance = instance
     84            sage: obj = Outer()
     85            sage: bar = obj.Inner()
     86            calling __classget__(<class '__main__.Inner'>, <__main__.Outer instance at 0x...>, __main__.Outer)
     87            sage: bar.instance == obj
     88            True
     89
     90        Calling ``Outer.Inner`` returns the (unbinded) class as usual::
     91
     92            sage: Inner = Outer.Inner
     93            calling __classget__(<class '__main__.Inner'>, None, __main__.Outer)
     94            sage: Inner
     95            <class '__main__.Inner'>
     96            sage: type(bar) is Inner
     97            True
     98
     99        ..warning:: Inner has to be a new style class (i.e. a subclass of object).
     100
     101        ..warning:: calling ``obj.Inner`` does no longer return a class::
     102
     103            sage: bind = obj.Inner
     104            calling __classget__(<class '__main__.Inner'>, <__main__.Outer instance at ...>, __main__.Outer)
     105            sage: bind
     106            <functools.partial object at 0x...>
     107        """
     108        if hasattr(cls, "__classget__"):
     109            return cls.__classget__(cls, instance, owner)
     110        else:
     111            return cls
     112
    79113    def __call__(cls, *args, **options):
    80114        """
    81115        This method implements ``cls(<some arguments>)``.
    82116
     117        Let ``cls`` be a class in ``ClasscallMetaclass``, and consider
     118        a call of the form:
     119
     120            ``cls(<some arguments>)``
     121
     122        If ``cls`` defines a method ``__classcall_private__``, then
     123        this results in a call to::
     124
     125         - ``cls.__classcall_private__(cls, <some arguments>)``
     126
     127        Otherwise, if ``cls`` has a method ``__classcall__``, then instead
     128        the following is called:
     129
     130         - ``cls.__classcall__(cls, <some arguments>)``
     131
     132        If neither of these two methods are implemented, then the standard
     133        ``type.__call__(cls, <some arguments>)`` is called, which in turn
     134        uses :meth:`__new__` and :meth:`__init__` as usual (see Section
     135        "Basic Customization" in the Python Reference Manual).
     136
    83137        EXAMPLES::
    84138
    85139            sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
    class ClasscallMetaclass(NestedClassMeta 
    150204            <__main__.Bar2 object at ...>
    151205
    152206
     207        ..rubric: Discussion
     208
     209        Typical applications include the implementation of factories or of
     210        unique representation (see :class:`UniqueRepresentation`). Such
     211        features are traditionaly implemented by either using a wrapper
     212        function, or fiddling with :meth:`__new__`.
     213
     214        The benefit, compared with fiddling directly with :meth:`__new__`
     215        is a clear separation of the three distinct roles:
     216
     217         - :meth:`cls.__classcall__`: what cls(<...>) does
     218         - :meth:`cls.__new__`: memory allocation for a *new* instance
     219         - :meth:`cls.__init__`: initialization of a newly created instance
     220
     221        The benefit, compared with using a wrapper function, is that the
     222        user interface has a single handle for the class::
     223
     224            sage: x = Partition([3,2,2])
     225            sage: isinstance(x, Partition)              # todo: not implemented
     226
     227        instead of::
     228
     229            sage: isinstance(x, sage.combinat.partition.Partition_class)
     230            True
     231
     232        Another difference is that :meth:`__classcall__` is inherited by
     233        subclasses, which may be desirable, or not. If not, one should
     234        instead define the method :meth:`__classcall_private__` which will
     235        not be called for subclasses. Specifically, if a class ``cls``
     236        defines both methods ``__classcall__`` and
     237        ``__classcall_private__`` then, for any subclass ``sub`` of ``cls``:
     238
     239         - ``cls(<args>)`` will call ``cls.__classcall_private__(cls, <args>)``
     240         - ``sub(<args>)`` will call ``cls.__classcall__(sub, <args>)``
     241
     242
     243
    153244        TESTS:
    154245
    155246        We check that the idiom ``method_name in cls.__dict__`` works