Bindable classes

class sage.misc.bindable_class.BindableClass[source]

Bases: object

Bindable classes.

This class implements a binding behavior for nested classes that derive from it. Namely, if a nested class Outer.Inner derives from BindableClass, and if outer is an instance of Outer, then outer.Inner(...) is equivalent to Outer.Inner(outer, ...).

EXAMPLES:

Let us consider the following class Outer with a nested class Inner:

sage: from sage.misc.nested_class import NestedClassMetaclass
sage: class Outer(metaclass=NestedClassMetaclass):
....:     class Inner:
....:         def __init__(self, *args):
....:             print(args)
....:     def f(self, *args):
....:         print("{} {}".format(self, args))
....:     @staticmethod
....:     def f../_static(*args):
....:         print(args)

sage: outer = Outer()
>>> from sage.all import *
>>> from sage.misc.nested_class import NestedClassMetaclass
>>> class Outer(metaclass=NestedClassMetaclass):
...     class Inner:
...         def __init__(self, *args):
...             print(args)
...     def f(self, *args):
...         print("{} {}".format(self, args))
...     @staticmethod
...     def f../_static(*args):
...         print(args)

>>> outer = Outer()

By default, when Inner is a class nested in Outer, accessing outer.Inner returns the Inner class as is:

sage: outer.Inner is Outer.Inner
True
>>> from sage.all import *
>>> outer.Inner is Outer.Inner
True

In particular, outer is completely ignored in the following call:

sage: x = outer.Inner(1,2,3)
(1, 2, 3)
>>> from sage.all import *
>>> x = outer.Inner(Integer(1),Integer(2),Integer(3))
(1, 2, 3)

This is similar to what happens with a static method:

sage: outer.f../_static(1,2,3)
(1, 2, 3)
>>> from sage.all import *
>>> outer.f../_static(Integer(1),Integer(2),Integer(3))
(1, 2, 3)

In some cases, we would want instead Inner to receive outer as parameter, like in a usual method call:

sage: outer.f(1,2,3)
<__main__.Outer object at ...> (1, 2, 3)
>>> from sage.all import *
>>> outer.f(Integer(1),Integer(2),Integer(3))
<__main__.Outer object at ...> (1, 2, 3)

To this end, outer.f returns a bound method:

sage: outer.f
<bound method Outer.f of <__main__.Outer object at ...>>
>>> from sage.all import *
>>> outer.f
<bound method Outer.f of <__main__.Outer object at ...>>

so that outer.f(1,2,3) is equivalent to:

sage: Outer.f(outer, 1,2,3)
<__main__.Outer object at ...> (1, 2, 3)
>>> from sage.all import *
>>> Outer.f(outer, Integer(1),Integer(2),Integer(3))
<__main__.Outer object at ...> (1, 2, 3)

BindableClass gives this binding behavior to all its subclasses:

sage: from sage.misc.bindable_class import BindableClass
sage: class Outer(metaclass=NestedClassMetaclass):
....:     class Inner(BindableClass):
....:         " some documentation "
....:         def __init__(self, outer, *args):
....:             print("{} {}".format(outer, args))
>>> from sage.all import *
>>> from sage.misc.bindable_class import BindableClass
>>> class Outer(metaclass=NestedClassMetaclass):
...     class Inner(BindableClass):
...         " some documentation "
...         def __init__(self, outer, *args):
...             print("{} {}".format(outer, args))

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

sage: Outer.Inner
<class '__main__.Outer.Inner'>
>>> from sage.all import *
>>> Outer.Inner
<class '__main__.Outer.Inner'>

However, outer.Inner(1,2,3) is equivalent to Outer.Inner(outer, 1,2,3):

sage: outer = Outer()
sage: x = outer.Inner(1,2,3)
<__main__.Outer object at ...> (1, 2, 3)
>>> from sage.all import *
>>> outer = Outer()
>>> x = outer.Inner(Integer(1),Integer(2),Integer(3))
<__main__.Outer object at ...> (1, 2, 3)

To achieve this, outer.Inner returns (some sort of) bound class:

sage: outer.Inner
<bound class '__main__.Outer.Inner' of <__main__.Outer object at ...>>
>>> from sage.all import *
>>> outer.Inner
<bound class '__main__.Outer.Inner' of <__main__.Outer object at ...>>

Note

This is not actually a class, but an instance of functools.partial:

sage: type(outer.Inner).mro()
[<class 'sage.misc.bindable_class.BoundClass'>,
 <... 'functools.partial'>,
 <... 'object'>]
>>> from sage.all import *
>>> type(outer.Inner).mro()
[<class 'sage.misc.bindable_class.BoundClass'>,
 <... 'functools.partial'>,
 <... 'object'>]

Still, documentation works as usual:

sage: outer.Inner.__doc__
' some documentation '
>>> from sage.all import *
>>> outer.Inner.__doc__
' some documentation '
class sage.misc.bindable_class.BoundClass(*args)[source]

Bases: partial

class sage.misc.bindable_class.Inner2[source]

Bases: BindableClass

Some documentation for Inner2

class sage.misc.bindable_class.Outer[source]

Bases: object

A class with a bindable nested class, for testing purposes

class Inner[source]

Bases: BindableClass

Some documentation for Outer.Inner

Inner2[source]

alias of Inner2