Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/misc/bindable_class.py
4036 views
1
"""
2
Bindable classes
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2012 Nicolas M. Thiery <nthiery at users.sf.net>
6
#
7
# Distributed under the terms of the GNU General Public License (GPL)
8
# http://www.gnu.org/licenses/
9
#*****************************************************************************
10
11
import functools
12
from sage.misc.nested_class import NestedClassMetaclass
13
from sage.misc.classcall_metaclass import ClasscallMetaclass
14
15
class BindableClass(object):
16
"""
17
Bindable classes
18
19
This class implements a binding behavior for nested classes that
20
derive from it. Namely, if a nested class ``Outer.Inner`` derives
21
from ``BindableClass``, and if ``outer`` is an instance of
22
``Outer``, then ``outer.Inner(...)`` is equivalent to
23
``Outer.Inner(outer, ...)``.
24
25
EXAMPLES:
26
27
Let us consider the following class ``Outer`` with a nested class ``Inner``::
28
29
sage: from sage.misc.nested_class import NestedClassMetaclass
30
sage: class Outer:
31
... __metaclass__ = NestedClassMetaclass # just a workaround for Python misnaming nested classes
32
...
33
... class Inner:
34
... def __init__(self, *args):
35
... print args
36
...
37
... def f(self, *args):
38
... print self, args
39
...
40
... @staticmethod
41
... def f_static(*args):
42
... print args
43
...
44
sage: outer = Outer()
45
46
By default, when ``Inner`` is a class nested in ``Outer``,
47
accessing ``outer.Inner`` returns the ``Inner`` class as is::
48
49
sage: outer.Inner is Outer.Inner
50
True
51
52
In particular, ``outer`` is completely ignored in the following call::
53
54
sage: x = outer.Inner(1,2,3)
55
(1, 2, 3)
56
57
This is similar to what happens with a static method::
58
59
sage: outer.f_static(1,2,3)
60
(1, 2, 3)
61
62
In some cases, we would want instead ``Inner``` to receive ``outer``
63
as parameter, like in a usual method call::
64
65
sage: outer.f(1,2,3)
66
<__main__.Outer object at ...> (1, 2, 3)
67
68
To this end, ``outer.f`` returns a *bound method*::
69
70
sage: outer.f
71
<bound method Outer.f of <__main__.Outer object at ...>>
72
73
so that ``outer.f(1,2,3)`` is equivalent to::
74
75
sage: Outer.f(outer, 1,2,3)
76
<__main__.Outer object at ...> (1, 2, 3)
77
78
:class:`BindableClass` gives this binding behavior to all its subclasses::
79
80
sage: from sage.misc.bindable_class import BindableClass
81
sage: class Outer:
82
... __metaclass__ = NestedClassMetaclass # just a workaround for Python misnaming nested classes
83
...
84
... class Inner(BindableClass):
85
... " some documentation "
86
... def __init__(self, outer, *args):
87
... print outer, args
88
89
Calling ``Outer.Inner`` returns the (unbound) class as usual::
90
91
sage: Outer.Inner
92
<class '__main__.Outer.Inner'>
93
94
However, ``outer.Inner(1,2,3)`` is equivalent to ``Outer.Inner(outer, 1,2,3)``::
95
96
sage: outer = Outer()
97
sage: x = outer.Inner(1,2,3)
98
<__main__.Outer object at ...> (1, 2, 3)
99
100
To achieve this, ``outer.Inner`` returns (some sort of) bound class::
101
102
sage: outer.Inner
103
<bound class '__main__.Outer.Inner' of <__main__.Outer object at ...>>
104
105
.. note::
106
107
This is not actually a class, but an instance of
108
:class:`functools.partial`::
109
110
sage: type(outer.Inner).mro()
111
[<class 'sage.misc.bindable_class.BoundClass'>,
112
<type 'functools.partial'>,
113
<type 'object'>]
114
115
Still, documentation works as usual::
116
117
sage: outer.Inner.__doc__
118
' some documentation '
119
120
TESTS::
121
122
sage: from sage.misc.bindable_class import Outer
123
sage: TestSuite(Outer.Inner).run()
124
sage: outer = Outer()
125
sage: TestSuite(outer.Inner).run(skip=["_test_pickling"])
126
"""
127
__metaclass__ = ClasscallMetaclass
128
129
@staticmethod
130
def __classget__(cls, instance, owner):
131
"""
132
Binds ``cls`` to ``instance``, returning a ``BoundClass``
133
134
INPUT:
135
136
- ``instance`` -- an object of the outer class or ``None``
137
138
For technical details, see the Section :python:`Implementing Descriptor
139
<reference/datamodel.html#implementing-descriptors>` in the Python
140
reference manual.
141
142
EXAMPLES::
143
144
sage: from sage.misc.bindable_class import Outer
145
sage: Outer.Inner
146
<class 'sage.misc.bindable_class.Outer.Inner'>
147
sage: Outer().Inner
148
<bound class 'sage.misc.bindable_class.Outer.Inner' of <sage.misc.bindable_class.Outer object at ...>>
149
"""
150
if instance is None:
151
return cls
152
return BoundClass(cls, instance)
153
# We probably do not need to use sage_wraps, since
154
# sageinspect already supports partial functions
155
#return sage_wraps(cls)(BoundClass(cls, instance))
156
157
class BoundClass(functools.partial):
158
"""
159
TESTS::
160
161
sage: from sage.misc.sageinspect import *
162
sage: from sage.misc.bindable_class import Outer
163
sage: x = Outer()
164
sage: c = x.Inner; c
165
<bound class 'sage.misc.bindable_class.Outer.Inner' of <sage.misc.bindable_class.Outer object at ...>>
166
167
Introspection works, at least partially:
168
169
sage: sage_getdoc(c)
170
' Some documentation for Outer.Inner\n'
171
sage: sage_getfile(c)
172
'.../sage/misc/bindable_class.py'
173
174
sage: c = x.Inner2
175
sage: sage_getdoc(c)
176
' Some documentation for Inner2\n'
177
sage: sage_getsourcelines(c)
178
(['class Inner2(BindableClass):...], ...)
179
180
.. warning::
181
182
Since ``c`` is not a class (as tested by inspect.isclass),
183
and has a ``__call__`` method, IPython's introspection
184
(with ``c?``) insists on showing not only its
185
documentation but also its class documentation and call
186
documentation (see :meth:`IPython.OInspect.Inspector.pdoc`)
187
if available.
188
189
Until a better approach is found, we reset the documentation
190
of ``BoundClass`` below, and make an exception for
191
:meth:`__init__`` to the strict rule that every method should
192
be doctested::
193
194
sage: c.__class__.__doc__
195
sage: c.__class__.__init__.__doc__
196
197
"""
198
__doc__ = None # See warning above
199
200
def __init__(self, *args):
201
super(BoundClass, self).__init__(*args)
202
self.__doc__ = self.func.__doc__
203
204
def __repr__(self):
205
"""
206
TESTS:
207
208
sage: from sage.misc.bindable_class import Outer
209
sage: x = Outer(); x
210
<sage.misc.bindable_class.Outer object at ...>
211
sage: x.Inner
212
<bound class 'sage.misc.bindable_class.Outer.Inner' of <sage.misc.bindable_class.Outer object at ...>>
213
"""
214
return "<bound %s of %s>"%(repr(self.func)[1:-1], self.args[0])
215
216
##############################################################################
217
# Test classes
218
##############################################################################
219
220
class Inner2(BindableClass):
221
"""
222
Some documentation for Inner2
223
"""
224
225
class Outer:
226
"""
227
A class with a bindable nested class, for testing purposes
228
"""
229
__metaclass__ = NestedClassMetaclass # workaround for python pickling bug
230
231
class Inner(BindableClass):
232
"""
233
Some documentation for Outer.Inner
234
"""
235
236
Inner2 = Inner2
237
238