Special Methods for Classes - Utilities

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

# Special Methods for Classes

AUTHORS:

- Nicolas M. Thiery (2009-2011) implementation of `__classcall__`, `__classget__`, `__classcontains__`;

- Florent Hivert (2010-2012): implementation of `__classcall_private__`, documentation, Cythonization and optimization.

class sage.misc.classcall_metaclass.ClasscallMetaclass

  Bases: `NestedClassMetaclass`

  A metaclass providing support for special methods for classes.

  From the Section Special method names of the Python Reference Manual:

  > `a class `cls` can implement certain operations on its instances that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names'.

  The purpose of this metaclass is to allow for the class `cls` to implement analogues of those special methods for the operations on the class itself.

  Currently, the following special methods are supported:

  > - `.__classcall__` (and `.__classcall_private__`) for customizing `cls(...)` (analogue of `.__call__`).

  > - `.__classcontains__` for customizing membership testing `x in cls` (analogue of `.__contains__`).

  > - `.__classget__` for customizing the binding behavior in `foo.cls` (analogue of `.__get__`).

  See the documentation of `__call__()` and of `__get__()` and `__contains__()` for the description of the respective protocols.

  Warning

  For technical reasons, `__classcall__`, `__classcall_private__`, `__classcontains__`, and `__classget__` must be defined as `staticmethod()`’s, even though they receive the class itself as their first argument.

  Warning

  For efficiency reasons, the resolution for the special methods is done once for all, upon creation of the class. Thus, later dynamic changes to those methods are ignored. But see also `_set_classcall()`.

  `ClasscallMetaclass` is an extension of the base `type`.

  Todo

  find a good name for this metaclass.

  Note

  If a class is put in this metaclass it automatically becomes a new-style class:

  Sage

      sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
      sage: class Foo(metaclass=ClasscallMetaclass): pass
      sage: x = Foo(); x
      <__main__.Foo object at 0x...>
      sage: issubclass(Foo, object)
      True
      sage: isinstance(Foo, type)
      True

  Python

      >>> from sage.all import *
      >>> from sage.misc.classcall_metaclass import ClasscallMetaclass
      >>> class Foo(metaclass=ClasscallMetaclass): pass
      >>> x = Foo(); x
      <__main__.Foo object at 0x...>
      >>> issubclass(Foo, object)
      True
      >>> isinstance(Foo, type)
      True

sage.misc.classcall_metaclass.timeCall(T, n, *args)

  We illustrate some timing when using the classcall mechanism.

  EXAMPLES:

  Sage

      sage: from sage.misc.classcall_metaclass import (
      ....:     ClasscallMetaclass, CRef, C2, C3, C2C, timeCall)
      sage: timeCall(object, 1000)

  Python

      >>> from sage.all import *
      >>> from sage.misc.classcall_metaclass import (
      ...     ClasscallMetaclass, CRef, C2, C3, C2C, timeCall)
      >>> timeCall(object, Integer(1000))

  For reference let construct basic objects and a basic Python class:

  Sage

      sage: %timeit timeCall(object, 1000)   # not tested
      625 loops, best of 3: 41.4 µs per loop

      sage: i1 = int(1); i3 = int(3) # don't use Sage's Integer
      sage: class PRef():
      ....:     def __init__(self, i):
      ....:         self.i = i+i1

  [Python]

      >>> from sage.all import *
      >>> %timeit timeCall(object, Integer(1000))   # not tested
      625 loops, best of 3: 41.4 µs per loop

      >>> i1 = int(Integer(1)); i3 = int(Integer(3)) # don't use Sage's Integer
      >>> class PRef():
      ...     def __init__(self, i):
      ...         self.i = i+i1

  For a Python class, compared to the reference class there is a 10% overhead in using `ClasscallMetaclass` if there is no classcall defined:

  Sage

      sage: class P(metaclass=ClasscallMetaclass):
      ....:     def __init__(self, i):
      ....:         self.i = i+i1

      sage: %timeit timeCall(PRef, 1000, i3)   # not tested
      625 loops, best of 3: 420 µs per loop
      sage: %timeit timeCall(P, 1000, i3)      # not tested
      625 loops, best of 3: 458 µs per loop

  Python

      >>> from sage.all import *
      >>> class P(metaclass=ClasscallMetaclass):
      ...     def __init__(self, i):
      ...         self.i = i+i1

      >>> %timeit timeCall(PRef, Integer(1000), i3)   # not tested
      625 loops, best of 3: 420 µs per loop
      >>> %timeit timeCall(P, Integer(1000), i3)      # not tested
      625 loops, best of 3: 458 µs per loop

  For a Cython class (not cdef since they doesn’t allows metaclasses), the overhead is a little larger:

  Sage

      sage: %timeit timeCall(CRef, 1000, i3)   # not tested
      625 loops, best of 3: 266 µs per loop
      sage: %timeit timeCall(C2, 1000, i3)     # not tested
      625 loops, best of 3: 298 µs per loop

  [Python]

      >>> from sage.all import *
      >>> %timeit timeCall(CRef, Integer(1000), i3)   # not tested
      625 loops, best of 3: 266 µs per loop
      >>> %timeit timeCall(C2, Integer(1000), i3)     # not tested
      625 loops, best of 3: 298 µs per loop

  Let’s now compare when there is a classcall defined:

  Sage

      sage: class PC(object, metaclass=ClasscallMetaclass):
      ....:     @staticmethod
      ....:     def __classcall__(cls, i):
      ....:         return i+i1
      sage: %timeit timeCall(C2C, 1000, i3)   # not tested
      625 loops, best of 3: 148 µs per loop
      sage: %timeit timeCall(PC, 1000, i3)    # not tested
      625 loops, best of 3: 289 µs per loop

  Python

      >>> from sage.all import *
      >>> class PC(object, metaclass=ClasscallMetaclass):
      ...     @staticmethod
      ...     def __classcall__(cls, i):
      ...         return i+i1
      >>> %timeit timeCall(C2C, Integer(1000), i3)   # not tested
      625 loops, best of 3: 148 µs per loop
      >>> %timeit timeCall(PC, Integer(1000), i3)    # not tested
      625 loops, best of 3: 289 µs per loop

  The overhead of the indirection ( `C(...) -> ClasscallMetaclass.__call__(...) -> C.__classcall__(...)`) is unfortunately quite large in this case (two method calls instead of one). In reasonable usecases, the overhead should be mostly hidden by the computations inside the classcall:

  Sage

      sage: %timeit timeCall(C2C.__classcall__, 1000, C2C, i3)  # not tested
      625 loops, best of 3: 33 µs per loop
      sage: %timeit timeCall(PC.__classcall__, 1000, PC, i3)    # not tested
      625 loops, best of 3: 131 µs per loop

  [Python]

      >>> from sage.all import *
      >>> %timeit timeCall(C2C.__classcall__, Integer(1000), C2C, i3)  # not tested
      625 loops, best of 3: 33 µs per loop
      >>> %timeit timeCall(PC.__classcall__, Integer(1000), PC, i3)    # not tested
      625 loops, best of 3: 131 µs per loop

  Finally, there is no significant difference between Cython’s V2 and V3 syntax for metaclass:

  Sage

      sage: %timeit timeCall(C2, 1000, i3)   # not tested
      625 loops, best of 3: 330 µs per loop
      sage: %timeit timeCall(C3, 1000, i3)   # not tested
      625 loops, best of 3: 328 µs per loop

  Python

      >>> from sage.all import *
      >>> %timeit timeCall(C2, Integer(1000), i3)   # not tested
      625 loops, best of 3: 330 µs per loop
      >>> %timeit timeCall(C3, Integer(1000), i3)   # not tested
      625 loops, best of 3: 328 µs per loop

sage.misc.classcall_metaclass.typecall(cls, *args, **kwds)

  Object construction.

  This is a faster equivalent to `type.__call__(cls, <some arguments>)`.

  INPUT:

  - `cls` – the class used for constructing the instance; it must be a builtin type or a new style class (inheriting from `object`)

  EXAMPLES:

  Sage

      sage: from sage.misc.classcall_metaclass import typecall
      sage: class Foo(): pass
      sage: typecall(Foo)
      <__main__.Foo object at 0x...>
      sage: typecall(list)
      []
      sage: typecall(Integer, 2)
      2

  Python

      >>> from sage.all import *
      >>> from sage.misc.classcall_metaclass import typecall
      >>> class Foo(): pass
      >>> typecall(Foo)
      <__main__.Foo object at 0x...>
      >>> typecall(list)
      []
      >>> typecall(Integer, Integer(2))
      2