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
Exemple #2
0
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
Exemple #3
0
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
Exemple #5
0
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)