Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

""" 

Abstract methods 

""" 

#***************************************************************************** 

# Copyright (C) 2008 Nicolas M. Thiery <nthiery at users.sf.net> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

 

import types 

 

def abstract_method(f = None, optional = False): 

r""" 

Abstract methods 

 

INPUT: 

 

- ``f`` -- a function 

- ``optional`` -- a boolean; defaults to False 

 

The decorator :obj:`abstract_method` can be used to declare 

methods that should be implemented by all concrete derived 

classes. This declaration should typically include documentation 

for the specification for this method. 

 

The purpose is to enforce a consistent and visual syntax for such 

declarations. It is used by the Sage categories for automated 

tests (see ``Sets.Parent.test_not_implemented``). 

 

EXAMPLES: 

 

We create a class with an abstract method:: 

 

sage: class A(object): 

... 

....: @abstract_method 

....: def my_method(self): 

....: ''' 

....: The method :meth:`my_method` computes my_method 

... 

....: EXAMPLES:: 

... 

....: ''' 

....: pass 

... 

sage: A.my_method 

<abstract method my_method at ...> 

 

The current policy is that a ``NotImplementedError`` is raised 

when accessing the method through an instance, even before the 

method is called:: 

 

sage: x = A() 

sage: x.my_method 

Traceback (most recent call last): 

... 

NotImplementedError: <abstract method my_method at ...> 

 

It is also possible to mark abstract methods as optional:: 

 

sage: class A(object): 

... 

....: @abstract_method(optional = True) 

....: def my_method(self): 

....: ''' 

....: The method :meth:`my_method` computes my_method 

... 

....: EXAMPLES:: 

... 

....: ''' 

....: pass 

... 

 

sage: A.my_method 

<optional abstract method my_method at ...> 

 

sage: x = A() 

sage: x.my_method 

NotImplemented 

 

The official mantra for testing whether an optional abstract 

method is implemented is:: 

 

# Fixme: sage -t complains about indentation below 

# sage: if x.my_method is not NotImplemented: 

# ... x.my_method() 

# ... else: 

# ... print "x.my_method not available. Let's use some other trick." 

# ... 

# x.my_method not available. Let's use some other trick. 

 

.. rubric:: Discussion 

 

The policy details are not yet fixed. The purpose of this first 

implementation is to let developers experiment with it and give 

feedback on what's most practical. 

 

The advantage of the current policy is that attempts at using a 

non implemented methods are caught as early as possible. On the 

other hand, one cannot use introspection directly to fetch the 

documentation:: 

 

sage: x.my_method? # todo: not implemented 

 

Instead one needs to do:: 

 

sage: A._my_method? # todo: not implemented 

 

This could probably be fixed in :mod:`sage.misc.sageinspect`. 

 

TODO: what should be the recommended mantra for existence testing from the class? 

 

TODO: should extra information appear in the output? The name of 

the class? That of the super class where the abstract method is defined? 

 

TODO: look for similar decorators on the web, and merge 

 

.. rubric:: Implementation details 

 

Technically, an abstract_method is a non-data descriptor (see 

Invoking Descriptors in the Python reference manual). 

 

The syntax ``@abstract_method`` w.r.t. @abstract_method(optional = True) 

is achieved by a little trick which we test here:: 

 

sage: abstract_method(optional = True) 

<function <lambda> at ...> 

sage: abstract_method(optional = True)(banner) 

<optional abstract method banner at ...> 

sage: abstract_method(banner, optional = True) 

<optional abstract method banner at ...> 

 

""" 

if f is None: 

return lambda f: AbstractMethod(f, optional = optional) 

else: 

return AbstractMethod(f, optional) 

 

class AbstractMethod(object): 

def __init__(self, f, optional = False): 

""" 

Constructor for abstract methods 

 

EXAMPLES:: 

 

sage: def f(x): 

....: "doc of f" 

....: return 1 

... 

sage: x = abstract_method(f); x 

<abstract method f at ...> 

sage: x.__doc__ 

'doc of f' 

sage: x.__name__ 

'f' 

sage: x.__module__ 

'__main__' 

""" 

assert isinstance(f, types.FunctionType) or getattr(type(f),'__name__',None)=='cython_function_or_method' 

assert isinstance(optional, bool) 

self._f = f 

self._optional = optional 

self.__doc__ = f.__doc__ 

self.__name__ = f.__name__ 

try: 

self.__module__ = f.__module__ 

except AttributeError: 

pass 

 

def __repr__(self): 

""" 

EXAMPLES:: 

 

sage: abstract_method(banner) 

<abstract method banner at ...> 

 

sage: abstract_method(banner, optional = True) 

<optional abstract method banner at ...> 

""" 

return "<" + ("optional " if self._optional else "") + "abstract method %s at %s>"%(self.__name__, hex(id(self._f))) 

 

def _sage_src_lines_(self): 

""" 

Returns the source code location for the wrapped function. 

 

EXAMPLES:: 

 

sage: from sage.misc.sageinspect import sage_getsourcelines 

sage: g = abstract_method(banner) 

sage: (src, lines) = sage_getsourcelines(g) 

sage: src[0] 

'def banner():\n' 

sage: lines 

81 

""" 

from sage.misc.sageinspect import sage_getsourcelines 

return sage_getsourcelines(self._f) 

 

 

def __get__(self, instance, cls): 

""" 

Implements the attribute access protocol. 

 

EXAMPLES:: 

 

sage: class A: pass 

sage: def f(x): return 1 

... 

sage: f = abstract_method(f) 

sage: f.__get__(A(), A) 

Traceback (most recent call last): 

... 

NotImplementedError: <abstract method f at ...> 

""" 

if instance is None: 

return self 

elif self._optional: 

return NotImplemented 

else: 

raise NotImplementedError(repr(self)) 

 

def is_optional(self): 

""" 

Returns whether an abstract method is optional or not. 

 

EXAMPLES:: 

 

sage: class AbstractClass: 

....: @abstract_method 

....: def required(): pass 

... 

....: @abstract_method(optional = True) 

....: def optional(): pass 

sage: AbstractClass.required.is_optional() 

False 

sage: AbstractClass.optional.is_optional() 

True 

""" 

return self._optional 

 

def abstract_methods_of_class(cls): 

""" 

Returns the required and optional abstract methods of the class 

 

EXAMPLES:: 

 

sage: class AbstractClass: 

....: @abstract_method 

....: def required1(): pass 

... 

....: @abstract_method(optional = True) 

....: def optional2(): pass 

... 

....: @abstract_method(optional = True) 

....: def optional1(): pass 

... 

....: @abstract_method 

....: def required2(): pass 

... 

sage: sage.misc.abstract_method.abstract_methods_of_class(AbstractClass) 

{'optional': ['optional1', 'optional2'], 

'required': ['required1', 'required2']} 

 

""" 

result = { "required" : [], 

"optional" : [] 

} 

for name in dir(cls): 

entry = getattr(cls, name) 

if not isinstance(entry, AbstractMethod): 

continue 

if entry.is_optional(): 

result["optional"].append(name) 

else: 

result["required"].append(name) 

return result