Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/misc/abstract_method.py
8814 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
79
198
"""
199
from sage.misc.sageinspect import sage_getsourcelines
200
return sage_getsourcelines(self._f)
201
202
203
def __get__(self, instance, cls):
204
"""
205
Implements the attribute access protocol.
206
207
EXAMPLES::
208
209
sage: class A: pass
210
sage: def f(x): return 1
211
...
212
sage: f = abstract_method(f)
213
sage: f.__get__(A(), A)
214
Traceback (most recent call last):
215
...
216
NotImplementedError: <abstract method f at ...>
217
"""
218
if instance is None:
219
return self
220
elif self._optional:
221
return NotImplemented
222
else:
223
raise NotImplementedError(repr(self))
224
225
def is_optional(self):
226
"""
227
Returns whether an abstract method is optional or not.
228
229
EXAMPLES::
230
231
sage: class AbstractClass:
232
... @abstract_method
233
... def required(): pass
234
...
235
... @abstract_method(optional = True)
236
... def optional(): pass
237
sage: AbstractClass.required.is_optional()
238
False
239
sage: AbstractClass.optional.is_optional()
240
True
241
"""
242
return self._optional
243
244
def abstract_methods_of_class(cls):
245
"""
246
Returns the required and optional abstract methods of the class
247
248
EXAMPLES::
249
250
sage: class AbstractClass:
251
... @abstract_method
252
... def required1(): pass
253
...
254
... @abstract_method(optional = True)
255
... def optional2(): pass
256
...
257
... @abstract_method(optional = True)
258
... def optional1(): pass
259
...
260
... @abstract_method
261
... def required2(): pass
262
...
263
sage: sage.misc.abstract_method.abstract_methods_of_class(AbstractClass)
264
{'required': ['required1', 'required2'], 'optional': ['optional1', 'optional2']}
265
266
"""
267
result = { "required" : [],
268
"optional" : []
269
}
270
for name in dir(cls):
271
entry = getattr(cls, name)
272
if not isinstance(entry, AbstractMethod):
273
continue
274
if entry.is_optional():
275
result["optional"].append(name)
276
else:
277
result["required"].append(name)
278
return result
279
280