Path: blob/master/src/sage/misc/classcall_metaclass.pyx
8814 views
r"""1Special Methods for Classes23AUTHORS:45- Nicolas M. Thiery (2009-2011) implementation of6``__classcall__``, ``__classget__``, ``__classcontains__``;7- Florent Hivert (2010-2012): implementation of ``__classcall_private__``,8documentation, Cythonization and optimization.9"""10#*****************************************************************************11# Copyright (C) 2009 Nicolas M. Thiery <nthiery at users.sf.net>12# Copyright (C) 2010-2012 Florent Hivert <Florent.Hivert at lri.fr>13#14# Distributed under the terms of the GNU General Public License (GPL)15# http://www.gnu.org/licenses/16#*****************************************************************************1718include 'sage/ext/python.pxi'1920cdef extern from "Python.h":21ctypedef PyObject *(*callfunc)(type, object, object) except NULL22ctypedef struct PyTypeObject_call "PyTypeObject":23callfunc tp_call # needed to call type.__call__ at very high speed.24cdef PyTypeObject_call PyType_Type # Python's type2526__all__ = ['ClasscallMetaclass', 'typecall', 'timeCall']2728cdef class ClasscallMetaclass(NestedClassMetaclass):29"""30A metaclass providing support for special methods for classes.3132From the Section :python:`Special method names33<reference/datamodel.html#special-method-names>` of the Python Reference34Manual:3536\`a class ``cls`` can implement certain operations on its instances37that are invoked by special syntax (such as arithmetic operations or38subscripting and slicing) by defining methods with special39names\'.4041The purpose of this metaclass is to allow for the class ``cls`` to42implement analogues of those special methods for the operations on the43class itself.4445Currently, the following special methods are supported:4647- ``.__classcall__`` (and ``.__classcall_private__``) for48customizing ``cls(...)`` (analogue of ``.__call__``).4950- ``.__classcontains__`` for customizing membership testing51``x in cls`` (analogue of ``.__contains__``).5253- ``.__classget__`` for customizing the binding behavior in54``foo.cls`` (analogue of ``.__get__``).5556See the documentation of :meth:`__call__` and of :meth:`__get__`57and :meth:`__contains__` for the description of the respective58protocols.5960.. WARNING::6162For technical reasons, ``__classcall__``,63``__classcall_private__``, ``__classcontains__``, and64``__classget__`` must be defined as :func:`staticmethod`'s,65even though they receive the class itself as their first66argument.6768.. WARNING::6970For efficiency reasons, the resolution for the special methods71is done once for all, upon creation of the class. Thus, later72dynamic changes to those methods are ignored. But see also73:meth:`_set_classcall`.7475``ClasscallMetaclass`` is an extension of the base :class:`type`.7677TODO: find a good name for this metaclass.7879TESTS::8081sage: PerfectMatchings(2).list()82[[(1, 2)]]8384.. note::8586If a class is put in this metaclass it automatically becomes a87new-style class::8889sage: from sage.misc.classcall_metaclass import ClasscallMetaclass90sage: class Foo:91... __metaclass__ = ClasscallMetaclass92sage: x = Foo(); x93<__main__.Foo object at 0x...>94sage: issubclass(Foo, object)95True96sage: isinstance(Foo, type)97True98"""99_included_private_doc_ = ['__call__', '__contains__', '__get__']100101def __cinit__(self, *args, **opts):102r"""103TESTS::104105sage: from sage.misc.classcall_metaclass import ClasscallMetaclass106sage: class FOO(object):107... __metaclass__ = ClasscallMetaclass108sage: isinstance(FOO, ClasscallMetaclass) # indirect doctest109True110"""111if '__classcall_private__' in self.__dict__:112self.classcall = self.__classcall_private__113elif hasattr(self, "__classcall__"):114self.classcall = self.__classcall__115else:116self.classcall = None117118self.classcontains = getattr(self, "__classcontains__", None)119self.classget = getattr(self, "__classget__", None)120121def _set_classcall(cls, function):122r"""123Change dynamically the classcall function for this class124125EXAMPLES::126127sage: from sage.misc.classcall_metaclass import ClasscallMetaclass128sage: class FOO(object):129... __metaclass__ = ClasscallMetaclass130sage: FOO()131<__main__.FOO object at ...>132133For efficiency reason, the resolution of the ``__classcall__``134method is done once for all, upon creation of the class. Thus,135later dynamic changes to this method are ignored by FOO::136137sage: FOO.__classcall__ = ConstantFunction(1)138sage: FOO()139<__main__.FOO object at ...>140141but not by subclasses created later on::142143sage: class BAR(FOO): pass144sage: BAR()1451146147To update the ``classcall`` special function for FOO, one148should use this setter::149150sage: FOO._set_classcall(ConstantFunction(2))151sage: FOO()1522153154Note that it has no influence on subclasses::155156sage: class BAR(FOO): pass157sage: BAR()1581159"""160cls.classcall = function161162def __call__(cls, *args, **opts):163r"""164This method implements ``cls(<some arguments>)``.165166Let ``cls`` be a class in :class:`ClasscallMetaclass`, and167consider a call of the form::168169cls(<some arguments>)170171- If ``cls`` defines a method ``__classcall_private__``, then172this results in a call to::173174cls.__classcall_private__(cls, <some arguments>)175176- Otherwise, if ``cls`` has a method ``__classcall__``, then instead177the following is called::178179cls.__classcall__(cls, <some arguments>)180181- If neither of these two methods are implemented, then the standard182``type.__call__(cls, <some arguments>)`` is called, which in turn183uses :meth:`~object.__new__` and :meth:`~object.__init__` as usual184(see Section :python:`Basic Customization185<reference/datamodel.html#basic-customization>` in the Python186Reference Manual).187188.. warning:: for technical reasons, ``__classcall__`` must be189defined as a :func:`staticmethod`, even though it receives190the class itself as its first argument.191192EXAMPLES::193194sage: from sage.misc.classcall_metaclass import ClasscallMetaclass195sage: class Foo(object):196... __metaclass__ = ClasscallMetaclass197... @staticmethod198... def __classcall__(cls):199... print "calling classcall"200... return type.__call__(cls)201... def __new__(cls):202... print "calling new"203... return super(Foo, cls).__new__(cls)204... def __init__(self):205... print "calling init"206sage: Foo()207calling classcall208calling new209calling init210<__main__.Foo object at ...>211212This behavior is inherited::213214sage: class Bar(Foo): pass215sage: Bar()216calling classcall217calling new218calling init219<__main__.Bar object at ...>220221We now show the usage of ``__classcall_private__``::222223sage: class FooNoInherits(object):224... __metaclass__ = ClasscallMetaclass225... @staticmethod226... def __classcall_private__(cls):227... print "calling private classcall"228... return type.__call__(cls)229...230sage: FooNoInherits()231calling private classcall232<__main__.FooNoInherits object at ...>233234Here the behavior is not inherited::235236sage: class BarNoInherits(FooNoInherits): pass237sage: BarNoInherits()238<__main__.BarNoInherits object at ...>239240We now show the usage of both::241242sage: class Foo2(object):243... __metaclass__ = ClasscallMetaclass244... @staticmethod245... def __classcall_private__(cls):246... print "calling private classcall"247... return type.__call__(cls)248... @staticmethod249... def __classcall__(cls):250... print "calling classcall with %s"%cls251... return type.__call__(cls)252...253sage: Foo2()254calling private classcall255<__main__.Foo2 object at ...>256257sage: class Bar2(Foo2): pass258sage: Bar2()259calling classcall with <class '__main__.Bar2'>260<__main__.Bar2 object at ...>261262263.. rubric:: Discussion264265Typical applications include the implementation of factories or of266unique representation (see :class:`UniqueRepresentation`). Such267features are traditionaly implemented by either using a wrapper268function, or fiddling with :meth:`~object.__new__`.269270The benefit, compared with fiddling directly with271:meth:`~object.__new__` is a clear separation of the three distinct272roles:273274- ``cls.__classcall__``: what ``cls(<...>)`` does275- ``cls.__new__``: memory allocation for a *new* instance276- ``cls.__init__``: initialization of a newly created instance277278The benefit, compared with using a wrapper function, is that the279user interface has a single handle for the class::280281sage: x = Partition([3,2,2])282sage: isinstance(x, Partition) # todo: not implemented283284instead of::285286sage: isinstance(x, sage.combinat.partition.Partition)287True288289Another difference is that ``__classcall__`` is inherited by290subclasses, which may be desirable, or not. If not, one should291instead define the method ``__classcall_private__`` which will292not be called for subclasses. Specifically, if a class ``cls``293defines both methods ``__classcall__`` and294``__classcall_private__`` then, for any subclass ``sub`` of ``cls``:295296- ``cls(<args>)`` will call ``cls.__classcall_private__(cls, <args>)``297- ``sub(<args>)`` will call ``cls.__classcall__(sub, <args>)``298299300TESTS:301302We check that the idiom ``method_name in cls.__dict__`` works303for extension types::304305sage: "_sage_" in SageObject.__dict__, "_sage_" in Parent.__dict__306(True, False)307308We check for memory leaks::309310sage: class NOCALL(object):311... __metaclass__ = ClasscallMetaclass312... pass313sage: sys.getrefcount(NOCALL())3141315316We check that exceptions are correctly handled::317318sage: class Exc(object):319... __metaclass__ = ClasscallMetaclass320... @staticmethod321... def __classcall__(cls):322... raise ValueError, "Calling classcall"323sage: Exc()324Traceback (most recent call last):325...326ValueError: Calling classcall327"""328if cls.classcall is not None:329return cls.classcall(cls, *args, **opts)330else:331###########################################################332# This is type.__call__(cls, *args, **opts) twice faster333# Using the following test code:334#335# sage: class NOCALL(object):336# ... __metaclass__ = ClasscallMetaclass337# ... pass338#339# with type.__call__ :340# sage: %timeit [NOCALL() for i in range(10000)]341# 125 loops, best of 3: 3.59 ms per loop342# with this ugly C call:343# sage: %timeit [NOCALL() for i in range(10000)]344# 125 loops, best of 3: 1.76 ms per loop345#346# Note: compared to a standard void Python class the slow down is347# only 5%:348# sage: %timeit [Rien() for i in range(10000)]349# 125 loops, best of 3: 1.7 ms per loop350res = <object> PyType_Type.tp_call(cls, args, opts)351Py_XDECREF(<PyObject*>res) # During the cast to <object> Cython did INCREF(res)352return res353354def __get__(cls, instance, owner):355r"""356This method implements instance binding behavior for nested classes.357358Suppose that a class ``Outer`` contains a nested class ``cls`` which359is an instance of this metaclass. For any object ``obj`` of ``cls``,360this method implements a instance binding behavior for ``obj.cls`` by361delegating it to ``cls.__classget__(Outer, obj, owner)`` if available.362Otherwise, ``obj.cls`` results in ``cls``, as usual.363364Similarily, a class binding as in ``Outer.cls`` is delegated365to ``cls.__classget__(Outer, None, owner)`` if available and366to ``cls`` if not.367368.. warning:: for technical reasons, ``__classget__`` must be369defined as a :func:`staticmethod`, even though it receives370the class itself as its first argument.371372For technical details, and in particular the description of the373``owner`` argument, see the Section :python:`Implementing Descriptor374<reference/datamodel.html#implementing-descriptors>` in the Python375reference manual.376377EXAMPLES:378379We show how to implement a nested class ``Outer.Inner`` with a380binding behavior, as if it was a method of ``Outer``: namely,381for ``obj`` an instance of ``Outer``, calling382``obj.Inner(...)`` is equivalent to ``Outer.Inner(obj, ...)``::383384sage: import functools385sage: from sage.misc.nested_class import NestedClassMetaclass386sage: from sage.misc.classcall_metaclass import ClasscallMetaclass387sage: class Outer:388... __metaclass__ = NestedClassMetaclass # workaround for python pickling bug389...390... class Inner(object):391... __metaclass__ = ClasscallMetaclass392... @staticmethod393... def __classget__(cls, instance, owner):394... print "calling __classget__(%s, %s, %s)"%(395... cls, instance, owner)396... if instance is None:397... return cls398... return functools.partial(cls, instance)399... def __init__(self, instance):400... self.instance = instance401sage: obj = Outer()402sage: bar = obj.Inner()403calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)404sage: bar.instance == obj405True406407Calling ``Outer.Inner`` returns the (unbinded) class as usual::408409sage: Inner = Outer.Inner410calling __classget__(<class '__main__.Outer.Inner'>, None, <class '__main__.Outer'>)411sage: Inner412<class '__main__.Outer.Inner'>413sage: type(bar) is Inner414True415416.. warning:: Inner has to be a new style class (i.e. a subclass of object).417418.. warning::419420calling ``obj.Inner`` does no longer return a class::421422sage: bind = obj.Inner423calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)424sage: bind425<functools.partial object at 0x...>426"""427if cls.classget:428return cls.classget(cls, instance, owner)429else:430return cls431432def __contains__(cls, x):433r"""434This method implements membership testing for a class435436Let ``cls`` be a class in :class:`ClasscallMetaclass`, and consider437a call of the form::438439x in cls440441If ``cls`` defines a method ``__classcontains__``, then this442results in a call to::443444cls.__classcontains__(cls, x)445446.. warning:: for technical reasons, ``__classcontains__`` must447be defined as a :func:`staticmethod`, even though it448receives the class itself as its first argument.449450EXAMPLES:451452We construct a class which implements membership testing, and453which contains ``1`` and no other x::454455sage: from sage.misc.classcall_metaclass import ClasscallMetaclass456sage: class Foo(object):457... __metaclass__ = ClasscallMetaclass458... @staticmethod459... def __classcontains__(cls, x):460... return x == 1461sage: 1 in Foo462True463sage: 2 in Foo464False465466We now check that for a class without ``__classcontains__``467method, we emulate the usual error message::468469sage: from sage.misc.classcall_metaclass import ClasscallMetaclass470sage: class Bar(object):471... __metaclass__ = ClasscallMetaclass472sage: 1 in Bar473Traceback (most recent call last):474...475TypeError: argument of type 'type' is not iterable476"""477if cls.classcontains:478return cls.classcontains(cls, x)479else:480return x in object481482483def typecall(type cls, *args, **opts):484r"""485Object construction486487This is a faster equivalent to ``type.__call__(cls, <some arguments>)``.488489INPUT:490491- ``cls`` -- the class used for constructing the instance. It must be492a builtin type or a new style class (inheriting from :class:`object`).493494EXAMPLES::495496sage: from sage.misc.classcall_metaclass import typecall497sage: class Foo(object): pass498sage: typecall(Foo)499<__main__.Foo object at 0x...>500sage: typecall(list)501[]502sage: typecall(Integer, 2)5032504505.. warning::506507:func:`typecall` doesn't work for old style class (not inheriting from508:class:`object`)::509510sage: class Bar: pass511sage: typecall(Bar)512Traceback (most recent call last):513...514TypeError: Argument 'cls' has incorrect type (expected type, got classobj)515"""516# See remarks in ClasscallMetaclass.__call__(cls, *args, **opts) for speed.517res = <object> PyType_Type.tp_call(cls, args, opts)518Py_XDECREF(<PyObject*>res) # During the cast to <object> Cython did INCREF(res)519return res520521# Class for timing::522523class CRef(object):524def __init__(self, i):525"""526TESTS::527528sage: from sage.misc.classcall_metaclass import CRef529sage: P = CRef(2); P.i5303531"""532self.i = i+1533534class C2(object):535__metaclass__ = ClasscallMetaclass536def __init__(self, i):537"""538TESTS::539540sage: from sage.misc.classcall_metaclass import C2541sage: P = C2(2); P.i5423543"""544self.i = i+1545546class C3(object, metaclass = ClasscallMetaclass):547def __init__(self, i):548"""549TESTS::550551sage: from sage.misc.classcall_metaclass import C3552sage: P = C3(2); P.i5533554"""555self.i = i+1556557class C2C(object):558__metaclass__ = ClasscallMetaclass559@staticmethod560def __classcall__(cls, i):561"""562TESTS::563564sage: from sage.misc.classcall_metaclass import C2C565sage: C2C(2)5663567"""568return i+1569570def timeCall(T, int n, *args):571r"""572We illustrate some timing when using the classcall mechanism.573574EXAMPLES::575576sage: from sage.misc.classcall_metaclass import (577... ClasscallMetaclass, CRef, C2, C3, C2C, timeCall)578sage: timeCall(object, 1000)579580For reference let construct basic objects and a basic Python class::581582sage: %timeit timeCall(object, 1000) # not tested583625 loops, best of 3: 41.4 µs per loop584585sage: i1 = int(1); i3 = int(3) # don't use Sage's Integer586sage: class PRef(object):587... def __init__(self, i):588... self.i = i+i1589590For a Python class, compared to the reference class there is a 10%591overhead in using :class:`ClasscallMetaclass` if there is no classcall592defined::593594sage: class P(object):595... __metaclass__ = ClasscallMetaclass596... def __init__(self, i):597... self.i = i+i1598599sage: %timeit timeCall(PRef, 1000, i3) # not tested600625 loops, best of 3: 420 µs per loop601sage: %timeit timeCall(P, 1000, i3) # not tested602625 loops, best of 3: 458 µs per loop603604For a Cython class (not cdef since they doesn't allows metaclasses), the605overhead is a little larger::606607sage: %timeit timeCall(CRef, 1000, i3) # not tested608625 loops, best of 3: 266 µs per loop609sage: %timeit timeCall(C2, 1000, i3) # not tested610625 loops, best of 3: 298 µs per loop611612Let's now compare when there is a classcall defined::613614sage: class PC(object):615... __metaclass__ = ClasscallMetaclass616... @staticmethod617... def __classcall__(cls, i):618... return i+i1619sage: %timeit timeCall(C2C, 1000, i3) # not tested620625 loops, best of 3: 148 µs per loop621sage: %timeit timeCall(PC, 1000, i3) # not tested622625 loops, best of 3: 289 µs per loop623624The overhead of the indirection ( ``C(...) ->625ClasscallMetaclass.__call__(...) -> C.__classcall__(...)``) is626unfortunately quite large in this case (two method calls instead of627one). In reasonable usecases, the overhead should be mostly hidden by the628computations inside the classcall::629630sage: %timeit timeCall(C2C.__classcall__, 1000, C2C, i3) # not tested631625 loops, best of 3: 33 µs per loop632sage: %timeit timeCall(PC.__classcall__, 1000, PC, i3) # not tested633625 loops, best of 3: 131 µs per loop634635Finally, there is no significant difference between Cython's V2 and V3636syntax for metaclass::637638sage: %timeit timeCall(C2, 1000, i3) # not tested639625 loops, best of 3: 330 µs per loop640sage: %timeit timeCall(C3, 1000, i3) # not tested641625 loops, best of 3: 328 µs per loop642"""643cdef int i644for 0<=i<n:645T(*args)646647648