class Classcall(six.with_metaclass(ClasscallMetaclass)): @staticmethod def __classcall__(cls, *args, **options): instance = typecall(cls, *args, **options) assert isinstance(instance, cls) instance._init_args = (cls, args, options) return instance
class Outer(six.with_metaclass(NestedClassMetaclass)): """ A class with a bindable nested class, for testing purposes """ class Inner(BindableClass): """ Some documentation for Outer.Inner """ Inner2 = Inner2
class TrivialUniqueRepresentation(six.with_metaclass(TrivialClasscallMetaClass)): """ A trivial version of :class:`UniqueRepresentation` without Cython dependencies. """ @staticmethod def __classcall__(cls, *args, **options): """ Construct a new object of this class or reuse an existing one. """ key = (cls, tuple(args), frozenset(options.items())) cached = _trivial_unique_representation_cache.get(key, None) if cached is None: cached = _trivial_unique_representation_cache[key] = type.__call__(cls, *args, **options) return cached
class CachedRepresentation(six.with_metaclass(ClasscallMetaclass)): """ Classes derived from CachedRepresentation inherit a weak cache for their instances. .. NOTE:: If this class is used as a base class, then instances are (weakly) cached, according to the arguments used to create the instance. Pickling is provided, of course by using the cache. .. NOTE:: Using this class, one can have arbitrary hash and comparison. Hence, *unique* representation behaviour is *not* provided. .. SEEALSO:: :class:`UniqueRepresentation`, :mod:`~sage.structure.unique_representation` EXAMPLES: Providing a class with a weak cache for the instances is easy: Just inherit from :class:`CachedRepresentation`:: sage: from sage.structure.unique_representation import CachedRepresentation sage: class MyClass(CachedRepresentation): ....: # all the rest as usual ....: pass We start with a simple class whose constructor takes a single value as argument (TODO: find a more meaningful example):: sage: class MyClass(CachedRepresentation): ....: def __init__(self, value): ....: self.value = value ....: def __eq__(self, other): ....: if type(self) != type(other): ....: return False ....: return self.value == other.value Two coexisting instances of ``MyClass`` created with the same argument data are guaranteed to share the same identity. Since :trac:`12215`, this is only the case if there is some strong reference to the returned instance, since otherwise it may be garbage collected:: sage: x = MyClass(1) sage: y = MyClass(1) sage: x is y # There is a strong reference True sage: z = MyClass(2) sage: x is z False In particular, modifying any one of them modifies the other (reference effect):: sage: x.value = 3 sage: x.value, y.value (3, 3) sage: y.value = 1 sage: x.value, y.value (1, 1) The arguments can consist of any combination of positional or keyword arguments, as taken by a usual :meth:`__init__ <object.__init__>` function. However, all values passed in should be hashable:: sage: MyClass(value = [1,2,3]) Traceback (most recent call last): ... TypeError: unhashable type: 'list' .. rubric:: Argument preprocessing Sometimes, one wants to do some preprocessing on the arguments, to put them in some canonical form. The following example illustrates how to achieve this; it takes as argument any iterable, and canonicalizes it into a tuple (which is hashable!):: sage: class MyClass2(CachedRepresentation): ....: @staticmethod ....: def __classcall__(cls, iterable): ....: t = tuple(iterable) ....: return super(MyClass2, cls).__classcall__(cls, t) ....: ....: def __init__(self, value): ....: self.value = value ....: sage: x = MyClass2([1,2,3]) sage: y = MyClass2(tuple([1,2,3])) sage: z = MyClass2(i for i in [1,2,3]) sage: x.value (1, 2, 3) sage: x is y, y is z (True, True) A similar situation arises when the constructor accepts default values for some of its parameters. Alas, the obvious implementation does not work:: sage: class MyClass3(CachedRepresentation): ....: def __init__(self, value = 3): ....: self.value = value ....: sage: MyClass3(3) is MyClass3() False Instead, one should do:: sage: class MyClass3(UniqueRepresentation): ....: @staticmethod ....: def __classcall__(cls, value = 3): ....: return super(MyClass3, cls).__classcall__(cls, value) ....: ....: def __init__(self, value): ....: self.value = value ....: sage: MyClass3(3) is MyClass3() True A bit of explanation is in order. First, the call ``MyClass2([1,2,3])`` triggers a call to ``MyClass2.__classcall__(MyClass2, [1,2,3])``. This is an extension of the standard Python behavior, needed by :class:`CachedRepresentation`, and implemented by the :class:`~sage.misc.classcall_metaclass.ClasscallMetaclass`. Then, ``MyClass2.__classcall__`` does the desired transformations on the arguments. Finally, it uses ``super`` to call the default implementation of ``__classcall__`` provided by :class:`CachedRepresentation`. This one in turn handles the caching and, if needed, constructs and initializes a new object in the class using :meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` as usual. Constraints: - :meth:`__classcall__` is a staticmethod (like, implicitly, :meth:`__new__<object.__new__>`) - the preprocessing on the arguments should be idempotent. That is, if ``MyClass2.__classcall__(<arguments>)`` calls ``CachedRepresentation.__classcall__(<preprocessed_arguments>)``, then ``MyClass2.__classcall__(<preprocessed_arguments>)`` should also result in a call to ``CachedRepresentation.__classcall__(<preprocessed_arguments>)``. - ``MyClass2.__classcall__`` should return the result of :meth:`CachedRepresentation.__classcall__` without modifying it. Other than that ``MyClass2.__classcall__`` may play any tricks, like acting as a factory and returning objects from other classes. .. WARNING:: It is possible, but strongly discouraged, to let the ``__classcall__`` method of a class ``C`` return objects that are not instances of ``C``. Of course, instances of a *subclass* of ``C`` are fine. Compare the examples in :mod:`~sage.structure.unique_representation`. We illustrate what is meant by an "idempotent" preprocessing. Imagine that one has instances that are created with an integer-valued argument, but only depend on the *square* of the argument. It would be a mistake to square the given argument during preprocessing:: sage: class WrongUsage(CachedRepresentation): ....: @staticmethod ....: def __classcall__(cls, n): ....: return super(WrongUsage,cls).__classcall__(cls, n^2) ....: def __init__(self, n): ....: self.n = n ....: def __repr__(self): ....: return "Something(%d)"%self.n ....: sage: import __main__ sage: __main__.WrongUsage = WrongUsage # This is only needed in doctests sage: w = WrongUsage(3); w Something(9) sage: w._reduction (<class '__main__.WrongUsage'>, (9,), {}) Indeed, the reduction data are obtained from the preprocessed arguments. By consequence, if the resulting instance is pickled and unpickled, the argument gets squared *again*:: sage: loads(dumps(w)) Something(81) Instead, the preprocessing should only take the absolute value of the given argument, while the squaring should happen inside of the ``__init__`` method, where it won't mess with the cache:: sage: class BetterUsage(CachedRepresentation): ....: @staticmethod ....: def __classcall__(cls, n): ....: return super(BetterUsage, cls).__classcall__(cls, abs(n)) ....: def __init__(self, n): ....: self.n = n^2 ....: def __repr__(self): ....: return "SomethingElse(%d)"%self.n ....: sage: __main__.BetterUsage = BetterUsage # This is only needed in doctests sage: b = BetterUsage(3); b SomethingElse(9) sage: loads(dumps(b)) is b True sage: b is BetterUsage(-3) True .. rubric:: Cached representation and mutability :class:`CachedRepresentation` is primarily intended for implementing objects which are (at least semantically) immutable. This is in particular assumed by the default implementations of ``copy`` and ``deepcopy``:: sage: copy(x) is x True sage: from copy import deepcopy sage: deepcopy(x) is x True However, in contrast to :class:`UniqueRepresentation`, using :class:`CachedRepresentation` allows for a comparison that is not by identity:: sage: t = MyClass(3) sage: z = MyClass(2) sage: t.value = 2 Now ``t`` and ``z`` are non-identical, but equal:: sage: t.value == z.value True sage: t == z True sage: t is z False .. rubric:: More on cached representation and identity :class:`CachedRepresentation` is implemented by means of a cache. This cache uses weak references in general, but strong references to the most recently created objects. Hence, when all other references to, say, ``MyClass(1)`` have been deleted, the instance is eventually deleted from memory (after enough other objects have been created to remove the strong reference to ``MyClass(1)``). A later call to ``MyClass(1)`` reconstructs the instance from scratch:: sage: class SomeClass(UniqueRepresentation): ....: def __init__(self, i): ....: print("creating new instance for argument %s" % i) ....: self.i = i ....: def __del__(self): ....: print("deleting instance for argument %s" % self.i) sage: class OtherClass(UniqueRepresentation): ....: def __init__(self, i): ....: pass sage: O = SomeClass(1) creating new instance for argument 1 sage: O is SomeClass(1) True sage: O is SomeClass(2) creating new instance for argument 2 False sage: L = [OtherClass(i) for i in range(200)] deleting instance for argument 2 sage: del O deleting instance for argument 1 sage: O = SomeClass(1) creating new instance for argument 1 sage: del O sage: del L sage: L = [OtherClass(i) for i in range(200)] deleting instance for argument 1 .. rubric:: Cached representation and pickling The default Python pickling implementation (by reconstructing an object from its class and dictionary, see "The pickle protocol" in the Python Library Reference) does not preserve cached representation, as Python has no chance to know whether and where the same object already exists. :class:`CachedRepresentation` tries to ensure appropriate pickling by implementing a :meth:`__reduce__ <object.__reduce__>` method returning the arguments passed to the constructor:: sage: import __main__ # Fake MyClass being defined in a python module sage: __main__.MyClass = MyClass sage: x = MyClass(1) sage: loads(dumps(x)) is x True :class:`CachedRepresentation` uses the :meth:`__reduce__ <object.__reduce__>` pickle protocol rather than :meth:`__getnewargs__ <object.__getnewargs__>` because the latter does not handle keyword arguments:: sage: x = MyClass(value = 1) sage: x.__reduce__() (<function unreduce at ...>, (<class '__main__.MyClass'>, (), {'value': 1})) sage: x is loads(dumps(x)) True .. NOTE:: The default implementation of :meth:`__reduce__ <object.__reduce__>` in :class:`CachedRepresentation` requires to store the constructor's arguments in the instance dictionary upon construction:: sage: x.__dict__ {'_reduction': (<class '__main__.MyClass'>, (), {'value': 1}), 'value': 1} It is often easy in a derived subclass to reconstruct the constructor's arguments from the instance data structure. When this is the case, :meth:`__reduce__ <object.__reduce__>` should be overridden; automagically the arguments won't be stored anymore:: sage: class MyClass3(UniqueRepresentation): ....: def __init__(self, value): ....: self.value = value ....: ....: def __reduce__(self): ....: return (MyClass3, (self.value,)) ....: sage: import __main__; __main__.MyClass3 = MyClass3 # Fake MyClass3 being defined in a python module sage: x = MyClass3(1) sage: loads(dumps(x)) is x True sage: x.__dict__ {'value': 1} .. rubric:: Migrating classes to ``CachedRepresentation`` and unpickling We check that, when migrating a class to :class:`CachedRepresentation`, older pickles can still be reasonably unpickled. Let us create a (new style) class, and pickle one of its instances:: sage: class MyClass4(object): ....: def __init__(self, value): ....: self.value = value ....: sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module sage: pickle = dumps(MyClass4(1)) It can be unpickled:: sage: y = loads(pickle) sage: y.value 1 Now, we upgrade the class to derive from :class:`UniqueRepresentation`, which inherits from :class:`CachedRepresentation`:: sage: class MyClass4(UniqueRepresentation, object): ....: def __init__(self, value): ....: self.value = value sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module sage: __main__.MyClass4 = MyClass4 The pickle can still be unpickled:: sage: y = loads(pickle) sage: y.value 1 Note however that, for the reasons explained above, unique representation is not guaranteed in this case:: sage: y is MyClass4(1) False .. TODO:: Illustrate how this can be fixed on a case by case basis. Now, we redo the same test for a class deriving from SageObject:: sage: class MyClass4(SageObject): ....: def __init__(self, value): ....: self.value = value sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module sage: pickle = dumps(MyClass4(1)) sage: class MyClass4(UniqueRepresentation, SageObject): ....: def __init__(self, value): ....: self.value = value sage: __main__.MyClass4 = MyClass4 sage: y = loads(pickle) sage: y.value 1 Caveat: unpickling instances of a formerly old-style class is not supported yet by default:: sage: class MyClass4: ....: def __init__(self, value): ....: self.value = value sage: import __main__; __main__.MyClass4 = MyClass4 # Fake MyClass4 being defined in a python module sage: pickle = dumps(MyClass4(1)) sage: class MyClass4(UniqueRepresentation, SageObject): ....: def __init__(self, value): ....: self.value = value sage: __main__.MyClass4 = MyClass4 sage: y = loads(pickle) # todo: not implemented sage: y.value # todo: not implemented 1 .. rubric:: Rationale for the current implementation :class:`CachedRepresentation` and derived classes use the :class:`~sage.misc.classcall_metaclass.ClasscallMetaclass` of the standard Python type. The following example explains why. We define a variant of ``MyClass`` where the calls to :meth:`__init__<object.__init__>` are traced:: sage: class MyClass(CachedRepresentation): ....: def __init__(self, value): ....: print("initializing object") ....: self.value = value ....: Let us create an object twice:: sage: x = MyClass(1) initializing object sage: z = MyClass(1) As desired the :meth:`__init__<object.__init__>` method was only called the first time, which is an important feature. As far as we can tell, this is not achievable while just using :meth:`__new__<object.__new__>` and :meth:`__init__<object.__init__>` (as defined by type; see Section :python:`Basic Customization <reference/datamodel.html#basic-customization>` in the Python Reference Manual). Indeed, :meth:`__init__<object.__init__>` is called systematically on the result of :meth:`__new__<object.__new__>` whenever the result is an instance of the class. Another difficulty is that argument preprocessing (as in the example above) cannot be handled by :meth:`__new__<object.__new__>`, since the unprocessed arguments will be passed down to :meth:`__init__<object.__init__>`. """ @weak_cached_function(cache=128) # automatically a staticmethod def __classcall__(cls, *args, **options): """ Construct a new object of this class or reuse an existing one. See also :class:`CachedRepresentation` and :class:`UniqueRepresentation` for a discussion. EXAMPLES:: sage: x = UniqueRepresentation() sage: y = UniqueRepresentation() sage: x is y # indirect doctest True """ instance = typecall(cls, *args, **options) assert isinstance(instance, cls) if instance.__class__.__reduce__ == CachedRepresentation.__reduce__: instance._reduction = (cls, args, options) return instance @classmethod def _clear_cache_(cls): """ Remove all instances of this class from the cache. EXAMPLES: If ``cls`` overloads :meth:`~sage.structure.unique_representation.CachedRepresentation.__classcall__`, clearing the cache still works, because ``cls.mro()`` is searched until a ``__classcall__`` with an attribute ``cache`` is found:: sage: class A(UniqueRepresentation): ....: def __init__(self, x): ....: pass sage: class B(A): ....: @staticmethod ....: def __classcall__(cls, *args, **kwds): ....: return super(B,cls).__classcall__(cls,*args,**kwds) sage: class C(B): pass sage: a = A(1) sage: b = B(2) sage: c = C(3) sage: a is A(1) True sage: b is B(2) True sage: c is C(3) True sage: B._clear_cache_() Now, all instances of (sub-classes of) ``B`` have disappeared from the cache:: sage: a is A(1) True sage: b is B(2) False sage: c is C(3) False Here is a similar example, using a private classcall in the class ``B``, which is not inherited by ``C``:: sage: class A(UniqueRepresentation): ....: def __init__(self, x): ....: pass sage: class B(A): ....: @staticmethod ....: def __classcall_private__(cls, *args, **kwds): ....: print("Private B") ....: return super(B,cls).__classcall__(cls,*args,**kwds) sage: class C(B): pass sage: a = A(1) sage: b = B(2) Private B sage: c = C(3) sage: a is A(1) True sage: b is B(2) Private B True sage: c is C(3) True sage: B._clear_cache_() Again, all instances of (sub-classes of) ``B`` have disappeared from the cache:: sage: a is A(1) True sage: b is B(2) Private B False sage: c is C(3) False """ del_list = [] cache = None for C in cls.mro(): try: cache = C.__classcall__.cache except AttributeError: pass for k in cache: if issubclass(k[0][0], cls): del_list.append(k) for k in del_list: del cache[k] def __reduce__(self): """ Return the arguments that have been passed to :meth:`__new__<object.__new__>` to construct this object, as per the pickle protocol. See also :class:`CachedRepresentation` and :class:`UniqueRepresentation` for a discussion. EXAMPLES:: sage: x = UniqueRepresentation() sage: x.__reduce__() # indirect doctest (<function unreduce at ...>, (<class 'sage.structure.unique_representation.UniqueRepresentation'>, (), {})) """ return (unreduce, self._reduction) def __copy__(self): """ Return ``self``, as a semantic copy of ``self``. This assumes that the object is semantically immutable. EXAMPLES:: sage: x = UniqueRepresentation() sage: x is copy(x) # indirect doctest True """ return self def __deepcopy__(self, memo): """ Return ``self``, as a semantic deep copy of ``self``. This assumes that the object is semantically immutable. EXAMPLES:: sage: from copy import deepcopy sage: x = UniqueRepresentation() sage: x is deepcopy(x) # indirect doctest True """ return self
class BindableClass(six.with_metaclass(ClasscallMetaclass)): """ Bindable classes This class implements a binding behavior for nested classes that derive from it. Namely, if a nested class ``Outer.Inner`` derives from ``BindableClass``, and if ``outer`` is an instance of ``Outer``, then ``outer.Inner(...)`` is equivalent to ``Outer.Inner(outer, ...)``. EXAMPLES: Let us consider the following class ``Outer`` with a nested class ``Inner``:: sage: from sage.misc.nested_class import NestedClassMetaclass sage: class Outer: ....: __metaclass__ = NestedClassMetaclass # just a workaround for Python misnaming nested classes ... ....: class Inner: ....: def __init__(self, *args): ....: print(args) ... ....: def f(self, *args): ....: print("{} {}".format(self, args)) ... ....: @staticmethod ....: def f_static(*args): ....: print(args) ... sage: outer = Outer() By default, when ``Inner`` is a class nested in ``Outer``, accessing ``outer.Inner`` returns the ``Inner`` class as is:: sage: outer.Inner is Outer.Inner True In particular, ``outer`` is completely ignored in the following call:: sage: x = outer.Inner(1,2,3) (1, 2, 3) This is similar to what happens with a static method:: sage: outer.f_static(1,2,3) (1, 2, 3) In some cases, we would want instead ``Inner``` to receive ``outer`` as parameter, like in a usual method call:: sage: outer.f(1,2,3) <__main__.Outer object at ...> (1, 2, 3) To this end, ``outer.f`` returns a *bound method*:: sage: outer.f <bound method Outer.f of <__main__.Outer object at ...>> so that ``outer.f(1,2,3)`` is equivalent to:: sage: Outer.f(outer, 1,2,3) <__main__.Outer object at ...> (1, 2, 3) :class:`BindableClass` gives this binding behavior to all its subclasses:: sage: from sage.misc.bindable_class import BindableClass sage: class Outer: ....: __metaclass__ = NestedClassMetaclass # just a workaround for Python misnaming nested classes ... ....: class Inner(BindableClass): ....: " some documentation " ....: def __init__(self, outer, *args): ....: print("{} {}".format(outer, args)) Calling ``Outer.Inner`` returns the (unbound) class as usual:: sage: Outer.Inner <class '__main__.Outer.Inner'> However, ``outer.Inner(1,2,3)`` is equivalent to ``Outer.Inner(outer, 1,2,3)``:: sage: outer = Outer() sage: x = outer.Inner(1,2,3) <__main__.Outer object at ...> (1, 2, 3) To achieve this, ``outer.Inner`` returns (some sort of) bound class:: sage: outer.Inner <bound class '__main__.Outer.Inner' of <__main__.Outer object at ...>> .. note:: This is not actually a class, but an instance of :class:`functools.partial`:: sage: type(outer.Inner).mro() [<class 'sage.misc.bindable_class.BoundClass'>, <type 'functools.partial'>, <... 'object'>] Still, documentation works as usual:: sage: outer.Inner.__doc__ ' some documentation ' TESTS:: sage: from sage.misc.bindable_class import Outer sage: TestSuite(Outer.Inner).run() sage: outer = Outer() sage: TestSuite(outer.Inner).run(skip=["_test_pickling"]) """ @staticmethod def __classget__(cls, instance, owner): """ Binds ``cls`` to ``instance``, returning a ``BoundClass`` INPUT: - ``instance`` -- an object of the outer class or ``None`` For technical details, see the Section :python:`Implementing Descriptor <reference/datamodel.html#implementing-descriptors>` in the Python reference manual. EXAMPLES:: sage: from sage.misc.bindable_class import Outer sage: Outer.Inner <class 'sage.misc.bindable_class.Outer.Inner'> sage: Outer().Inner <bound class 'sage.misc.bindable_class.Outer.Inner' of <sage.misc.bindable_class.Outer object at ...>> """ if instance is None: return cls return BoundClass(cls, instance)