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[source]

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__(), __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: 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
>>> 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
__call__(*args, **kwds)[source]

This method implements cls(<some arguments>).

Let cls be a class in ClasscallMetaclass, and consider a call of the form:

cls(<some arguments>)
  • If cls defines a method __classcall_private__, then this results in a call to:

    cls.__classcall_private__(cls, <some arguments>)
    
  • Otherwise, if cls has a method __classcall__, then instead the following is called:

    cls.__classcall__(cls, <some arguments>)
    
  • If neither of these two methods are implemented, then the standard type.__call__(cls, <some arguments>) is called, which in turn uses __new__() and __init__() as usual (see Section Basic Customization in the Python Reference Manual).

Warning

for technical reasons, __classcall__ must be defined as a staticmethod(), even though it receives the class itself as its first argument.

EXAMPLES:

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
sage: class Foo(metaclass=ClasscallMetaclass):
....:     @staticmethod
....:     def __classcall__(cls):
....:         print("calling classcall")
....:         return type.__call__(cls)
....:     def __new__(cls):
....:         print("calling new")
....:         return super(Foo, cls).__new__(cls)
....:     def __init__(self):
....:         print("calling init")
sage: Foo()
calling classcall
calling new
calling init
<__main__.Foo object at ...>
>>> from sage.all import *
>>> from sage.misc.classcall_metaclass import ClasscallMetaclass
>>> class Foo(metaclass=ClasscallMetaclass):
...     @staticmethod
...     def __classcall__(cls):
...         print("calling classcall")
...         return type.__call__(cls)
...     def __new__(cls):
...         print("calling new")
...         return super(Foo, cls).__new__(cls)
...     def __init__(self):
...         print("calling init")
>>> Foo()
calling classcall
calling new
calling init
<__main__.Foo object at ...>

This behavior is inherited:

sage: class Bar(Foo): pass
sage: Bar()
calling classcall
calling new
calling init
<__main__.Bar object at ...>
[Python]
>>> from sage.all import *
>>> class Bar(Foo): pass
>>> Bar()
calling classcall
calling new
calling init
<__main__.Bar object at ...>

We now show the usage of __classcall_private__:

sage: class FooNoInherits(object, metaclass=ClasscallMetaclass):
....:     @staticmethod
....:     def __classcall_private__(cls):
....:         print("calling private classcall")
....:         return type.__call__(cls)
sage: FooNoInherits()
calling private classcall
<__main__.FooNoInherits object at ...>
>>> from sage.all import *
>>> class FooNoInherits(object, metaclass=ClasscallMetaclass):
...     @staticmethod
...     def __classcall_private__(cls):
...         print("calling private classcall")
...         return type.__call__(cls)
>>> FooNoInherits()
calling private classcall
<__main__.FooNoInherits object at ...>

Here the behavior is not inherited:

sage: class BarNoInherits(FooNoInherits): pass
sage: BarNoInherits()
<__main__.BarNoInherits object at ...>
[Python]
>>> from sage.all import *
>>> class BarNoInherits(FooNoInherits): pass
>>> BarNoInherits()
<__main__.BarNoInherits object at ...>

We now show the usage of both:

sage: class Foo2(object, metaclass=ClasscallMetaclass):
....:     @staticmethod
....:     def __classcall_private__(cls):
....:         print("calling private classcall")
....:         return type.__call__(cls)
....:     @staticmethod
....:     def __classcall__(cls):
....:         print("calling classcall with %s" % cls)
....:         return type.__call__(cls)
...
sage: Foo2()
calling private classcall
<__main__.Foo2 object at ...>

sage: class Bar2(Foo2): pass
sage: Bar2()
calling classcall with <class '__main__.Bar2'>
<__main__.Bar2 object at ...>
>>> from sage.all import *
>>> class Foo2(object, metaclass=ClasscallMetaclass):
...     @staticmethod
...     def __classcall_private__(cls):
...         print("calling private classcall")
...         return type.__call__(cls)
...     @staticmethod
...     def __classcall__(cls):
...         print("calling classcall with %s" % cls)
...         return type.__call__(cls)
...
>>> Foo2()
calling private classcall
<__main__.Foo2 object at ...>

