# Ticket #11935: trac11935_weak_pickling_by_construction_rel11943-nt.patch

File trac11935_weak_pickling_by_construction_rel11943-nt.patch, 32.8 KB (added by nthiery, 10 years ago)
• ## sage/categories/bimodules.py

# HG changeset patch
# User Simon King <simon.king@uni-jena.de>
# Date 1318941258 -7200
# Parent  02bd55d02cd49a785ff35bbe772cc30a546c07cf
#11935: Make parent/element classes independent of base rings

Other change in sage.categories.examples.semigroups_cython:
- rename the experimental class IdempotentSemigroups.ElementMethods
and remove its super class.

diff --git a/sage/categories/bimodules.py b/sage/categories/bimodules.py
 a Bimodules #                  http://www.gnu.org/licenses/ #****************************************************************************** from sage.categories.category import Category from sage.misc.cachefunc import cached_method from sage.misc.unknown import Unknown from sage.categories.category import Category, CategoryWithParameters from sage.categories.left_modules import LeftModules from sage.categories.right_modules import RightModules from sage.categories.rings import Rings _Rings = Rings() #?class Bimodules(Category_over_base_rng, Category_over_base_rng): class Bimodules(Category): class Bimodules(CategoryWithParameters): """ The category of (R,S)-bimodules
• ## sage/categories/category.py

