Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/structure/factory.pyx
4045 views
1
r"""
2
Factory for unique objects
3
4
More general than :mod:`~sage.structure.unique_representation`, the
5
:class:`UniqueFactory` class can specify a subset of the arguments
6
that serve as the unique key. Typically, this is used to construct
7
objects that accept an optional ``check=[True|False]`` argument, but
8
whose result should be unique irregardless of said optional argument.
9
"""
10
11
#*****************************************************************************
12
# Copyright (C) 2008 Robert Bradshaw <[email protected]>
13
#
14
# Distributed under the terms of the GNU General Public License (GPL)
15
#
16
# This code is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
# General Public License for more details.
20
#
21
# The full text of the GPL is available at:
22
#
23
# http://www.gnu.org/licenses/
24
#******************************************************************************
25
26
import weakref, types, copy_reg
27
28
from sage_object cimport SageObject
29
30
cdef sage_version
31
from sage.version import version as sage_version
32
33
sage_version = sage_version.split('.')
34
for i in range(len(sage_version)):
35
try:
36
sage_version[i] = int(sage_version[i])
37
except ValueError:
38
pass
39
sage_version = tuple(sage_version)
40
41
42
cdef class UniqueFactory(SageObject):
43
"""
44
This class is intended to make it easy to make unique, cached objects.
45
46
It is based on the idea that the object is uniquely defined by a set of
47
defining data (the key). There is also the possibility some non-defining
48
data (extra args) which will be used in initial creation, but not affect
49
the caching.
50
51
The objects created are cached (using weakrefs) based on their key and
52
returned directly rather than re-created if requested again. Pickling
53
will return the same object for the same version of Sage, and distinct
54
(but hopefully equal) objects for different versions of Sage.
55
56
Typically one only needs to implement :meth:`create_key` and
57
:meth:`create_object`.
58
"""
59
60
cdef readonly _name
61
cdef readonly _cache
62
63
def __init__(self, name):
64
"""
65
INPUT:
66
67
- ``name`` -- string. A name in the global namespace referring
68
to self or a fully qualified path name to self, which is
69
used to locate the factory on unpickling.
70
71
EXAMPLES::
72
73
sage: from sage.structure.factory import UniqueFactory
74
sage: fake_factory = UniqueFactory('ZZ')
75
sage: loads(dumps(fake_factory))
76
Integer Ring
77
sage: fake_factory = UniqueFactory('sage.rings.all.QQ')
78
sage: loads(dumps(fake_factory))
79
Rational Field
80
"""
81
self._name = name
82
self._cache = {}
83
84
def __reduce__(self):
85
"""
86
EXAMPLES::
87
88
sage: A = FiniteField(127)
89
sage: A is loads(dumps(A)) # indirect doctest
90
True
91
sage: B = FiniteField(3^3,'b')
92
sage: B is loads(dumps(B))
93
True
94
sage: C = FiniteField(2^16,'c')
95
sage: C is loads(dumps(C))
96
True
97
sage: D = FiniteField(3^20,'d')
98
sage: D is loads(dumps(D))
99
True
100
101
TESTS::
102
103
sage: loads(dumps(FiniteField)) is FiniteField
104
True
105
sage: from sage.structure.test_factory import test_factory
106
sage: loads(dumps(test_factory)) is test_factory
107
True
108
"""
109
return lookup_global, (self._name,)
110
111
def __call__(self, *args, **kwds):
112
"""
113
This is the method invoked to create objects. It first creates a key
114
from the given parameters, then if an object with that key already
115
exists returns it, and otherwise creates one and stores a weak reference
116
to it in its dictionary.
117
118
Do not override this method, override create_key and create_object and
119
put the docstring in the body of the class.
120
121
EXAMPLES::
122
123
sage: from sage.structure.test_factory import test_factory
124
sage: _ = test_factory(1,2,3); _
125
Making object (1, 2, 3)
126
<sage.structure.test_factory.A instance at ...>
127
128
It already created one, so don't re-create::
129
130
sage: test_factory(1,2,3)
131
<sage.structure.test_factory.A instance at ...>
132
sage: test_factory(1,2,3) is test_factory(1,2,3)
133
True
134
135
Of course, with a different key, a new object will be created::
136
137
sage: test_factory(1,2,3) is test_factory(1,2,4)
138
Making object (1, 2, 4)
139
False
140
"""
141
key, kwds = self.create_key_and_extra_args(*args, **kwds)
142
version = self.get_version(sage_version)
143
return self.get_object(version, key, kwds)
144
145
cpdef get_object(self, version, key, extra_args):
146
"""
147
Returns the object corresponding to key, creating it with extra_args
148
if necessary (for example, it isn't in the cache or it is unpickling
149
from an older version of Sage).
150
151
EXAMPLES::
152
153
sage: from sage.structure.test_factory import test_factory
154
sage: a = test_factory.get_object(3.0, 'a', {}); a
155
Making object a
156
<sage.structure.test_factory.A instance at ...>
157
sage: test_factory.get_object(3.0, 'a', {}) is test_factory.get_object(3.0, 'a', {})
158
True
159
sage: test_factory.get_object(3.0, 'a', {}) is test_factory.get_object(3.1, 'a', {})
160
Making object a
161
False
162
sage: test_factory.get_object(3.0, 'a', {}) is test_factory.get_object(3.0, 'b', {})
163
Making object b
164
False
165
"""
166
try:
167
obj = self._cache[version, key]()
168
if obj is not None:
169
return obj
170
except KeyError:
171
pass
172
obj = self.create_object(version, key, **extra_args)
173
self._cache[version, key] = weakref.ref(obj)
174
try:
175
other_keys = self.other_keys(key, obj)
176
for key in other_keys:
177
try:
178
new_obj = self._cache[version, key]()
179
if new_obj is not None:
180
obj = new_obj
181
break
182
except KeyError:
183
pass
184
for key in other_keys:
185
self._cache[version, key] = weakref.ref(obj)
186
obj._factory_data = self, version, key, extra_args
187
if obj.__class__.__reduce__.__objclass__ is object:
188
# replace the generic object __reduce__ to use this one
189
obj.__reduce_ex__ = types.MethodType(generic_factory_reduce, obj)
190
except AttributeError:
191
pass
192
return obj
193
194
cpdef get_version(self, sage_version):
195
"""
196
This is provided to allow more or less granular control over
197
pickle versioning. Objects pickled in the same version of Sage
198
will unpickle to the same rather than simply equal objects. This
199
can provide significant gains as arithmetic must be performed on
200
objects with identical parents. However, if there has been an
201
incompatible change (e.g. in element representation) we want the
202
version number to change so coercion is forced between the two
203
parents.
204
205
Defaults to the Sage version that is passed in, but courser
206
granularity can be provided.
207
208
EXAMPLES::
209
210
sage: from sage.structure.test_factory import test_factory
211
sage: test_factory.get_version((3,1,0))
212
(3, 1, 0)
213
"""
214
return sage_version
215
216
def create_key_and_extra_args(self, *args, **kwds):
217
r"""
218
Return a tuple containing the key (uniquely defining data)
219
and any extra arguments (empty by default).
220
221
Defaults to :meth:`create_key`.
222
223
EXAMPLES::
224
225
sage: from sage.structure.test_factory import test_factory
226
sage: test_factory.create_key_and_extra_args(1, 2, key=5)
227
((1, 2), {})
228
sage: GF.create_key_and_extra_args(3, foo='value')
229
((3, None, None, None, "{'foo': 'value'}", 3, 1, True), {'foo': 'value'})
230
"""
231
return self.create_key(*args, **kwds), {}
232
233
def create_key(self, *args, **kwds):
234
"""
235
Given the arguments and keywords, create a key that uniquely
236
determines this object.
237
238
EXAMPLES::
239
240
sage: from sage.structure.test_factory import test_factory
241
sage: test_factory.create_key(1, 2, key=5)
242
(1, 2)
243
"""
244
raise NotImplementedError
245
246
def create_object(self, version, key, **extra_args):
247
"""
248
Create the object from the key and extra arguments. This is only
249
called if the object was not found in the cache.
250
251
EXAMPLES::
252
253
sage: from sage.structure.test_factory import test_factory
254
sage: test_factory.create_object(0, (1,2,3))
255
Making object (1, 2, 3)
256
<sage.structure.test_factory.A instance at ...>
257
sage: test_factory('a')
258
Making object ('a',)
259
<sage.structure.test_factory.A instance at ...>
260
sage: test_factory('a') # NOT called again
261
<sage.structure.test_factory.A instance at ...>
262
"""
263
raise NotImplementedError
264
265
cpdef other_keys(self, key, obj):
266
"""
267
Sometimes during object creation, certain defaults are chosen which
268
may result in a new (more specific) key. This allows the more specific
269
key to be cached as well, and used for pickling.
270
271
EXAMPLES::
272
273
sage: key, _ = GF.create_key_and_extra_args(27, 'k'); key
274
(27, ('k',), 'conway', None, '{}', 3, 3, True)
275
sage: K = GF.create_object(0, key); K
276
Finite Field in k of size 3^3
277
sage: GF.other_keys(key, K)
278
[(27, ('k',), x^3 + 2*x + 1, None, '{}', 3, 3, True),
279
(27, ('k',), x^3 + 2*x + 1, 'givaro', '{}', 3, 3, True)]
280
281
sage: K = GF(7^40, 'a')
282
sage: loads(dumps(K)) is K
283
True
284
"""
285
return []
286
287
cpdef reduce_data(self, obj):
288
"""
289
The results of this function can be returned from
290
:meth:`__reduce__`. This is here so the factory internals can
291
change without having to re-write :meth:`__reduce__` methods
292
that use it.
293
294
EXAMPLE::
295
296
sage: V = FreeModule(ZZ, 5)
297
sage: factory, data = FreeModule.reduce_data(V)
298
sage: factory(*data)
299
Ambient free module of rank 5 over the principal ideal domain Integer Ring
300
sage: factory(*data) is V
301
True
302
303
sage: from sage.structure.test_factory import test_factory
304
sage: a = test_factory(1, 2)
305
Making object (1, 2)
306
sage: test_factory.reduce_data(a)
307
(<built-in function generic_factory_unpickle>,
308
(<class 'sage.structure.test_factory.UniqueFactoryTester'>,
309
(...),
310
(1, 2),
311
{}))
312
313
Note that the ellipsis ``(...)`` here stands for the Sage
314
version.
315
"""
316
return generic_factory_unpickle, obj._factory_data
317
318
319
def generic_factory_unpickle(UniqueFactory factory, *args):
320
"""
321
Method used for unpickling the object.
322
323
The unpickling mechanism needs a plain Python function to call.
324
It takes a factory as the first argument, passes the rest of the
325
arguments onto the factory's :meth:`UniqueFactory.get_object` method.
326
327
EXAMPLES::
328
329
sage: V = FreeModule(ZZ, 5)
330
sage: func, data = FreeModule.reduce_data(V)
331
sage: func is sage.structure.factory.generic_factory_unpickle
332
True
333
sage: sage.structure.factory.generic_factory_unpickle(*data) is V
334
True
335
"""
336
return factory.get_object(*args)
337
338
def generic_factory_reduce(self, proto):
339
"""
340
Used to provide a ``__reduce__`` method if one does not already exist.
341
342
EXAMPLES::
343
344
sage: V = QQ^6
345
sage: sage.structure.factory.generic_factory_reduce(V, 1) == V.__reduce_ex__(1)
346
True
347
"""
348
if self._factory_data is None:
349
raise NotImplementedError, "__reduce__ not implemented for %s" % type(self)
350
else:
351
return self._factory_data[0].reduce_data(self)
352
353
def lookup_global(name):
354
"""
355
Used in unpickling the factory itself.
356
357
EXAMPLES::
358
359
sage: from sage.structure.factory import lookup_global
360
sage: lookup_global('ZZ')
361
Integer Ring
362
sage: lookup_global('sage.rings.all.ZZ')
363
Integer Ring
364
"""
365
if '.' in name:
366
module, name = name.rsplit('.', 1)
367
all = __import__(module, fromlist=[name])
368
else:
369
import sage.all as all
370
return getattr(all, name)
371
372
373
# To make the pickle jar happy:
374
from sage.structure.test_factory import test_factory
375
376