Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/structure/dynamic_class.py
4036 views
1
"""
2
Dynamic classes
3
4
.. rubric:: Why dynamic classes?
5
6
The short answer:
7
8
- Multiple inheritance is a powerful tool for constructing new classes
9
by combining preexisting building blocks.
10
- There is a combinatorial explosion in the number of potentially
11
useful classes that can be produced this way.
12
- The implementation of standard mathematical constructions calls for
13
producing such combinations automatically.
14
- Dynamic classes, i.e. classes created on the fly by the Python
15
interpreter, are a natural mean to achieve this.
16
17
The long answer:
18
19
Say we want to construct a new class ``MyPermutation`` for
20
permutations in a given set `S` (in Sage, `S` will be modelled by a
21
parent, but we won't discuss this point here). First, we have to
22
choose a data structure for the permutations, typically among the
23
following:
24
25
- Stored by cycle type
26
- Stored by code
27
- Stored in list notation
28
- C arrays of short ints (for small permutations)
29
- python lists of ints (for huge permutations)
30
- ...
31
- Stored by reduced word
32
- Stored as a function
33
- ...
34
35
Luckily, the Sage library provides (or will provide) classes
36
implementing each of those data structures. Those classes all share a
37
common interface (or possibly a common abstract base class). So we can
38
just derive our class from the chosen one::
39
40
class MyPermutation(PermutationCycleType):
41
...
42
43
Then we may want to further choose a specific memory behavior (unique
44
representation, copy-on-write) which (hopefuly) can again be achieved
45
by inheritance::
46
47
class MyPermutation(UniqueRepresentation, PermutationCycleType):
48
...
49
50
Finaly, we may want to endow the permutations in `S` with further
51
operations coming from the (algebraic) structure of `S`:
52
53
- group operations
54
- or just monoid operations (for a subset of permutations not stable by inverse)
55
- poset operations (for left/right/Bruhat order)
56
- word operations (searching for substrings, patterns, ...)
57
58
Or any combination thereof. Now, our class typically looks like::
59
60
class MyPermutation(UniqueRepresentation, PermutationCycleType, PosetElement, GroupElement):
61
...
62
63
Note the combinatorial explosion in the potential number of classes
64
which can be created this way.
65
66
67
In practice, such classes will be used in mathematical constructions
68
like::
69
70
SymmetricGroup(5).subset(... TODO: find a good example in the context above ...)
71
72
In such a construction, the structure of the result, and therefore the
73
operations on its elements can only be determined at execution
74
time. Let us take another standard construction::
75
76
A = cartesian_product( B, C )
77
78
Depending on the structure of `B` and `C`, and possibly on further
79
options passed down by the user, `A` may be:
80
81
- an enumerated set
82
- a group
83
- an algebra
84
- a poset
85
- ...
86
87
Or any combination thereof.
88
89
Hardcoding classes for all potential combinations would be at best
90
tedious. Furthermore, this would require a cumbersome mechanism to
91
lookup the appropriate class depending on the desired combination.
92
93
Instead, one may use the ability of Python to create new classes
94
dynamicaly::
95
96
type("class name", tuple of base classes, dictionary of methods)
97
98
This paradigm is powerful, but there are some technicalities to
99
address. The purpose of this library is to standardize its use within
100
Sage, and in particular to ensure that the constructed classes are
101
reused whenever possible (unique representation), and can be pickled.
102
103
.. rubric:: Combining dynamic classes and Cython classes
104
105
Cython classes cannot inherit from a dynamic class (there might be
106
some partial support for this in the future). On the other hand, such
107
an inheritance can be partially emulated using :meth:`__getattr__`. See
108
``sage.categories.examples.semigroups_cython`` for an example.
109
110
"""
111
112
#*****************************************************************************
113
# Copyright (C) 2008-2009 Nicolas M. Thiery <nthiery at users.sf.net>
114
#
115
# Distributed under the terms of the GNU General Public License (GPL)
116
# http://www.gnu.org/licenses/
117
#*****************************************************************************
118
119
from sage.misc.cachefunc import cached_function
120
from sage.structure.unique_representation import ClasscallMetaclass
121
122
def dynamic_class(name, bases, cls = None, reduction = None, doccls=None):
123
r"""
124
INPUT:
125
126
- ``name`` -- a string
127
- ``bases`` -- a tuple of classes
128
- ``cls`` -- a class or None
129
- ``reduction`` -- a tuple or None
130
- ``doccls`` -- a class or None
131
132
Constructs dynamically a new class ``C`` with name ``name``, and
133
bases ``bases``. If ``cls`` is provided, then its methods will be
134
inserted into ``C`` as well. The module of ``C`` is set from the
135
module of ``cls`` or from the first base class (``bases`` should
136
be non empty if ``cls` is ``None``).
137
138
Documentation and source instrospection is taken from ``doccls``, or
139
``cls`` if ``doccls`` is ``None``, or ``bases[0]`` if both are ``None``.
140
141
The constructed class can safely be pickled (assuming the
142
arguments themselves can).
143
144
The result is cached, ensuring unique representation of dynamic
145
classes.
146
147
See :mod:`sage.structure.dynamic_class` for a discussion of the
148
dynamic classes paradigm, and its relevance to Sage.
149
150
EXAMPLES:
151
152
To setup the stage, we create a class Foo with some methods,
153
cached methods, and lazy_attributes, and a class Bar::
154
155
sage: from sage.misc.lazy_attribute import lazy_attribute
156
sage: from sage.misc.cachefunc import cached_function
157
sage: from sage.structure.dynamic_class import dynamic_class
158
sage: class Foo(object):
159
... "The Foo class"
160
... def __init__(self, x):
161
... self._x = x
162
... @cached_method
163
... def f(self):
164
... return self._x^2
165
... def g(self):
166
... return self._x^2
167
... @lazy_attribute
168
... def x(self):
169
... return self._x
170
...
171
sage: class Bar:
172
... def bar(self):
173
... return self._x^2
174
...
175
176
We now create a class FooBar which is a copy of Foo, except that it
177
also inherits from Bar::
178
179
sage: FooBar = dynamic_class("FooBar", (Bar,), Foo)
180
sage: x = FooBar(3)
181
sage: x.f()
182
9
183
sage: x.f() is x.f()
184
True
185
sage: x.x
186
3
187
sage: x.bar()
188
9
189
sage: FooBar.__name__
190
'FooBar'
191
sage: FooBar.__module__
192
'__main__'
193
194
sage: Foo.__bases__
195
(<type 'object'>,)
196
sage: FooBar.__bases__
197
(<type 'object'>, <class __main__.Bar at ...>)
198
sage: Foo.mro()
199
[<class '__main__.Foo'>, <type 'object'>]
200
sage: FooBar.mro()
201
[<class '__main__.FooBar'>, <type 'object'>, <class __main__.Bar at ...>]
202
203
Dynamic classes are pickled by construction. Namely, upon
204
unpickling, the class will be reconstructed by recalling
205
dynamic_class with the same arguments::
206
207
sage: type(FooBar).__reduce__(FooBar)
208
(<function dynamic_class at ...>, ('FooBar', (<class __main__.Bar at ...>,), <class '__main__.Foo'>, None, None))
209
210
Technically, this is achieved by using a metaclass, since the
211
Python pickling protocol for classes is to pickle by name::
212
213
sage: type(FooBar)
214
<class 'sage.structure.dynamic_class.DynamicMetaclass'>
215
216
The following (meaningless) example illustrates how to customize
217
the result of the reduction::
218
219
sage: BarFoo = dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (3,)))
220
sage: type(BarFoo).__reduce__(BarFoo)
221
(<type 'str'>, (3,))
222
sage: loads(dumps(BarFoo))
223
'3'
224
225
TESTS::
226
227
sage: import __main__
228
sage: __main__.Foo = Foo
229
sage: __main__.Bar = Bar
230
sage: x = FooBar(3)
231
sage: x.__dict__ # Breaks without the __dict__ deletion in dynamic_class_internal
232
{'_x': 3}
233
234
sage: type(FooBar).__reduce__(FooBar)
235
(<function dynamic_class at ...>, ('FooBar', (<class __main__.Bar at ...>,), <class '__main__.Foo'>, None, None))
236
sage: import cPickle
237
sage: cPickle.loads(cPickle.dumps(FooBar)) == FooBar
238
True
239
240
We check that instrospection works reasonably::
241
242
sage: sage.misc.sageinspect.sage_getdoc(FooBar)
243
'The Foo class\n'
244
245
Finally, we check that classes derived from UniqueRepresentation
246
are handled gracefuly (despite them also using a metaclass)::
247
248
sage: FooUnique = dynamic_class("Foo", (Bar, UniqueRepresentation))
249
sage: loads(dumps(FooUnique)) is FooUnique
250
True
251
252
"""
253
bases = tuple(bases)
254
#assert(len(bases) > 0 )
255
assert(type(name) is str)
256
# assert(cls is None or issubtype(type(cls), type) or type(cls) is classobj)
257
return dynamic_class_internal(name, bases, cls, reduction, doccls)
258
259
@cached_function
260
def dynamic_class_internal(name, bases, cls = None, reduction = None, doccls = None):
261
r"""
262
See sage.structure.dynamic_class.dynamic_class? for indirect doctests.
263
264
TESTS::
265
266
sage: Foo1 = sage.structure.dynamic_class.dynamic_class_internal("Foo", (object,))
267
sage: Foo2 = sage.structure.dynamic_class.dynamic_class_internal("Foo", (object,), doccls = sage.structure.dynamic_class.TestClass)
268
sage: Foo3 = sage.structure.dynamic_class.dynamic_class_internal("Foo", (object,), cls = sage.structure.dynamic_class.TestClass)
269
sage: all(Foo.__name__ == 'Foo' for Foo in [Foo1, Foo2, Foo3])
270
True
271
sage: all(Foo.__bases__ == (object,) for Foo in [Foo1, Foo2, Foo3])
272
True
273
sage: Foo1.__module__ == object.__module__
274
True
275
sage: Foo2.__module__ == sage.structure.dynamic_class.TestClass.__module__
276
True
277
sage: Foo3.__module__ == sage.structure.dynamic_class.TestClass.__module__
278
True
279
sage: Foo1.__doc__ == object.__doc__
280
True
281
sage: Foo2.__doc__ == sage.structure.dynamic_class.TestClass.__doc__
282
True
283
sage: Foo3.__doc__ == sage.structure.dynamic_class.TestClass.__doc__
284
True
285
286
We check that instrospection works reasonably::
287
288
sage: import inspect
289
sage: inspect.getfile(Foo2)
290
'.../sage/structure/dynamic_class.pyc'
291
sage: inspect.getfile(Foo3)
292
'.../sage/structure/dynamic_class.pyc'
293
sage: sage.misc.sageinspect.sage_getsourcelines(Foo2)
294
(['class TestClass:...'], ...)
295
sage: sage.misc.sageinspect.sage_getsourcelines(Foo3)
296
(['class TestClass:...'], ...)
297
sage: sage.misc.sageinspect.sage_getsourcelines(Foo2())
298
(['class TestClass:...'], ...)
299
sage: sage.misc.sageinspect.sage_getsourcelines(Foo3())
300
(['class TestClass:...'], ...)
301
sage: sage.misc.sageinspect.sage_getsourcelines(Foo3().bla)
302
([' def bla():...'], ...)
303
304
"""
305
if reduction is None:
306
reduction = (dynamic_class, (name, bases, cls, reduction, doccls))
307
if cls is not None:
308
methods = dict(cls.__dict__)
309
# Anything else that should not be kept?
310
if methods.has_key("__dict__"):
311
methods.__delitem__("__dict__")
312
bases = cls.__bases__ + bases
313
else:
314
methods = {}
315
if doccls is None:
316
if cls is not None:
317
doccls = cls
318
else:
319
assert bases != ()
320
doccls = bases[0]
321
methods['_reduction'] = reduction
322
if not methods.has_key("_sage_src_lines_"):
323
from sage.misc.sageinspect import sage_getsourcelines
324
@staticmethod
325
def _sage_src_lines():
326
return sage_getsourcelines(doccls)
327
methods['_sage_src_lines_'] = _sage_src_lines
328
methods['__doc__'] = doccls.__doc__
329
methods['__module__'] = doccls.__module__
330
#if not methods.has_key("_sage_doc_"):
331
# from sage.misc.sageinspect import sage_getdoc
332
# def _sage_getdoc(obj):
333
# return sage_getdoc(cls)
334
# methods['_sage_src_lines_'] = _sage_getdoc
335
336
metaclass = DynamicMetaclass
337
# The metaclass of a class must derive from the metaclasses of its
338
# bases. The following handles the case where one of the base
339
# class is readilly in the ClasscallMetaclass. This
340
# approach won't scale well if we start using metaclasses
341
# elsewhere in Sage.
342
for base in bases:
343
if type(base) is ClasscallMetaclass:
344
metaclass = DynamicClasscallMetaclass
345
return metaclass(name, bases, methods)
346
347
class DynamicMetaclass(type):
348
"""
349
A metaclass implementing an appropriate reduce-by-construction method
350
"""
351
352
def __reduce__(self):
353
"""
354
See sage.structure.dynamic_class.dynamic_class? for non trivial tests.
355
356
TESTS::
357
358
sage: class Foo: pass
359
sage: class DocClass: pass
360
sage: C = sage.structure.dynamic_class.dynamic_class_internal("bla", (object,), Foo, doccls = DocClass)
361
sage: type(C).__reduce__(C)
362
(<function dynamic_class at ...>,
363
('bla', (<type 'object'>,), <class __main__.Foo at ...>, None, <class __main__.DocClass at ...>))
364
sage: C = sage.structure.dynamic_class.dynamic_class_internal("bla", (object,), Foo, doccls = DocClass, reduction = "blah")
365
sage: type(C).__reduce__(C)
366
'blah'
367
"""
368
return self._reduction
369
370
class DynamicClasscallMetaclass(DynamicMetaclass, ClasscallMetaclass):
371
pass
372
373
# This registers the appropriate reduction methods (depends on #5985)
374
import copy_reg
375
copy_reg.pickle(DynamicMetaclass, DynamicMetaclass.__reduce__)
376
377
import copy_reg
378
copy_reg.pickle(DynamicClasscallMetaclass, DynamicMetaclass.__reduce__)
379
380
class TestClass:
381
"""
382
A class used for checking that introspection works
383
"""
384
def bla():
385
"""
386
bla ...
387
"""
388
pass
389
390