diff --git a/sage/categories/category.py b/sage/categories/category.py
 a from sage.misc.unknown import Unknown from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation from sage.structure.dynamic_class import dynamic_class_internal from sage.structure.dynamic_class import dynamic_class from weakref import WeakValueDictionary _join_cache = WeakValueDictionary() class Category(UniqueRepresentation, Sag same super_categories. For example, Algebras(QQ) has VectorSpaces(QQ) as super category, whereas Algebras(ZZ) only has Modules(ZZ) as super category. In particular, the constructed parent_class and element_class will differ (inheriting, or not, methods specific for vector spaces). On the other hand, caching ensures that two identical hierarchy of classes are built only once:: parent class and element class will differ (inheriting, or not, methods specific for vector spaces):: # TODO: redo the same with Algebras # and show the mro for Algebras(QQ) w.r.t Algebras(ZZ) # 2009/03/11: this feature is temporarily broken, due to the current work around for pickling sage: Coalgebras(QQ).parent_class is Coalgebras(FractionField(QQ[x])).parent_class # todo: not implemented sage: Algebras(QQ).parent_class is Algebras(ZZ).parent_class False sage: issubclass(Algebras(QQ).parent_class, VectorSpaces(QQ).parent_class) True On the other hand, identical hierarchies of classes are, preferably, built only once (e.g. for cagories over a base ring):: sage: Algebras(QQ).parent_class is Algebras(GF(5)).parent_class True sage: Coalgebras(QQ).parent_class is Coalgebras(FractionField(QQ[x])).parent_class True We now construct a parent in the usual way:: class Category(UniqueRepresentation, Sag INPUT: - s -- A string giving the name of this category.  If None, the name is determined from the name of the class. - s -- A string giving the name of this category. If None, the name is determined from the name of the class. EXAMPLES:: class Category(UniqueRepresentation, Sag self; this is the first test that is done in :meth:is_subcategory. This default implementation tests whether the parent class of category is a subclass of the parent class of self. Currently (as of trac ticket #11900) this is a complete subcategory test. But this will change with trac ticket #11935. This default implementation tests whether the parent class of category is a subclass of the parent class of self. This is most of the time a complete subcategory test. .. warning:: This test is incomplete for categories in :class:CategoryWithParameters, as introduced by :trac:11935. This method is therefore overwritten by :meth:~sage.categories.category.CategoryWithParameters._subcategory_hook_. EXAMPLE:: class Category(UniqueRepresentation, Sag """ pass def _make_named_class(self, name, method_provider, cache=False): """ Construction of the parent/element/... class of self. INPUT: - name -- a string; the name of the class as an attribute of self. E.g. "parent_class" - method_provider -- a string; the name of an attribute of self that provides methods for the new class (in addition to those coming from the super categories). E.g. "ParentMethods" - cache -- a boolean or ignore_reduction (default: False) (passed down to dynamic_class; for internal use only) ASSUMPTION: It is assumed that this method is only called from a lazy attribute whose name coincides with the given name OUTPUT: A dynamic class with bases given by the corresponding named classes of self's super_categories and methods taken from the class getattr(self,method_provider). .. NOTE:: In this default implementation, the reduction data of the named class makes it depend on self. Since the result is going to be stored in a lazy attribute of self anyway, we may as well disable the caching in dynamic_class (hence the default value cache=False). :class:CategoryWithParameters overrides this method so that the same parent/element/... classes can be shared between closely related categories. .. SEEALSO:: :meth:CategoryWithParameters._make_named_class EXAMPLES:: sage: PC = Rings()._make_named_class("parent_class", "ParentMethods"); PC sage: type(PC) sage: PC.__bases__ (, ) Note that, by default, the result is not cached:: sage: PC is Rings()._make_named_class("parent_class", "ParentMethods") False Indeed this method is only meant to construct lazy attributes like parent_class which already handle this caching:: sage: Rings().parent_class Reduction for pickling also assumes the existence of this lazy attribute:: sage: PC._reduction (, (Category of rings, 'parent_class')) sage: loads(dumps(PC)) is Rings().parent_class True TESTS:: sage: class A: pass sage: class BrokenCategory(Category): ...       def super_categories(self): return [] ...       ParentMethods = 1 ...       class ElementMethods(A): ...           pass ...       class MorphismMethods(object): ...           pass sage: C = BrokenCategory() sage: C._make_named_class("parent_class",   "ParentMethods") Traceback (most recent call last): ... AssertionError: BrokenCategory.ParentMethods should be a class sage: C._make_named_class("element_class",  "ElementMethods") doctest:...: UserWarning: BrokenCategory.ElementMethods should not have a super class sage: C._make_named_class("morphism_class", "MorphismMethods") """ cls = self.__class__ class_name = "%s.%s"%(cls.__name__, name) method_provider_cls = getattr(self, method_provider, None) if method_provider_cls is None: # If the category provides no XXXMethods class, # point to the documentation of the category itself doccls = cls else: # Otherwise, check XXXMethods import inspect assert inspect.isclass(method_provider_cls),\ "%s.%s should be a class"%(type(self).__name__, method_provider) mro = inspect.getmro(method_provider_cls) if len(mro) > 2 or (len(mro) == 2 and mro[1] is not object): from warnings import warn warn("%s.%s should not have a super class"%(type(self).__name__, method_provider)) # and point the documentation to it doccls = method_provider_cls self._super_categories return dynamic_class(class_name, tuple(getattr(cat,name) for cat in self._super_categories), method_provider_cls, prepend_cls_bases = False, doccls = doccls, reduction = (getattr, (self, name)), cache = cache) @lazy_attribute def parent_class(self): """ class Category(UniqueRepresentation, Sag sage: type(C) By :trac:11935, some categories share their parent classes. For example, the parent class of an algebra only depends on the base ring if the choice of the base ring alters the type of the super categories. A typical example is the category of algebras over a field versus algebras over a non-field:: sage: Algebras(QQ).parent_class is Algebras(GF(3)).parent_class True sage: Algebras(QQ).parent_class is Algebras(ZZ).parent_class False sage: Algebras(ZZ['t']).parent_class is Algebras(ZZ['t','x']).parent_class True See :class:CategoryWithParameters for an abstract base class for categories that depend on parameters, even though the parent and element classes only depend on the parent or element classes of its super categories. It is used in :class:~sage.categories.bimodules.Bimodules, :class:~sage.categories.category_types.Category_over_base and :class:sage.categories.category.JoinCategory. """ # Remark: # For now, we directly call the underlying function, avoiding the overhead # of using a cached function. The rationale: When this lazy method is called # then we can be sure that the parent class had not been constructed before. # The parent and element classes belong to a category, and they are pickled # as such. Hence, they are rightfully cached as an attribute of a category. # # However, we should try to "unify" parent classes. They should depend on the # super categories, but not on the base (except when the super categories depend # on the base). When that is done, calling the cached function will be needed again. #return dynamic_class("%s.parent_class"%self.__class__.__name__, #                     tuple(cat.parent_class for cat in self.super_categories), #                     self.ParentMethods, #                     reduction = (getattr, (self, "parent_class"))) return dynamic_class_internal.f("%s.parent_class"%self.__class__.__name__, tuple(cat.parent_class for cat in self._super_categories), self.ParentMethods, reduction = (getattr, (self, "parent_class"))) return self._make_named_class('parent_class', 'ParentMethods') @lazy_attribute def element_class(self): class Category(UniqueRepresentation, Sag sage: type(C) By :trac:11935, some categories share their element classes. For example, the element class of an algebra only depends on the base ring if the choice of the base ring alters the type of the super categories. A typical example is the category of algebras over a field versus algebras over a non-field:: sage: Algebras(QQ).element_class is Algebras(GF(3)).element_class True sage: Algebras(QQ).element_class is Algebras(ZZ).element_class False sage: Algebras(ZZ['t']).element_class is Algebras(ZZ['t','x']).element_class True """ # Remark: # For now, we directly call the underlying function, avoiding the overhead # of using a cached function. The rationale: When this lazy method is called # then we can be sure that the element class had not been constructed before. # The parent and element classes belong to a category, and they are pickled # as such. Hence, they are rightfully cached as an attribute of a category. # # However, we should try to "unify" element classes. They should depend on the # super categories, but not on the base (except when the super categories depend # on the base). When that is done, calling the cached function will be needed again. #return dynamic_class("%s.element_class"%self.__class__.__name__, #                     (cat.element_class for cat in self.super_categories), #                     self.ElementMethods, #                     reduction = (getattr, (self, "element_class")) #                     ) return dynamic_class_internal.f("%s.element_class"%self.__class__.__name__, tuple(cat.element_class for cat in self._super_categories), self.ElementMethods, reduction = (getattr, (self, "element_class"))) return self._make_named_class('element_class', 'ElementMethods') def required_methods(self): """ class HomCategory(Category): return [] ############################################################################## # Parametrized categories whose parent/element class depend only on # the super categories ############################################################################## class CategoryWithParameters(Category): """ A parametrized category whose parent/element classes depend only on its super categories Many categories in Sage are parametrized, like C=Algebras(K) which takes a base ring as parameter. In many cases however, the operations provided by C in the parent class and element class depend only on the super categories of C. For example, the vector space operations are provided if and only if K is a field, since VectorSpaces(K) is a super category of C only in that case. In such cases, and as an optimization (see :trac:11935), we want to use the same parent and element class for all fields. This is the purpose of this abstract class. Currently, :class:~sage.categories.category.JoinCategory, :class:~sage.categories.category_types.Category_over_base and :class:~sage.categories.bimodules.Bimodules inherit from this class. EXAMPLES:: sage: C1 = Algebras(QQ) sage: C2 = Algebras(GF(3)) sage: C3 = Algebras(ZZ) sage: from sage.categories.category import CategoryWithParameters sage: isinstance(C1, CategoryWithParameters) True sage: C1.parent_class is C2.parent_class True sage: C1.parent_class is C3.parent_class False """ def _make_named_class(self, name, method_provider): """ Purpose: Create the parent/element/... class of self. INPUT: - name -- a string; the name of the class as an attribute of self - method_provider -- a string; the name of an attribute of self that provides methods for the new class (in addition to what comes from the super categories). ASSUMPTION: It is assumed that this method is only called from a lazy attribute whose name coincides with the given name OUTPUT: A dynamic class that has the corresponding named classes of the super categories of self as bases and contains the methods provided by getattr(self,method_provider). .. NOTE:: This method overrides :meth:Category._make_named_class so that the returned class *only* depends on the corresponding named classes of the super categories and on the provided methods. This allows for sharing the named classes across closely related categories providing the same code to their parents, elements and so on. EXAMPLES:: The categories of bimodules over the ring ZZ['x'] or the ring ZZ provide the same methods to their parents and elements:: sage: Bimodules(ZZ,ZZ['x']).parent_class is Bimodules(ZZ,ZZ).parent_class #indirect doctest True sage: Bimodules(ZZ,ZZ['x']).element_class is Bimodules(ZZ,ZZ).element_class True On the other hand, modules over a field have more methods than modules over a ring:: sage: Modules(GF(3)).parent_class is Modules(ZZ).parent_class False sage: Modules(GF(3)).element_class is Modules(ZZ).element_class False TESTS:: sage: Modules(GF(3),dispatch=False).parent_class  is Modules(ZZ).parent_class True sage: Modules(GF(3),dispatch=False).element_class is Modules(ZZ).element_class True sage: PC = Algebras(QQ).parent_class; PC   # indirect doctest sage: type(PC) sage: PC.__bases__ (, ) sage: loads(dumps(PC)) is PC True """ # Implementation: we use the cache functionality of # dynamic_class, and specify that the result depends only on # the classes this class is built from # (e.g. self.ParentMethods and c.parent_class for each # super category c of self) and not on the reduction # data (which includes self). return Category._make_named_class(self, name, method_provider, cache="ignore_reduction") def _subcategory_hook_(self, C): """ A quick but partial test whether C is a subcategory of self INPUT: - C -- a category OUTPUT: - False, if C.parent_class is not a subclass of self.parent_class, and :obj:~sage.misc.unknown.Unknown otherwise. EXAMPLES:: sage: Bimodules(QQ,QQ)._subcategory_hook_(Modules(QQ)) Unknown sage: Bimodules(QQ,QQ)._subcategory_hook_(Rings()) False """ if not issubclass(C.parent_class, self.parent_class): return False return Unknown ############################################################# # Join of several categories ############################################################# class JoinCategory(Category): class JoinCategory(CategoryWithParameters): """ A class for joins of several categories. Do not use directly; see Category.join instead. class JoinCategory(Category): [Category of groups, Category of commutative additive monoids] sage: J.all_super_categories(proper=True) [Category of groups, Category of monoids, Category of semigroups, Category of magmas, Category of commutative additive monoids, Category of commutative additive semigroups, Category of additive magmas, Category of sets, Category of sets with partial maps, Category of objects] By :trac:11935, join categories and categories over base rings inherit from :class:CategoryWithParameters. This allows for sharing parent and element classes between similar categories. For example, since polynomial rings belong to a join category and since the underlying implementation is the same for all finite fields, we have:: sage: GF(3)['x'].category() Join of Category of euclidean domains and Category of commutative algebras over Finite Field of size 3 sage: type(GF(3)['x']) is type(GF(5)['z']) True """ def __init__(self, super_categories, **kwds): class JoinCategory(Category): """ return all(category.is_subcategory(X) for X in self._super_categories) def is_subcategory(self, C): """ Tell whether this join category is subcategory of another category C. EXAMPLES:: sage: Category.join([Rings(),Modules(QQ)]).is_subcategory(Category.join([Rngs(),Bimodules(QQ,QQ)])) True """ if C is self: return True hook = C._subcategory_hook_(self) if hook is Unknown: return any(X.is_subcategory(C) for X in self._super_categories) return hook def _repr_(self): """ Print representation.
• ## sage/categories/category_types.py

diff --git a/sage/categories/category_types.py b/sage/categories/category_types.py
 a This is placed in a separate file from c #***************************************************************************** from sage.misc.latex import latex from category import Category from sage.misc.unknown import Unknown from category import Category, CategoryWithParameters from objects import Objects #################################################################### class Sequences(Category): ############################################################# # Category of objects over some base object ############################################################# class Category_over_base(Category): class Category_over_base(CategoryWithParameters): def __init__(self, base, name=None): Category.__init__(self, name) self.__base = base class Category_over_base(Category): """ return "\\mathbf{%s}_{%s}"%(self._label, latex(self.__base)) def _subcategory_hook_(self, C): """ A quick test whether a category C may be subcategory of this category. INPUT: - C  a category (type not tested) OUTPUT: a boolean if it is certain that C is (or is not) a subcategory of self. :obj:~sage.misc.unknown.Unknown otherwise. EXAMPLES:: sage: Algebras(QQ)._subcategory_hook_(HopfAlgebras(QQ)) True sage: Algebras(QQ)._subcategory_hook_(HopfAlgebras(ZZ)) False sage: VectorSpaces(QQ)._subcategory_hook_(VectorSpaces(QQ).hom_category()) True sage: VectorSpaces(QQ)._subcategory_hook_(Category.join([VectorSpaces(QQ).hom_category(),Rings()])) Unknown """ if not issubclass(C.parent_class, self.parent_class): return False try: if C.base() is self.__base: return True except AttributeError: pass return Unknown #    def construction(self): #        return (self.__class__, self.__base)
• ## sage/categories/examples/semigroups_cython.pyx

diff --git a/sage/categories/examples/semigroups_cython.pyx b/sage/categories/examples/semigroups_cython.pyx
 a from sage.categories.all import Category from sage.misc.cachefunc import cached_method from sage.categories.examples.semigroups import LeftZeroSemigroup as LeftZeroSemigroupPython class DummyClass: def method(self): """ TESTS:: sage: from sage.categories.examples.semigroups_cython import DummyClass sage: DummyClass().method() """ pass cdef class DummyCClass: def method(self): """ TESTS:: sage: from sage.categories.examples.semigroups_cython import DummyCClass sage: DummyCClass().method() """ pass cpdef cpmethod(self): """ TESTS:: sage: from sage.categories.examples.semigroups_cython import DummyCClass sage: DummyCClass().cpmethod() """ pass instancemethod     = type(DummyClass.method) method_descriptor  = type(DummyCClass.method) cdef class IdempotentSemigroupsElement(Element): cdef class IdempotentSemigroupsElementMethods: def _pow_(self, i): """ EXAMPLES:: cdef class IdempotentSemigroupsElement(E return True class IdempotentSemigroups(Category): #@cached_method def super_categories(self): """ EXAMPLES:: class IdempotentSemigroups(Category): """ return [Semigroups()] ElementMethods = IdempotentSemigroupsElement ElementMethods = IdempotentSemigroupsElementMethods cdef class LeftZeroSemigroupElement(Element): class LeftZeroSemigroup(LeftZeroSemigrou Comments: - nested classes seem not to be currently supported by cython - nested classes seem not to be currently supported by Cython - one cannot play ugly use class surgery tricks (as with _mul_parent) - one cannot play ugly class surgery tricks (as with _mul_parent). available operations should really be declared to the coercion model! EXAMPLES::
• ## sage/structure/dynamic_class.py

diff --git a/sage/structure/dynamic_class.py b/sage/structure/dynamic_class.py
 a an inheritance can be partially emulated from sage.misc.cachefunc import weak_cached_function from sage.structure.unique_representation import ClasscallMetaclass def dynamic_class(name, bases, cls = None, reduction = None, doccls=None): def dynamic_class(name, bases, cls = None, reduction = None, doccls = None, prepend_cls_bases = True, cache=True): r""" INPUT: def dynamic_class(name, bases, cls = Non - cls -- a class or None - reduction -- a tuple or None - doccls -- a class or None - prepend_cls_bases -- a boolean (default: True) - cache -- a boolean or "ignore_reduction" (default: True) Constructs dynamically a new class C with name name, and bases bases. If cls is provided, then its methods will be inserted into C as well. The module of C is set from the module of cls or from the first base class (bases should be non empty if cls is None). inserted into C, and its bases will be prepended to bases (unless prepend_cls_bases is False). Documentation and source instrospection is taken from doccls, or cls if doccls is None, or bases[0] if both are None. The module, documentation and source instrospection is taken from doccls, or cls if doccls is None, or bases[0] if both are None (therefore bases should be non empty if cls is None). The constructed class can safely be pickled (assuming the arguments themselves can). The result is cached, ensuring unique representation of dynamic classes. Unless cache is False, the result is cached, ensuring unique representation of dynamic classes. See :mod:sage.structure.dynamic_class for a discussion of the dynamic classes paradigm, and its relevance to Sage. def dynamic_class(name, bases, cls = Non sage: FooBar.mro() [, , ] .. RUBRIC:: Pickling Dynamic classes are pickled by construction. Namely, upon unpickling, the class will be reconstructed by recalling dynamic_class with the same arguments:: def dynamic_class(name, bases, cls = Non sage: type(FooBar) The following (meaningless) example illustrates how to customize the result of the reduction:: def dynamic_class(name, bases, cls = Non sage: loads(dumps(BarFoo)) '3' .. RUBRIC:: Caching By default, the built class is cached: sage: dynamic_class("FooBar", (Bar,), Foo) is FooBar True sage: dynamic_class("FooBar", (Bar,), Foo, cache=True) is FooBar True and the result depends on the reduction:: sage: dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (3,))) is BarFoo True sage: dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (2,))) is BarFoo False With cache=False, a new class is created each time:: sage: FooBar1 = dynamic_class("FooBar", (Bar,), Foo, cache=False); FooBar1 sage: FooBar2 = dynamic_class("FooBar", (Bar,), Foo, cache=False); FooBar2 sage: FooBar1 is FooBar False sage: FooBar2 is FooBar1 False With cache="ignore_reduction", the class does not depend on the reduction:: sage: BarFoo = dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (3,)), cache="ignore_reduction") sage: dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (2,)), cache="ignore_reduction") is BarFoo True In particular, the reduction used is that provided upon creating the first class:: sage: dynamic_class("BarFoo", (Foo,), Bar, reduction = (str, (2,)), cache="ignore_reduction")._reduction (, (3,)) .. WARNING:: The behaviour upon creating several dynamic classes from the same data but with different values for cache option is currently left unspecified. In other words, for a given application, it is recommended to consistently use the same value for that option. TESTS:: sage: import __main__ def dynamic_class(name, bases, cls = Non #assert(len(bases) > 0 ) assert(type(name) is str) #    assert(cls is None or issubtype(type(cls), type) or type(cls) is classobj) return dynamic_class_internal(name, bases, cls, reduction, doccls) if cache is True: return dynamic_class_internal(name, bases, cls, reduction, doccls, prepend_cls_bases) elif cache is False: # bypass the cached method return dynamic_class_internal.f(name, bases, cls, reduction, doccls, prepend_cls_bases) else: # cache = "ignore_reduction" result = dynamic_class_internal(name, bases, cls, False, doccls, prepend_cls_bases) if result._reduction is False: result._reduction = reduction return result @weak_cached_function def dynamic_class_internal(name, bases, cls = None, reduction = None, doccls = None): def dynamic_class_internal(name, bases, cls = None, reduction = None, doccls = None, prepend_cls_bases=True): r""" See sage.structure.dynamic_class.dynamic_class? for indirect doctests. def dynamic_class_internal(name, bases, # Anything else that should not be kept? if methods.has_key("__dict__"): methods.__delitem__("__dict__") bases = cls.__bases__ + bases if prepend_cls_bases: bases = cls.__bases__ + bases else: methods = {} if doccls is None: