Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/structure/unique_representation.py
4045 views
1
r"""
2
Unique Representation
3
4
Abstract class for singleton and unique representation behavior.
5
6
.. SEEALSO::
7
8
:class:`sage.structure.factory.UniqueFactory`
9
"""
10
#*****************************************************************************
11
# Copyright (C) 2008 Nicolas M. Thiery <nthiery at users.sf.net>
12
#
13
# Distributed under the terms of the GNU General Public License (GPL)
14
#
15
# This code is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18
# General Public License for more details.
19
#
20
# The full text of the GPL is available at:
21
#
22
# http://www.gnu.org/licenses/
23
#******************************************************************************
24
25
from sage.misc.cachefunc import cached_function
26
from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall
27
28
class UniqueRepresentation:
29
"""
30
Classes derived from UniqueRepresentation inherit a unique
31
representation behavior for their instances.
32
33
EXAMPLES:
34
35
The short story: to construct a class whose instances have a
36
unique representation behavior one just has to do::
37
38
sage: class MyClass(UniqueRepresentation):
39
... # all the rest as usual
40
... pass
41
42
Everything below is for the curious or for advanced usage.
43
44
.. rubric:: What is unique representation?
45
46
Instances of a class have a *unique representation behavior* when
47
several instances constructed with the same arguments share the
48
same memory representation. For example, calling twice::
49
50
sage: f = GF(7)
51
sage: g = GF(7)
52
53
to create the finite field of order 7 actually gives back the same
54
object::
55
56
sage: f == g
57
True
58
sage: f is g
59
True
60
61
This is a standard design pattern. Besides saving memory, it
62
allows for sharing cached data (say representation theoretical
63
information about a group) as well as for further optimizations
64
(fast hashing, equality testing). This behaviour is typically
65
desirable for parents and categories. It can also be useful for
66
intensive computations where one wants to cache all the operations
67
on a small set of elements (say the multiplication table of a
68
small group), and access this cache as quickly as possible.
69
70
The :class:`UniqueRepresentation` and
71
:class:`~sage.structure.factory.UniqueFactory` classes
72
provide two alternative implementations of this design pattern. Both
73
implementations have their own merits. :class:`UniqueRepresentation` is
74
very easy to use: a class just needs to derive from it, or make sure some
75
of its super classes does. Also, it groups together the
76
class and the factory in a single gadget; in the example above, one would
77
want to do::
78
79
sage: isinstance(f, GF) # todo: not implemented
80
True
81
82
but this does not work, because GF is only the factory.
83
84
On the other hand the :class:`UniqueRepresentation` class is more
85
intrusive, as it imposes a behavior (and a metaclass) to all the
86
subclasses. Its implementation is also more technical, which leads
87
to some subtleties.
88
89
EXAMPLES:
90
91
We start with a simple class whose constructor takes a single
92
value as argument (TODO: find a more meaningful example)::
93
94
sage: class MyClass(UniqueRepresentation):
95
... def __init__(self, value):
96
... self.value = value
97
...
98
99
Two coexisting instances of MyClass created with the same
100
argument data are guaranteed to share the same identity::
101
102
sage: x = MyClass(1)
103
sage: y = MyClass(1)
104
sage: x is y
105
True
106
sage: z = MyClass(2)
107
sage: x is z
108
False
109
110
In particular, modifying any one of them modifies the other
111
(reference effect)::
112
113
sage: x.value = 3
114
sage: x.value, y.value
115
(3, 3)
116
sage: y.value = 1
117
sage: x.value, y.value
118
(1, 1)
119
120
Unless overridden by the derived class, equality testing is
121
implemented by comparing identities, which is as fast as it can get::
122
123
sage: x == y
124
True
125
sage: z = MyClass(2)
126
sage: x == z, x is z
127
(False, False)
128
129
Similarly, the identity is used as hash function, which is also as
130
fast as it can get. However this means that the hash function may
131
change in between Sage sessions, or even within the same Sage
132
session (see below). Subclasses should overload :meth:`__hash__ <object.__hash__>`
133
if this could be a problem.
134
135
136
The arguments can consist of any combination of positional or
137
keyword arguments, as taken by a usual
138
:meth:`__init__ <object.__init__>`
139
function. However, all values passed in should be hashable::
140
141
sage: MyClass(value = [1,2,3])
142
Traceback (most recent call last):
143
...
144
TypeError: unhashable type: 'list'
145
146
.. rubric:: Argument preprocessing
147
148
Sometimes, one wants to do some preprocessing on the arguments, to
149
put them in some canonical form. The following example illustrates
150
how to achieve this; it takes as argument any iterable, and
151
canonicalizes it into a tuple (which is hashable!)::
152
153
sage: class MyClass2(UniqueRepresentation):
154
... @staticmethod
155
... def __classcall__(cls, iterable):
156
... t = tuple(iterable)
157
... return super(MyClass2, cls).__classcall__(cls, t)
158
...
159
... def __init__(self, value):
160
... self.value = value
161
...
162
sage: x = MyClass2([1,2,3])
163
sage: y = MyClass2(tuple([1,2,3]))
164
sage: z = MyClass2(i for i in [1,2,3])
165
sage: x.value
166
(1, 2, 3)
167
sage: x is y, y is z
168
(True, True)
169
170
A similar situation arises when the constructor accepts default
171
values for some of its parameters. Alas, the obvious
172
implementation does not work::
173
174
sage: class MyClass3(UniqueRepresentation):
175
... def __init__(self, value = 3):
176
... self.value = value
177
...
178
sage: MyClass3(3) is MyClass3()
179
False
180
181
Instead, one should do::
182
183
sage: class MyClass3(UniqueRepresentation):
184
... @staticmethod
185
... def __classcall__(cls, value = 3):
186
... return super(MyClass3, cls).__classcall__(cls, value)
187
...
188
... def __init__(self, value):
189
... self.value = value
190
...
191
sage: MyClass3(3) is MyClass3()
192
True
193
194
A bit of explanation is in order. First, the call
195
``MyClass2([1,2,3])`` triggers a call to
196
``MyClass2.__classcall__(MyClass2, [1,2,3])``. This is an extension of
197
the standard Python behavior, needed by :class:`UniqueRepresentation`,
198
and implemented by the
199
:class:`~sage.misc.classcall_metaclass.ClasscallMetaclass`. Then,
200
``MyClass2.__classcall__`` does the desired transformations on the
201
arguments. Finally, it uses ``super`` to call the default
202
implementation of ``__classcall__`` provided by
203
:class:`UniqueRepresentation`. This one in turn handles the caching
204
and, if needed, constructs and initializes a new object in the
205
class using :meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` as usual.
206
207
Constraints:
208
209
- :meth:`__classcall__` is a staticmethod (like, implicitly, :meth:`__new__<object.__new__>`)
210
211
- the preprocessing on the arguments should be
212
idempotent. Namely, If ``MyClass2.__classcall__`` calls
213
``UniqueRepresentation.__classcall__(<some_arguments>)``, then
214
it should accept <some_arguments> as its own input, and pass it
215
down unmodified to :meth:`UniqueRepresentation.__classcall__`.
216
- ``MyClass2.__classcall__`` should return the result of
217
:meth:`UniqueRepresentation.__classcall__` without modifying it.
218
219
Other than that ``MyClass2.__classcall__`` may play any tricks,
220
like acting as a Factory and returning object from other classes.
221
222
.. rubric:: Unique representation and mutability
223
224
:class:`UniqueRepresentation` is primarily intended for implementing
225
objects which are (at least semantically) immutable. This is in
226
particular assumed by the default implementations of ``copy`` and
227
``deepcopy``::
228
229
sage: copy(x) is x
230
True
231
sage: from copy import deepcopy
232
sage: deepcopy(x) is x
233
True
234
235
Using :class:`UniqueRepresentation` on mutable objects may lead to
236
subtle behavior::
237
238
sage: t = MyClass(3)
239
sage: z = MyClass(2)
240
sage: t.value = 2
241
242
Now x and z have the same data structure, but are not considered
243
as equal::
244
245
sage: t.value == z.value
246
True
247
sage: t == z
248
False
249
250
.. rubric:: More on unique representation and identity
251
252
:class:`UniqueRepresentation` is implemented by mean of a cache. This
253
cache uses weak references so that, when all other references to,
254
say, ``MyClass(1)`` have been deleted, the instance is actually
255
deleted from memory. A later call to ``MyClass(1)`` reconstructs the
256
instance from scratch, *most likely with a different id*.
257
258
TODO: add an example illustrating this behavior
259
260
261
.. rubric:: Unique representation and pickling
262
263
The default Python pickling implementation (by reconstructing an
264
object from its class and dictionary, see "The pickle protocol" in
265
the Python Library Reference) does not preserves unique
266
representation, as Python has no chance to know whether and where
267
the same object already exists.
268
269
:class:`UniqueRepresentation` tries to ensure appropriate pickling by
270
implementing a :meth:`__reduce__ <object.__reduce__>` method returning the arguments
271
passed to the constructor::
272
273
sage: import __main__ # Fake MyClass being defined in a python module
274
sage: __main__.MyClass = MyClass
275
sage: x = MyClass(1)
276
sage: loads(dumps(x)) is x
277
True
278
279
:class:`UniqueRepresentation` uses the :meth:`__reduce__
280
<object.__reduce__>` pickle protocol rather than :meth:`__getnewargs__
281
<object.__getnewargs__>` because the later does not handle keyword
282
arguments::
283
284
sage: x = MyClass(value = 1)
285
sage: x.__reduce__()
286
(<function unreduce at ...>, (<class '__main__.MyClass'>, (), {'value': 1}))
287
sage: x is loads(dumps(x))
288
True
289
290
.. warning::
291
292
the default implementation of :meth:`__reduce__ <object.__reduce__>`
293
in :class:`UniqueRepresentation` requires to store the constructor's
294
arguments in the instance dictionary upon construction::
295
296
sage: x.__dict__
297
{'_reduction': (<class '__main__.MyClass'>, (), {'value': 1}), 'value': 1}
298
299
It is often easy in a derived subclass to reconstruct the constructors
300
arguments from the instance data structure. When this is the case,
301
:meth:`__reduce__ <object.__reduce__>` should be overridden; automagically
302
the arguments won't be stored anymore::
303
304
sage: class MyClass3(UniqueRepresentation):
305
... def __init__(self, value):
306
... self.value = value
307
...
308
... def __reduce__(self):
309
... return (MyClass3, (self.value,))
310
...
311
sage: import __main__; __main__.MyClass3 = MyClass3 # Fake MyClass3 being defined in a python module
312
sage: x = MyClass3(1)
313
sage: loads(dumps(x)) is x
314
True
315
sage: x.__dict__
316
{'value': 1}
317
318
.. rubric:: Migrating classes to ``UniqueRepresentation`` and unpickling
319
320
We check that, when migrating a class to :class:`UniqueRepresentation`,
321
older pickle can still be reasonably unpickled. Let us create a
322
(new style) class, and pickle one of its instances::
323
324
sage: class MyClass4(object):
325
... def __init__(self, value):
326
... self.value = value
327
...
328
sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module
329
sage: pickle = dumps(MyClass4(1))
330
331
It can be unpickled::
332
333
sage: y = loads(pickle)
334
sage: y.value
335
1
336
337
Now, we upgrade the class to derive from :class:`UniqueRepresentation`::
338
339
sage: class MyClass4(UniqueRepresentation, object):
340
... def __init__(self, value):
341
... self.value = value
342
sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module
343
sage: __main__.MyClass4 = MyClass4
344
345
The pickle can still be unpickled::
346
347
sage: y = loads(pickle)
348
sage: y.value
349
1
350
351
Note however that, for the reasons explained above, unique
352
representation is not guaranteed in this case::
353
354
sage: y is MyClass4(1)
355
False
356
357
Todo: illustrate how this can be fixed on a case by case basis.
358
359
360
Now, we redo the same test for a class deriving from SageObject::
361
362
sage: class MyClass4(SageObject):
363
... def __init__(self, value):
364
... self.value = value
365
sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module
366
sage: pickle = dumps(MyClass4(1))
367
368
sage: class MyClass4(UniqueRepresentation, SageObject):
369
... def __init__(self, value):
370
... self.value = value
371
sage: __main__.MyClass4 = MyClass4
372
sage: y = loads(pickle)
373
sage: y.value
374
1
375
376
Caveat: unpickling instances of a formerly old-style class is not supported yet by default::
377
378
sage: class MyClass4:
379
... def __init__(self, value):
380
... self.value = value
381
sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module
382
sage: pickle = dumps(MyClass4(1))
383
384
sage: class MyClass4(UniqueRepresentation, SageObject):
385
... def __init__(self, value):
386
... self.value = value
387
sage: __main__.MyClass4 = MyClass4
388
sage: y = loads(pickle) # todo: not implemented
389
sage: y.value # todo: not implemented
390
1
391
392
393
394
.. rubric:: Rationale for the current implementation
395
396
:class:`UniqueRepresentation` and derived classes use the
397
:class:`~sage.misc.classcall_metaclass.ClasscallMetaclass`
398
of the standard Python type. The following example explains why.
399
400
We define a variant of ``MyClass`` where the calls to
401
:meth:`__init__<object.__init__>` are traced::
402
403
sage: class MyClass(UniqueRepresentation):
404
... def __init__(self, value):
405
... print "initializing object"
406
... self.value = value
407
...
408
409
Let us create an object twice::
410
411
sage: x = MyClass(1)
412
initializing object
413
sage: z = MyClass(1)
414
415
As desired the :meth:`__init__<object.__init__>` method was only called
416
the first time, which is an important feature.
417
418
As far as we can tell, this is not achievable while just using
419
:meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` (as
420
defined by type; see Section :python:`Basic Customization
421
<reference/datamodel.html#basic-customization>` in the Python Reference
422
Manual). Indeed, :meth:`__init__<object.__init__>` is called
423
systematically on the result of :meth:`__new__<object.__new__>` whenever
424
the result is an instance of the class.
425
426
Another difficulty is that argument preprocessing (as in the example
427
above) cannot be handled by :meth:`__new__<object.__new__>`, since the
428
unprocessed arguments will be passed down to
429
:meth:`__init__<object.__init__>`.
430
431
.. rubric:: Mixing super types and super classes
432
433
TESTS:
434
435
For the record, this test did fail with previous implementation
436
attempts::
437
438
sage: class bla(UniqueRepresentation, SageObject):
439
... pass
440
...
441
sage: b = bla()
442
443
"""
444
445
__metaclass__ = ClasscallMetaclass
446
447
_included_private_doc_ = ["__classcall__"]
448
449
@cached_function # automatically a staticmethod
450
def __classcall__(cls, *args, **options):
451
"""
452
Constructs a new object of this class or reuse an existing one.
453
454
See also :class:`UniqueRepresentation` for a discussion.
455
456
EXAMPLES::
457
458
sage: x = UniqueRepresentation()
459
sage: y = UniqueRepresentation()
460
sage: x is y
461
True
462
"""
463
instance = typecall(cls, *args, **options)
464
assert isinstance( instance, cls )
465
if instance.__class__.__reduce__ == UniqueRepresentation.__reduce__:
466
instance._reduction = (cls, args, options)
467
return instance
468
469
# Should be cythoned
470
def __eq__(self, other):
471
"""
472
Test if ``self`` and ``other` are equal by comparing their
473
identity.
474
475
See also :class:`UniqueRepresentation` for a discussion.
476
477
EXAMPLES::
478
479
sage: x = UniqueRepresentation()
480
sage: y = UniqueRepresentation()
481
sage: x == y
482
True
483
sage: x is y
484
True
485
sage: x == 3
486
False
487
488
TESTS::
489
490
sage: class bla(UniqueRepresentation, SageObject):
491
... def __init__(self, i): pass
492
sage: b1 = bla(1); b2 = bla(2)
493
sage: b1 == b1
494
True
495
sage: b1 == b2
496
False
497
"""
498
return self is other
499
500
# Should be cythoned
501
def __ne__(self, other):
502
"""
503
Test if ``self`` and ``other` are different by comparing their
504
identity.
505
506
See also :class:`UniqueRepresentation` for a discussion.
507
508
EXAMPLES::
509
510
sage: x = UniqueRepresentation()
511
sage: y = UniqueRepresentation()
512
sage: x != y
513
False
514
515
TESTS::
516
517
sage: class bla(UniqueRepresentation, SageObject):
518
... def __init__(self, i): pass
519
sage: b1 = bla(1); b2 = bla(2)
520
sage: b1 != b1
521
False
522
sage: b1 != b2
523
True
524
"""
525
return self is not other
526
527
# Should be cythoned
528
def __hash__(self):
529
"""
530
Returns the hash value of ``self``.
531
532
See also :class:`UniqueRepresentation` for a discussion.
533
534
EXAMPLES::
535
536
sage: x = UniqueRepresentation()
537
sage: y = UniqueRepresentation()
538
sage: hash(x) # random
539
74153040
540
sage: type(hash(x))
541
<type 'int'>
542
sage: hash(x) == hash(y)
543
True
544
sage: class bla(UniqueRepresentation, SageObject): pass
545
sage: x = bla(); hx = hash(x)
546
sage: x.rename("toto")
547
sage: hx == hash(x)
548
True
549
"""
550
return id(self)
551
552
def __reduce__(self):
553
"""
554
Returns the argument that have been passed to :meth:`__new__<object.__new__>`
555
to construct this object, as per the pickle protocol.
556
557
See also :class:`UniqueRepresentation` for a discussion.
558
559
EXAMPLES::
560
561
sage: x = UniqueRepresentation()
562
sage: x.__reduce__()
563
(<function unreduce at ...>, (<class 'sage.structure.unique_representation.UniqueRepresentation'>, (), {}))
564
"""
565
return (unreduce, self._reduction)
566
567
def __copy__(self):
568
"""
569
Returns self, as a semantic copy of self
570
571
This assume that the object is semantically immutable.
572
573
EXAMPLES::
574
575
sage: x = UniqueRepresentation()
576
sage: x is copy(x)
577
True
578
"""
579
return self
580
581
def __deepcopy__(self, memo):
582
"""
583
Returns self, as a semantic deep copy of self
584
585
This assume that the object is semantically immutable.
586
587
EXAMPLES::
588
589
sage: from copy import deepcopy
590
sage: x = UniqueRepresentation()
591
sage: x is deepcopy(x)
592
True
593
"""
594
return self
595
596
def unreduce(cls, args, keywords):
597
"""
598
Calls a class on the given arguments::
599
600
sage: sage.structure.unique_representation.unreduce(Integer, (1,), {})
601
1
602
603
Todo: should reuse something preexisting ...
604
"""
605
return cls(*args, **keywords)
606
607