>>> class Bar2(Foo2): pass
>>> Bar2()
calling classcall with <class '__main__.Bar2'>
<__main__.Bar2 object at ...>

Discussion

Typical applications include the implementation of factories or of unique representation (see UniqueRepresentation). Such features are traditionally implemented by either using a wrapper function, or fiddling with __new__().

The benefit, compared with fiddling directly with __new__() is a clear separation of the three distinct roles:

  • cls.__classcall__: what cls(<...>) does

  • cls.__new__: memory allocation for a new instance

  • cls.__init__: initialization of a newly created instance

The benefit, compared with using a wrapper function, is that the user interface has a single handle for the class:

sage: x = Partition([3,2,2])                                                # needs sage.combinat
sage: isinstance(x, Partition)      # not implemented                       # needs sage.combinat
[Python]
>>> from sage.all import *
>>> x = Partition([Integer(3),Integer(2),Integer(2)])                                                # needs sage.combinat
>>> isinstance(x, Partition)      # not implemented                       # needs sage.combinat

instead of:

sage: isinstance(x, sage.combinat.partition.Partition)                      # needs sage.combinat
True
>>> from sage.all import *
>>> isinstance(x, sage.combinat.partition.Partition)                      # needs sage.combinat
True

Another difference is that __classcall__ is inherited by subclasses, which may be desirable, or not. If not, one should instead define the method __classcall_private__ which will not be called for subclasses. Specifically, if a class cls defines both methods __classcall__ and __classcall_private__ then, for any subclass sub of cls:

  • cls(<args>) will call cls.__classcall_private__(cls, <args>)

  • sub(<args>) will call cls.__classcall__(sub, <args>)

__get__(instance, owner)[source]

This method implements instance binding behavior for nested classes.

Suppose that a class Outer contains a nested class cls which is an instance of this metaclass. For any object obj of cls, this method implements a instance binding behavior for obj.cls by delegating it to cls.__classget__(Outer, obj, owner) if available. Otherwise, obj.cls results in cls, as usual.

Similarly, a class binding as in Outer.cls is delegated to cls.__classget__(Outer, None, owner) if available and to cls if not.

Warning

for technical reasons, __classget__ must be defined as a staticmethod(), even though it receives the class itself as its first argument.

For technical details, and in particular the description of the owner argument, see the Section Implementing Descriptor in the Python reference manual.

EXAMPLES:

We show how to implement a nested class Outer.Inner with a binding behavior, as if it was a method of Outer: namely, for obj an instance of Outer, calling obj.Inner(...) is equivalent to Outer.Inner(obj, ...):

sage: import functools
sage: from sage.misc.nested_class import NestedClassMetaclass
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
sage: class Outer(metaclass=NestedClassMetaclass):
....:     class Inner(metaclass=ClasscallMetaclass):
....:         @staticmethod
....:         def __classget__(cls, instance, owner):
....:             print("calling __classget__(%s, %s, %s)" % (
....:                        cls, instance, owner))
....:             if instance is None:
....:                 return cls
....:             return functools.partial(cls, instance)
....:         def __init__(self, instance):
....:             self.instance = instance
sage: obj = Outer()
sage: bar = obj.Inner()
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
sage: bar.instance == obj
True
>>> from sage.all import *
>>> import functools
>>> from sage.misc.nested_class import NestedClassMetaclass
>>> from sage.misc.classcall_metaclass import ClasscallMetaclass
>>> class Outer(metaclass=NestedClassMetaclass):
...     class Inner(metaclass=ClasscallMetaclass):
...         @staticmethod
...         def __classget__(cls, instance, owner):
...             print("calling __classget__(%s, %s, %s)" % (
...                        cls, instance, owner))
...             if instance is None:
...                 return cls
...             return functools.partial(cls, instance)
...         def __init__(self, instance):
...             self.instance = instance
>>> obj = Outer()
>>> bar = obj.Inner()
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
>>> bar.instance == obj
True

Calling Outer.Inner returns the (unbinded) class as usual:

