Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/misc/abstract_method.py
4036 views
1
"""
2
Abstract methods
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2008 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 types
12
13
def abstract_method(f = None, optional = False):
14
r"""
15
Abstract methods
16
17
INPUT:
18
19
- ``f`` -- a function
20
- ``optional`` -- a boolean; defaults to False
21
22
The decorator :obj:`abstract_method` can be used to declare
23
methods that should be implemented by all concrete derived
24
classes. This declaration should typically include documentation
25
for the specification for this method.
26
27
The purpose is to enforce a consistent and visual syntax for such
28
declarations. It is used by the Sage categories for automated
29
tests (see ``Sets.Parent.test_not_implemented``).
30
31
EXAMPLES:
32
33
We create a class with an abstract method::
34
35
sage: class A(object):
36
...
37
... @abstract_method
38
... def my_method(self):
39
... '''
40
... The method :meth:`my_method` computes my_method
41
...
42
... EXAMPLES::
43
...
44
... '''
45
... pass
46
...
47
sage: A.my_method
48
<abstract method my_method at ...>
49
50
The current policy is that a ``NotImplementedError`` is raised
51
when accessing the method through an instance, even before the
52
method is called::
53
54
sage: x = A()
55
sage: x.my_method
56
Traceback (most recent call last):
57
...
58
NotImplementedError: <abstract method my_method at ...>
59
60
It is also possible to mark abstract methods as optional::
61
62
sage: class A(object):
63
...
64
... @abstract_method(optional = True)
65
... def my_method(self):
66
... '''
67
... The method :meth:`my_method` computes my_method
68
...
69
... EXAMPLES::
70
...
71
... '''
72
... pass
73
...
74
75
sage: A.my_method
76
<optional abstract method my_method at ...>
77
78
sage: x = A()
79
sage: x.my_method
80
NotImplemented
81
82
The official mantra for testing whether an optional abstract
83
method is implemented is::
84
85
# Fixme: sage -t complains about indentation below
86
# sage: if x.my_method is not NotImplemented:
87
# ... x.my_method()
88
# ... else:
89
# ... print "x.my_method not available. Let's use some other trick."
90
# ...
91
# x.my_method not available. Let's use some other trick.
92
93
.. rubric:: Discussion
94
95
The policy details are not yet fixed. The purpose of this first
96
implementation is to let developers experiment with it and give
97
feedback on what's most practical.
98
99
The advantage of the current policy is that attempts at using a
100
non implemented methods are caught as early as possible. On the
101
other hand, one cannot use introspection directly to fetch the
102
documentation::
103
104
sage: x.my_method? # todo: not implemented
105
106
Instead one needs to do::
107
108
sage: A._my_method? # todo: not implemented
109
110
This could probably be fixed in :mod:`sage.misc.sageinspect`.
111
112
TODO: what should be the recommended mantra for existence testing from the class?
113
114
TODO: should extra information appear in the output? The name of
115
the class? That of the super class where the abstract method is defined?
116
117
TODO: look for similar decorators on the web, and merge
118
119
.. rubric:: Implementation details
120
121
Technically, an abstract_method is a non-data descriptor (see
122
Invoking Descriptors in the Python reference manual).
123
124
The syntax ``@abstract_method`` w.r.t. @abstract_method(optional = True)
125
is achieved by a little trick which we test here::
126
127
sage: abstract_method(optional = True)
128
<function <lambda> at ...>
129
sage: abstract_method(optional = True)(banner)
130
<optional abstract method banner at ...>
131
sage: abstract_method(banner, optional = True)
132
<optional abstract method banner at ...>
133
134
"""
135
if f is None:
136
return lambda f: AbstractMethod(f, optional = optional)
137
else:
138
return AbstractMethod(f, optional)
139
140
class AbstractMethod(object):
141
def __init__(self, f, optional = False):
142
"""
143
Constructor for abstract methods
144
145
EXAMPLES::
146
147
sage: def f(x):
148
... "doc of f"
149
... return 1
150
...
151
sage: x = abstract_method(f); x
152
<abstract method f at ...>
153
sage: x.__doc__
154
'doc of f'
155
sage: x.__name__
156
'f'
157
sage: x.__module__
158
'__main__'
159
"""
160
assert isinstance(f, types.FunctionType) # only plain functions are supported yet
161
assert isinstance(optional, bool)
162
self._f = f
163
self._optional = optional
164
if hasattr(f, "func_doc"):
165
self.__doc__ = f.func_doc
166
if hasattr(f, "func_name"):
167
self.__name__ = f.func_name
168
else:
169
self.__name__ = "..."
170
if hasattr(f, "__module__"):
171
self.__module__ = f.__module__
172
173
def __repr__(self):
174
"""
175
EXAMPLES::
176
177
sage: abstract_method(banner)
178
<abstract method banner at ...>
179
180
sage: abstract_method(banner, optional = True)
181
<optional abstract method banner at ...>
182
"""
183
return "<" + ("optional " if self._optional else "") + "abstract method %s"%repr(self._f)[10:]
184
185
def _sage_src_lines_(self):
186
"""
187
Returns the source code location for the wrapped function.
188
189
EXAMPLES::
190
191
sage: from sage.misc.sageinspect import sage_getsourcelines
192
sage: g = abstract_method(banner)
193
sage: (src, lines) = sage_getsourcelines(g)
194
sage: src[0]
195
'def banner():\n'
196
sage: lines
197
77
198
199
"""
200
from sage.misc.sageinspect import sage_getsourcelines
201
return sage_getsourcelines(self._f)
202
203
204
def __get__(self, instance, cls):
205
"""
206
Implements the attribute access protocol.
207
208
EXAMPLES::
209
210
sage: class A: pass
211
sage: def f(x): return 1
212
...
213
sage: f = abstract_method(f)
214
sage: f.__get__(A(), A)
215
Traceback (most recent call last):
216
...
217
NotImplementedError: <abstract method f at ...>
218
"""
219
if instance is None:
220
return self
221
elif self._optional:
222
return NotImplemented
223
else:
224
raise NotImplementedError(repr(self))
225
226
def is_optional(self):
227
"""
228
Returns whether an abstract method is optional or not.
229
230
EXAMPLES::
231
232
sage: class AbstractClass:
233
... @abstract_method
234
... def required(): pass
235
...
236
... @abstract_method(optional = True)
237
... def optional(): pass
238
sage: AbstractClass.required.is_optional()
239
False
240
sage: AbstractClass.optional.is_optional()
241
True
242
"""
243
return self._optional
244
245
def abstract_methods_of_class(cls):
246
"""
247
Returns the required and optional abstract methods of the class
248
249
EXAMPLES::
250
251
sage: class AbstractClass:
252
... @abstract_method
253
... def required1(): pass
254
...
255
... @abstract_method(optional = True)
256
... def optional2(): pass
257
...
258
... @abstract_method(optional = True)
259
... def optional1(): pass
260
...
261
... @abstract_method
262
... def required2(): pass
263
...
264
sage: sage.misc.abstract_method.abstract_methods_of_class(AbstractClass)
265
{'required': ['required1', 'required2'], 'optional': ['optional1', 'optional2']}
266
267
"""
268
result = { "required" : [],
269
"optional" : []
270
}
271
for name in dir(cls):
272
entry = getattr(cls, name)
273
if not isinstance(entry, AbstractMethod):
274
continue
275
if entry.is_optional():
276
result["optional"].append(name)
277
else:
278
result["required"].append(name)
279
return result
280
281