Path: blob/master/sage/structure/unique_representation.py
4045 views
r"""1Unique Representation23Abstract class for singleton and unique representation behavior.45.. SEEALSO::67:class:`sage.structure.factory.UniqueFactory`8"""9#*****************************************************************************10# Copyright (C) 2008 Nicolas M. Thiery <nthiery at users.sf.net>11#12# Distributed under the terms of the GNU General Public License (GPL)13#14# This code is distributed in the hope that it will be useful,15# but WITHOUT ANY WARRANTY; without even the implied warranty of16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU17# General Public License for more details.18#19# The full text of the GPL is available at:20#21# http://www.gnu.org/licenses/22#******************************************************************************2324from sage.misc.cachefunc import cached_function25from sage.misc.classcall_metaclass import ClasscallMetaclass, typecall2627class UniqueRepresentation:28"""29Classes derived from UniqueRepresentation inherit a unique30representation behavior for their instances.3132EXAMPLES:3334The short story: to construct a class whose instances have a35unique representation behavior one just has to do::3637sage: class MyClass(UniqueRepresentation):38... # all the rest as usual39... pass4041Everything below is for the curious or for advanced usage.4243.. rubric:: What is unique representation?4445Instances of a class have a *unique representation behavior* when46several instances constructed with the same arguments share the47same memory representation. For example, calling twice::4849sage: f = GF(7)50sage: g = GF(7)5152to create the finite field of order 7 actually gives back the same53object::5455sage: f == g56True57sage: f is g58True5960This is a standard design pattern. Besides saving memory, it61allows for sharing cached data (say representation theoretical62information about a group) as well as for further optimizations63(fast hashing, equality testing). This behaviour is typically64desirable for parents and categories. It can also be useful for65intensive computations where one wants to cache all the operations66on a small set of elements (say the multiplication table of a67small group), and access this cache as quickly as possible.6869The :class:`UniqueRepresentation` and70:class:`~sage.structure.factory.UniqueFactory` classes71provide two alternative implementations of this design pattern. Both72implementations have their own merits. :class:`UniqueRepresentation` is73very easy to use: a class just needs to derive from it, or make sure some74of its super classes does. Also, it groups together the75class and the factory in a single gadget; in the example above, one would76want to do::7778sage: isinstance(f, GF) # todo: not implemented79True8081but this does not work, because GF is only the factory.8283On the other hand the :class:`UniqueRepresentation` class is more84intrusive, as it imposes a behavior (and a metaclass) to all the85subclasses. Its implementation is also more technical, which leads86to some subtleties.8788EXAMPLES:8990We start with a simple class whose constructor takes a single91value as argument (TODO: find a more meaningful example)::9293sage: class MyClass(UniqueRepresentation):94... def __init__(self, value):95... self.value = value96...9798Two coexisting instances of MyClass created with the same99argument data are guaranteed to share the same identity::100101sage: x = MyClass(1)102sage: y = MyClass(1)103sage: x is y104True105sage: z = MyClass(2)106sage: x is z107False108109In particular, modifying any one of them modifies the other110(reference effect)::111112sage: x.value = 3113sage: x.value, y.value114(3, 3)115sage: y.value = 1116sage: x.value, y.value117(1, 1)118119Unless overridden by the derived class, equality testing is120implemented by comparing identities, which is as fast as it can get::121122sage: x == y123True124sage: z = MyClass(2)125sage: x == z, x is z126(False, False)127128Similarly, the identity is used as hash function, which is also as129fast as it can get. However this means that the hash function may130change in between Sage sessions, or even within the same Sage131session (see below). Subclasses should overload :meth:`__hash__ <object.__hash__>`132if this could be a problem.133134135The arguments can consist of any combination of positional or136keyword arguments, as taken by a usual137:meth:`__init__ <object.__init__>`138function. However, all values passed in should be hashable::139140sage: MyClass(value = [1,2,3])141Traceback (most recent call last):142...143TypeError: unhashable type: 'list'144145.. rubric:: Argument preprocessing146147Sometimes, one wants to do some preprocessing on the arguments, to148put them in some canonical form. The following example illustrates149how to achieve this; it takes as argument any iterable, and150canonicalizes it into a tuple (which is hashable!)::151152sage: class MyClass2(UniqueRepresentation):153... @staticmethod154... def __classcall__(cls, iterable):155... t = tuple(iterable)156... return super(MyClass2, cls).__classcall__(cls, t)157...158... def __init__(self, value):159... self.value = value160...161sage: x = MyClass2([1,2,3])162sage: y = MyClass2(tuple([1,2,3]))163sage: z = MyClass2(i for i in [1,2,3])164sage: x.value165(1, 2, 3)166sage: x is y, y is z167(True, True)168169A similar situation arises when the constructor accepts default170values for some of its parameters. Alas, the obvious171implementation does not work::172173sage: class MyClass3(UniqueRepresentation):174... def __init__(self, value = 3):175... self.value = value176...177sage: MyClass3(3) is MyClass3()178False179180Instead, one should do::181182sage: class MyClass3(UniqueRepresentation):183... @staticmethod184... def __classcall__(cls, value = 3):185... return super(MyClass3, cls).__classcall__(cls, value)186...187... def __init__(self, value):188... self.value = value189...190sage: MyClass3(3) is MyClass3()191True192193A bit of explanation is in order. First, the call194``MyClass2([1,2,3])`` triggers a call to195``MyClass2.__classcall__(MyClass2, [1,2,3])``. This is an extension of196the standard Python behavior, needed by :class:`UniqueRepresentation`,197and implemented by the198:class:`~sage.misc.classcall_metaclass.ClasscallMetaclass`. Then,199``MyClass2.__classcall__`` does the desired transformations on the200arguments. Finally, it uses ``super`` to call the default201implementation of ``__classcall__`` provided by202:class:`UniqueRepresentation`. This one in turn handles the caching203and, if needed, constructs and initializes a new object in the204class using :meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` as usual.205206Constraints:207208- :meth:`__classcall__` is a staticmethod (like, implicitly, :meth:`__new__<object.__new__>`)209210- the preprocessing on the arguments should be211idempotent. Namely, If ``MyClass2.__classcall__`` calls212``UniqueRepresentation.__classcall__(<some_arguments>)``, then213it should accept <some_arguments> as its own input, and pass it214down unmodified to :meth:`UniqueRepresentation.__classcall__`.215- ``MyClass2.__classcall__`` should return the result of216:meth:`UniqueRepresentation.__classcall__` without modifying it.217218Other than that ``MyClass2.__classcall__`` may play any tricks,219like acting as a Factory and returning object from other classes.220221.. rubric:: Unique representation and mutability222223:class:`UniqueRepresentation` is primarily intended for implementing224objects which are (at least semantically) immutable. This is in225particular assumed by the default implementations of ``copy`` and226``deepcopy``::227228sage: copy(x) is x229True230sage: from copy import deepcopy231sage: deepcopy(x) is x232True233234Using :class:`UniqueRepresentation` on mutable objects may lead to235subtle behavior::236237sage: t = MyClass(3)238sage: z = MyClass(2)239sage: t.value = 2240241Now x and z have the same data structure, but are not considered242as equal::243244sage: t.value == z.value245True246sage: t == z247False248249.. rubric:: More on unique representation and identity250251:class:`UniqueRepresentation` is implemented by mean of a cache. This252cache uses weak references so that, when all other references to,253say, ``MyClass(1)`` have been deleted, the instance is actually254deleted from memory. A later call to ``MyClass(1)`` reconstructs the255instance from scratch, *most likely with a different id*.256257TODO: add an example illustrating this behavior258259260.. rubric:: Unique representation and pickling261262The default Python pickling implementation (by reconstructing an263object from its class and dictionary, see "The pickle protocol" in264the Python Library Reference) does not preserves unique265representation, as Python has no chance to know whether and where266the same object already exists.267268:class:`UniqueRepresentation` tries to ensure appropriate pickling by269implementing a :meth:`__reduce__ <object.__reduce__>` method returning the arguments270passed to the constructor::271272sage: import __main__ # Fake MyClass being defined in a python module273sage: __main__.MyClass = MyClass274sage: x = MyClass(1)275sage: loads(dumps(x)) is x276True277278:class:`UniqueRepresentation` uses the :meth:`__reduce__279<object.__reduce__>` pickle protocol rather than :meth:`__getnewargs__280<object.__getnewargs__>` because the later does not handle keyword281arguments::282283sage: x = MyClass(value = 1)284sage: x.__reduce__()285(<function unreduce at ...>, (<class '__main__.MyClass'>, (), {'value': 1}))286sage: x is loads(dumps(x))287True288289.. warning::290291the default implementation of :meth:`__reduce__ <object.__reduce__>`292in :class:`UniqueRepresentation` requires to store the constructor's293arguments in the instance dictionary upon construction::294295sage: x.__dict__296{'_reduction': (<class '__main__.MyClass'>, (), {'value': 1}), 'value': 1}297298It is often easy in a derived subclass to reconstruct the constructors299arguments from the instance data structure. When this is the case,300:meth:`__reduce__ <object.__reduce__>` should be overridden; automagically301the arguments won't be stored anymore::302303sage: class MyClass3(UniqueRepresentation):304... def __init__(self, value):305... self.value = value306...307... def __reduce__(self):308... return (MyClass3, (self.value,))309...310sage: import __main__; __main__.MyClass3 = MyClass3 # Fake MyClass3 being defined in a python module311sage: x = MyClass3(1)312sage: loads(dumps(x)) is x313True314sage: x.__dict__315{'value': 1}316317.. rubric:: Migrating classes to ``UniqueRepresentation`` and unpickling318319We check that, when migrating a class to :class:`UniqueRepresentation`,320older pickle can still be reasonably unpickled. Let us create a321(new style) class, and pickle one of its instances::322323sage: class MyClass4(object):324... def __init__(self, value):325... self.value = value326...327sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module328sage: pickle = dumps(MyClass4(1))329330It can be unpickled::331332sage: y = loads(pickle)333sage: y.value3341335336Now, we upgrade the class to derive from :class:`UniqueRepresentation`::337338sage: class MyClass4(UniqueRepresentation, object):339... def __init__(self, value):340... self.value = value341sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module342sage: __main__.MyClass4 = MyClass4343344The pickle can still be unpickled::345346sage: y = loads(pickle)347sage: y.value3481349350Note however that, for the reasons explained above, unique351representation is not guaranteed in this case::352353sage: y is MyClass4(1)354False355356Todo: illustrate how this can be fixed on a case by case basis.357358359Now, we redo the same test for a class deriving from SageObject::360361sage: class MyClass4(SageObject):362... def __init__(self, value):363... self.value = value364sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module365sage: pickle = dumps(MyClass4(1))366367sage: class MyClass4(UniqueRepresentation, SageObject):368... def __init__(self, value):369... self.value = value370sage: __main__.MyClass4 = MyClass4371sage: y = loads(pickle)372sage: y.value3731374375Caveat: unpickling instances of a formerly old-style class is not supported yet by default::376377sage: class MyClass4:378... def __init__(self, value):379... self.value = value380sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module381sage: pickle = dumps(MyClass4(1))382383sage: class MyClass4(UniqueRepresentation, SageObject):384... def __init__(self, value):385... self.value = value386sage: __main__.MyClass4 = MyClass4387sage: y = loads(pickle) # todo: not implemented388sage: y.value # todo: not implemented3891390391392393.. rubric:: Rationale for the current implementation394395:class:`UniqueRepresentation` and derived classes use the396:class:`~sage.misc.classcall_metaclass.ClasscallMetaclass`397of the standard Python type. The following example explains why.398399We define a variant of ``MyClass`` where the calls to400:meth:`__init__<object.__init__>` are traced::401402sage: class MyClass(UniqueRepresentation):403... def __init__(self, value):404... print "initializing object"405... self.value = value406...407408Let us create an object twice::409410sage: x = MyClass(1)411initializing object412sage: z = MyClass(1)413414As desired the :meth:`__init__<object.__init__>` method was only called415the first time, which is an important feature.416417As far as we can tell, this is not achievable while just using418:meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` (as419defined by type; see Section :python:`Basic Customization420<reference/datamodel.html#basic-customization>` in the Python Reference421Manual). Indeed, :meth:`__init__<object.__init__>` is called422systematically on the result of :meth:`__new__<object.__new__>` whenever423the result is an instance of the class.424425Another difficulty is that argument preprocessing (as in the example426above) cannot be handled by :meth:`__new__<object.__new__>`, since the427unprocessed arguments will be passed down to428:meth:`__init__<object.__init__>`.429430.. rubric:: Mixing super types and super classes431432TESTS:433434For the record, this test did fail with previous implementation435attempts::436437sage: class bla(UniqueRepresentation, SageObject):438... pass439...440sage: b = bla()441442"""443444__metaclass__ = ClasscallMetaclass445446_included_private_doc_ = ["__classcall__"]447448@cached_function # automatically a staticmethod449def __classcall__(cls, *args, **options):450"""451Constructs a new object of this class or reuse an existing one.452453See also :class:`UniqueRepresentation` for a discussion.454455EXAMPLES::456457sage: x = UniqueRepresentation()458sage: y = UniqueRepresentation()459sage: x is y460True461"""462instance = typecall(cls, *args, **options)463assert isinstance( instance, cls )464if instance.__class__.__reduce__ == UniqueRepresentation.__reduce__:465instance._reduction = (cls, args, options)466return instance467468# Should be cythoned469def __eq__(self, other):470"""471Test if ``self`` and ``other` are equal by comparing their472identity.473474See also :class:`UniqueRepresentation` for a discussion.475476EXAMPLES::477478sage: x = UniqueRepresentation()479sage: y = UniqueRepresentation()480sage: x == y481True482sage: x is y483True484sage: x == 3485False486487TESTS::488489sage: class bla(UniqueRepresentation, SageObject):490... def __init__(self, i): pass491sage: b1 = bla(1); b2 = bla(2)492sage: b1 == b1493True494sage: b1 == b2495False496"""497return self is other498499# Should be cythoned500def __ne__(self, other):501"""502Test if ``self`` and ``other` are different by comparing their503identity.504505See also :class:`UniqueRepresentation` for a discussion.506507EXAMPLES::508509sage: x = UniqueRepresentation()510sage: y = UniqueRepresentation()511sage: x != y512False513514TESTS::515516sage: class bla(UniqueRepresentation, SageObject):517... def __init__(self, i): pass518sage: b1 = bla(1); b2 = bla(2)519sage: b1 != b1520False521sage: b1 != b2522True523"""524return self is not other525526# Should be cythoned527def __hash__(self):528"""529Returns the hash value of ``self``.530531See also :class:`UniqueRepresentation` for a discussion.532533EXAMPLES::534535sage: x = UniqueRepresentation()536sage: y = UniqueRepresentation()537sage: hash(x) # random53874153040539sage: type(hash(x))540<type 'int'>541sage: hash(x) == hash(y)542True543sage: class bla(UniqueRepresentation, SageObject): pass544sage: x = bla(); hx = hash(x)545sage: x.rename("toto")546sage: hx == hash(x)547True548"""549return id(self)550551def __reduce__(self):552"""553Returns the argument that have been passed to :meth:`__new__<object.__new__>`554to construct this object, as per the pickle protocol.555556See also :class:`UniqueRepresentation` for a discussion.557558EXAMPLES::559560sage: x = UniqueRepresentation()561sage: x.__reduce__()562(<function unreduce at ...>, (<class 'sage.structure.unique_representation.UniqueRepresentation'>, (), {}))563"""564return (unreduce, self._reduction)565566def __copy__(self):567"""568Returns self, as a semantic copy of self569570This assume that the object is semantically immutable.571572EXAMPLES::573574sage: x = UniqueRepresentation()575sage: x is copy(x)576True577"""578return self579580def __deepcopy__(self, memo):581"""582Returns self, as a semantic deep copy of self583584This assume that the object is semantically immutable.585586EXAMPLES::587588sage: from copy import deepcopy589sage: x = UniqueRepresentation()590sage: x is deepcopy(x)591True592"""593return self594595def unreduce(cls, args, keywords):596"""597Calls a class on the given arguments::598599sage: sage.structure.unique_representation.unreduce(Integer, (1,), {})6001601602Todo: should reuse something preexisting ...603"""604return cls(*args, **keywords)605606607