sage: Inner = Outer.Inner
calling __classget__(<class '__main__.Outer.Inner'>, None, <class '__main__.Outer'>)
sage: Inner
<class '__main__.Outer.Inner'>
sage: type(bar) is Inner
True
[Python]
>>> from sage.all import *
>>> Inner = Outer.Inner
calling __classget__(<class '__main__.Outer.Inner'>, None, <class '__main__.Outer'>)
>>> Inner
<class '__main__.Outer.Inner'>
>>> type(bar) is Inner
True

Warning

Inner has to be a new style class (i.e. a subclass of object).

Warning

Calling obj.Inner no longer returns a class:

sage: bind = obj.Inner
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
sage: bind
functools.partial(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>)
>>> from sage.all import *
>>> bind = obj.Inner
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
>>> bind
functools.partial(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>)
__contains__(x)[source]

This method implements membership testing for a class.

Let cls be a class in ClasscallMetaclass, and consider a call of the form:

x in cls

If cls defines a method __classcontains__, then this results in a call to:

cls.__classcontains__(cls, x)

Warning

for technical reasons, __classcontains__ must be defined as a staticmethod(), even though it receives the class itself as its first argument.

EXAMPLES:

We construct a class which implements membership testing, and which contains 1 and no other x:

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
sage: class Foo(metaclass=ClasscallMetaclass):
....:     @staticmethod
....:     def __classcontains__(cls, x):
....:         return x == 1
sage: 1 in Foo
True
sage: 2 in Foo
False
>>> from sage.all import *
>>> from sage.misc.classcall_metaclass import ClasscallMetaclass
>>> class Foo(metaclass=ClasscallMetaclass):
...     @staticmethod
...     def __classcontains__(cls, x):
...         return x == Integer(1)
>>> Integer(1) in Foo
True
>>> Integer(2) in Foo
False

We now check that for a class without __classcontains__ method, we emulate the usual error message:

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
sage: class Bar(metaclass=ClasscallMetaclass): pass
sage: 1 in Bar
Traceback (most recent call last):
...
TypeError: argument of type 'type' is not... iterable
[Python]
>>> from sage.all import *
>>> from sage.misc.classcall_metaclass import ClasscallMetaclass
>>> class Bar(metaclass=ClasscallMetaclass): pass
>>> Integer(1) in Bar
Traceback (most recent call last):
...
TypeError: argument of type 'type' is not... iterable
_set_classcall(function)[source]

Change dynamically the classcall function for this class.

EXAMPLES:

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
sage: class FOO(metaclass=ClasscallMetaclass): pass
sage: FOO()
<__main__.FOO object at ...>
>>> from sage.all import *
>>> from sage.misc.classcall_metaclass import ClasscallMetaclass
>>> class FOO(metaclass=ClasscallMetaclass): pass
>>> FOO()
<__main__.FOO object at ...>

For efficiency reason, the resolution of the __classcall__ method is done once for all, upon creation of the class. Thus, later dynamic changes to this method are ignored by FOO:

sage: FOO.__classcall__ = ConstantFunction(1)
sage: FOO()
<__main__.FOO object at ...>
[Python]
>>> from sage.all import *
>>> FOO.__classcall__ = ConstantFunction(Integer(1))
>>> FOO()
<__main__.FOO object at ...>

but not by subclasses created later on:

sage: class BAR(FOO): pass
sage: BAR()
1
>>> from sage.all import *
>>> class BAR(FOO): pass
>>> BAR()
1

To update the classcall special function for FOO, one should use this setter:

sage: FOO._set_classcall(ConstantFunction(2))
sage: FOO()
2
[Python]
>>> from sage.all import *
>>> FOO._set_classcall(ConstantFunction(Integer(2)))
>>> FOO()
2

Note that it has no influence on subclasses:

sage: class BAR(FOO): pass
sage: BAR()
1
>>> from sage.all import *
>>> class BAR(FOO): pass
>>> BAR()
1
sage.misc.classcall_metaclass.timeCall(T, n, *args)[source]

We illustrate some timing when using the classcall mechanism.

EXAMPLES:

sage: from sage.misc.classcall_metaclass import (
....:     ClasscallMetaclass, CRef, C2, C3, C2C, timeCall)
sage: timeCall(object, 1000)
>>> 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: %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: 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
>>> 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: %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: 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
>>> 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: %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: %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
>>> 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)[source]

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: 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
>>> 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