Abstract base class for Sage objects

Abstract base class for Sage objects

class sage.structure.sage_object.SageObject

Bases: object

x.__init__(...) initializes x; see help(type(x)) for signature

category()
db(name, compress=True)

Dumps self into the Sage database. Use db(name) by itself to reload.

The database directory is $HOME/.sage/db

dump(filename, compress=True)

Same as self.save(filename, compress)

dumps(compress=True)

Dump self to a string s, which can later be reconstituted as self using loads(s).

There is an optional boolean argument compress which defaults to True.

EXAMPLES:

sage: O=SageObject(); O.dumps()
'x\x9ck`J.NLO\xd5+.)*M.)-\x02\xb2\x80\xdc\xf8\xfc\xa4\xac\xd4\xe4\x12\xae` \xdb\x1f\xc2,d\xd4l,d\xd2\x03\x00\xb7X\x10\xf1'
sage: O.dumps(compress=False)
'\x80\x02csage.structure.sage_object\nSageObject\nq\x01)\x81q\x02.'
rename(x=None)

Change self so it prints as x, where x is a string.

Note

This is only supported for Python classes that derive from SageObject.

EXAMPLES:

sage: x = PolynomialRing(QQ, 'x', sparse=True).gen()
sage: g = x^3 + x - 5
sage: g
x^3 + x - 5
sage: g.rename('a polynomial')
sage: g
a polynomial
sage: g + x
x^3 + 2*x - 5
sage: h = g^100
sage: str(h)[:20]
'x^300 + 100*x^298 - '
sage: h.rename('x^300 + ...')
sage: h
x^300 + ...

Real numbers are not Python classes, so rename is not supported:

sage: a = 3.14
sage: type(a)
<type 'sage.rings.real_mpfr.RealLiteral'>
sage: a.rename('pi')
Traceback (most recent call last):
...
NotImplementedError: object does not support renaming: 3.14000000000000

Note

The reason C-extension types are not supported by default is if they were then every single one would have to carry around an extra attribute, which would be slower and waste a lot of memory.

To support them for a specific class, add a cdef public __custom_name attribute.

reset_name()

Remove the custrom name of an object.

EXAMPLES:

sage: P.<x> = QQ[]
sage: P
Univariate Polynomial Ring in x over Rational Field
sage: P.rename('A polynomial ring')
sage: P
A polynomial ring
sage: P.reset_name()
sage: P
Univariate Polynomial Ring in x over Rational Field
save(filename=None, compress=True)

Save self to the given filename.

EXAMPLES:

sage: f = x^3 + 5
sage: f.save(os.path.join(SAGE_TMP, 'file'))
sage: load(os.path.join(SAGE_TMP, 'file.sobj'))
x^3 + 5
version()

The version of Sage.

Call this to save the version of Sage in this object. If you then save and load this object it will know in what version of Sage it was created.

This only works on Python classes that derive from SageObject.

sage.structure.sage_object.dumps(obj, compress=True)

Dump obj to a string s. To recover obj, use loads(s).

See also

dumps()

EXAMPLES:

sage: a = 2/3
sage: s = dumps(a)
sage: print len(s)
49
sage: loads(s)
2/3
sage.structure.sage_object.have_same_parent(self, other)

EXAMPLES:

sage: from sage.structure.sage_object import have_same_parent
sage: have_same_parent(1, 3)
True
sage: have_same_parent(1, 1/2)
False
sage: have_same_parent(gap(1), gap(1/2))
True
sage.structure.sage_object.load(compress=True, verbose=True, *filename)

Load Sage object from the file with name filename, which will have an .sobj extension added if it doesn’t have one. Or, if the input is a filename ending in .py, .pyx, or .sage, load that file into the current running session. Loaded files are not loaded into their own namespace, i.e., this is much more like Python’s execfile than Python’s import.

Note

There is also a special Sage command (that is not available in Python) called load that you use by typing

sage: load filename.sage           # not tested

The documentation below is not for that command. The documentation for load is almost identical to that for attach. Type attach? for help on attach.

This function also loads a .sobj file over a network by specifying the full URL. (Setting verbose = False suppresses the loading progress indicator.)

Finally, if you give multiple positional input arguments, then all of those files are loaded, or all of the objects are loaded and a list of the corresponding loaded objects is returned.

EXAMPLE:

sage: u = 'http://sage.math.washington.edu/home/was/db/test.sobj'
sage: s = load(u)                                                  # optional - internet
Attempting to load remote file: http://sage.math.washington.edu/home/was/db/test.sobj
Loading: [.]
sage: s                                                            # optional - internet
'hello SAGE'

We test loading a file or multiple files or even mixing loading files and objects:

sage: t=tmp_filename()+'.py'; open(t,'w').write("print 'hello world'")
sage: load(t)
hello world
sage: load(t,t)
hello world
hello world
sage: t2=tmp_filename(); save(2/3,t2)
sage: load(t,t,t2)
hello world
hello world
[None, None, 2/3]
sage.structure.sage_object.loads(s, compress=True)

Recover an object x that has been dumped to a string s using s = dumps(x).

See also

dumps()

EXAMPLES:

sage: a = matrix(2, [1,2,3,-4/3])
sage: s = dumps(a)
sage: loads(s)
[   1    2]
[   3 -4/3]

If compress is True (the default), it will try to decompress the data with zlib and with bz2 (in turn); if neither succeeds, it will assume the data is actually uncompressed. If compress=False is explicitly specified, then no decompression is attempted.

sage: v = [1..10]
sage: loads(dumps(v, compress=False)) == v
True
sage: loads(dumps(v, compress=False), compress=True) == v
True
sage: loads(dumps(v, compress=True), compress=False)
Traceback (most recent call last):
...
UnpicklingError: invalid load key, 'x'.
sage.structure.sage_object.picklejar(obj, dir=None)

Create pickled sobj of obj in dir, with name the absolute value of the hash of the pickle of obj. This is used in conjunction with unpickle_all().

To use this to test the whole Sage library right now, set the environment variable SAGE_PICKLE_JAR, which will make it so dumps will by default call picklejar with the default dir. Once you do that and doctest Sage, you’ll find that the SAGE_ROOT/tmp/ contains a bunch of pickled objects along with corresponding txt descriptions of them. Use the unpickle_all() to see if they unpickle later.

INPUTS:

  • obj – a pickleable object
  • dir – a string or None; if None then dir defaults to SAGE_ROOT/tmp/pickle_jar

EXAMPLES:

sage: dir = tmp_dir()
sage: sage.structure.sage_object.picklejar(1, dir)
sage: sage.structure.sage_object.picklejar('test', dir)
sage: len(os.listdir(dir))   # Two entries (sobj and txt) for each object
4

TESTS:

Test an unaccessible directory:

sage: import os
sage: os.chmod(dir, 0000)
sage: try:
...   uid = os.getuid()
... except AttributeError:
...    uid = -1
sage: if uid==0:
...     raise OSError('You must not run the doctests as root, geez!')
... else: sage.structure.sage_object.picklejar(1, dir + '/noaccess')
Traceback (most recent call last):
...
OSError: ...
sage: os.chmod(dir, 0755)
sage.structure.sage_object.register_unpickle_override(module, name, callable, call_name=None)

Python pickles include the module and class name of classes. This means that rearranging the Sage source can invalidate old pickles. To keep the old pickles working, you can call register_unpickle_override with an old module name and class name, and the Python callable (function, class with __call__ method, etc.) to use for unpickling. (If this callable is a value in some module, you can specify the module name and class name, for the benefit of explain_pickle() when called with in_current_sage=True).)

EXAMPLES:

sage: from sage.structure.sage_object import unpickle_override, register_unpickle_override
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.integer.Integer'>

Now we horribly break the pickling system:

sage: register_unpickle_override('sage.rings.integer', 'Integer', Rational, call_name=('sage.rings.rational', 'Rational'))
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.rational.Rational'>

and we reach into the internals and put it back:

sage: del unpickle_override[('sage.rings.integer', 'Integer')]
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.integer.Integer'>

In many cases, unpickling problems for old pickles can be resolved with a simple call to register_unpickle_override, as in the example above and in many of the sage source files. However, if the underlying data structure has changed significantly then unpickling may fail and it will be necessary to explicitly implement unpickling methods for the associated objects. The python pickle protocol is described in detail on the web and, in particular, in the python pickling documentation. For example, the following excerpt from this documentation shows that the unpickling of classes is controlled by their __setstate__() method.

object.__setstate__(state)

    Upon unpickling, if the class also defines the method :meth:`__setstate__`, it is
    called with the unpickled state. If there is no :meth:`__setstate__` method,
    the pickled state must be a dictionary and its items are assigned to the new
    instance's dictionary. If a class defines both :meth:`getstate__` and
    :meth:`__setstate__`, the state object needn't be a dictionary and these methods
    can do what they want.

By implementing a __setstate__() method for a class it should be possible to fix any unpickling problems for the class. As an example of what needs to be done, we show how to unpickle a CombinatorialObject object using a class which also inherits from Element. This exact problem often arises when refactoring old code into the element framework. First we create a pickle to play with:

sage: from sage.structure.element import Element
sage: class SourPickle(CombinatorialObject): pass
sage: class SweetPickle(CombinatorialObject,Element): pass
sage: import __main__
sage: __main__.SourPickle=SourPickle
sage: __main__.SweetPickle=SweetPickle  # a hack to allow us to pickle command line classes
sage: gherkin = dumps( SourPickle([1,2,3]) )

Using register_unpickle_override() we try to sweeten our pickle, but we are unable to eat it:

sage: from sage.structure.sage_object import register_unpickle_override
sage: register_unpickle_override('__main__','SourPickle',SweetPickle)
sage: loads( gherkin )
Traceback (most recent call last):
...
KeyError: 0

The problem is that the SweetPickle has inherited a __setstate__() method from Element which is not compatible with unpickling for CombinatorialObject. We can fix this by explicitly defining a new __setstate__() method:

sage: class SweeterPickle(CombinatorialObject,Element):
...       def __setstate__(self, state):
...           if isinstance(state, dict):   # a pickle from CombinatorialObject is just its instance dictionary
...               self._set_parent(Tableaux())      # this is a fudge: we need an appropriate parent here
...               self.__dict__ = state
...           else:
...               self._set_parent(state[0])
...               self.__dict__ = state[1]
...
sage: __main__.SweeterPickle = SweeterPickle
sage: register_unpickle_override('__main__','SourPickle',SweeterPickle)
sage: loads( gherkin )
[1, 2, 3]
sage: loads(dumps( SweeterPickle([1,2,3]) ))   # check that pickles work for SweeterPickle
[1, 2, 3]

The state passed to __setstate__() will usually be something like the instance dictionary of the pickled object, however, with some older classes such as CombinatorialObject it will be a tuple. In general, the state can be any python object. Sage provides a special tool, explain_pickle(), which can help in figuring out the contents of an old pickle. Here is a second example.

sage: class A(object):
...      def __init__(self,value):
...          self.original_attribute = value
...      def __repr__(self):
...          return 'A(%s)'%self.original_attribute
sage: class B(object):
...      def __init__(self,value):
...          self.new_attribute = value
...      def __setstate__(self,state):
...          try:
...              self.new_attribute = state['new_attribute']
...          except KeyError:      # an old pickle
...              self.new_attribute = state['original_attribute']
...      def __repr__(self):
...          return 'B(%s)'%self.new_attribute
sage: import __main__
sage: __main__.A=A; __main__.B=B  # a hack to allow us to pickle command line classes
sage: A(10)
A(10)
sage: loads( dumps(A(10)) )
A(10)
sage: sage.misc.explain_pickle.explain_pickle( dumps(A(10)) )
pg_A = unpickle_global('__main__', 'A')
si = unpickle_newobj(pg_A, ())
pg_make_integer = unpickle_global('sage.rings.integer', 'make_integer')
unpickle_build(si, {'original_attribute':pg_make_integer('a')})
si
sage: from sage.structure.sage_object import register_unpickle_override
sage: register_unpickle_override('__main__', 'A', B)
sage: loads( dumps(A(10)) )
B(10)
sage: loads( dumps(B(10)) )
B(10)

Pickling for python classes and extension classes, such as cython, is different – again this is discussed in the python pickling documentation. For the unpickling of extension classes you need to write a __reduce__() method which typically returns a tuple (f, args,...) such that f(*args) returns (a copy of) the original object. The following code snippet is the __reduce__() method from sage.rings.integer.Integer.

def __reduce__(self):
    'Including the documentation properly causes a doc-test failure so we include it as a comment:'
    #* '''
    #* This is used when pickling integers.
    #*
    #* EXAMPLES::
    #*
    #*     sage: n = 5
    #*     sage: t = n.__reduce__(); t
    #*     (<built-in function make_integer>, ('5',))
    #*     sage: t[0](*t[1])
    #*     5
    #*     sage: loads(dumps(n)) == n
    #*     True
    #* '''
    # This single line below took me HOURS to figure out.
    # It is the *trick* needed to pickle Cython extension types.
    # The trick is that you must put a pure Python function
    # as the first argument, and that function must return
    # the result of unpickling with the argument in the second
    # tuple as input. All kinds of problems happen
    # if we don't do this.
    return sage.rings.integer.make_integer, (self.str(32),)
sage.structure.sage_object.save(obj, filename=None, compress=True, **kwds)

Save obj to the file with name filename, which will have an .sobj extension added if it doesn’t have one, or if obj doesn’t have its own save() method, like e.g. Python tuples.

For image objects and the like (which have their own save() method), you may have to specify a specific extension, e.g. .png, if you don’t want the object to be saved as a Sage object (or likewise, if filename could be interpreted as already having some extension).

Warning

This will replace the contents of the file if it already exists.

EXAMPLES:

sage: a = matrix(2, [1,2,3,-5/2])
sage: objfile = os.path.join(SAGE_TMP, 'test.sobj')
sage: objfile_short = os.path.join(SAGE_TMP, 'test')
sage: save(a, objfile)
sage: load(objfile_short)
[   1    2]
[   3 -5/2]
sage: E = EllipticCurve([-1,0])
sage: P = plot(E)
sage: save(P, objfile_short)   # saves the plot to "test.sobj"
sage: save(P, filename=os.path.join(SAGE_TMP, "sage.png"), xmin=-2)
sage: save(P, os.path.join(SAGE_TMP, "filename.with.some.wrong.ext"))
Traceback (most recent call last):
...
ValueError: allowed file extensions for images are '.eps', '.pdf', '.png', '.ps', '.sobj', '.svg'!
sage: print load(objfile)
Graphics object consisting of 2 graphics primitives
sage: save("A python string", os.path.join(SAGE_TMP, 'test'))
sage: load(objfile)
'A python string'
sage: load(objfile_short)
'A python string'

TESTS:

Check that trac ticket #11577 is fixed:

sage: filename = os.path.join(SAGE_TMP, "foo.bar")  # filename containing a dot
sage: save((1,1),filename)            # saves tuple to "foo.bar.sobj"
sage: load(filename)
(1, 1)
sage.structure.sage_object.unpickle_all(dir=None, debug=False, run_test_suite=False)

Unpickle all sobj’s in the given directory, reporting failures as they occur. Also printed the number of successes and failure.

INPUT:

  • dir – a string; the name of a directory (or of a .tar.bz2 file that decompresses to a directory) full of pickles. (default: the standard pickle jar)
  • debug – a boolean (default: False) whether to report a stacktrace in case of failure
  • run_test_suite – a boolean (default: False) whether to run TestSuite(x).run() on the unpickled objects

EXAMPLES:

sage: dir = tmp_dir()
sage: sage.structure.sage_object.picklejar('hello', dir)
sage: sage.structure.sage_object.unpickle_all(dir)
Successfully unpickled 1 objects.
Failed to unpickle 0 objects.

When run with no arguments unpickle_all() tests that all of the “standard” pickles stored in the pickle_jar at SAGE_ROOT/local/share/sage/ext/pickle_jar/pickle_jar.tar.bz2 can be unpickled.

sage: sage.structure.sage_object.unpickle_all()  # (4s on sage.math, 2011)
doctest:... DeprecationWarning: This class is replaced by Matrix_modn_dense_float/Matrix_modn_dense_double.
See http://trac.sagemath.org/4260 for details.
Successfully unpickled ... objects.
Failed to unpickle 0 objects.

When it is not possible to unpickle a pickle in the pickle_jar then unpickle_all() prints the following error message which warns against removing pickles from the pickle_jar and directs the user towards register_unpickle_override(). The following code intentionally breaks a pickle to illustrate this:

sage: from sage.structure.sage_object import register_unpickle_override, unpickle_all, unpickle_global
sage: class A(CombinatorialObject,sage.structure.element.Element):
...       pass # to break a pickle
sage: tableau_unpickler=unpickle_global('sage.combinat.tableau','Tableau_class')
sage: register_unpickle_override('sage.combinat.tableau','Tableau_class',A) # breaking the pickle
sage: unpickle_all()  # todo: not tested
...
Failed:
_class__sage_combinat_crystals_affine_AffineCrystalFromClassicalAndPromotion_with_category_element_class__.sobj
_class__sage_combinat_crystals_tensor_product_CrystalOfTableaux_with_category_element_class__.sobj
_class__sage_combinat_crystals_tensor_product_TensorProductOfCrystalsWithGenerators_with_category__.sobj
_class__sage_combinat_tableau_Tableau_class__.sobj
----------------------------------------------------------------------
** This error is probably due to an old pickle failing to unpickle.
** See sage.structure.sage_object.register_unpickle_override for
** how to override the default unpickling methods for (old) pickles.
** NOTE: pickles should never be removed from the pickle_jar!
----------------------------------------------------------------------
Successfully unpickled 583 objects.
Failed to unpickle 4 objects.
sage: register_unpickle_override('sage.combinat.tableau','Tableau_class',tableau_unpickler) # restore to default

Todo

Create a custom-made SourPickle for the last example.

If you want to find lots of little issues in Sage then try the following:

sage: print "x"; sage.structure.sage_object.unpickle_all(run_test_suite = True) # todo: not tested

This runs TestSuite tests on all objects in the Sage pickle jar. Some of those objects seem to unpickle properly, but do not pass the tests because their internal data structure is messed up. In most cases though it is just that their source file misses a TestSuite call, and therefore some misfeatures went unnoticed (typically Parents not implementing the an_element method).

Note

Every so often the standard pickle jar should be updated by running the doctest suite with the environment variable SAGE_PICKLE_JAR set, then copying the files from SAGE_ROOT/tmp/pickle_jar* into the standard pickle jar.

Warning

Sage’s pickle jar helps to ensure backward compatibility in sage. Pickles should only be removed from the pickle jar after the corresponding objects have been properly deprecated. Any proposal to remove pickles from the pickle jar should first be discussed on sage-devel.

sage.structure.sage_object.unpickle_global(module, name)

Given a module name and a name within that module (typically a class name), retrieve the corresponding object. This normally just looks up the name in the module, but it can be overridden by register_unpickle_override. This is used in the Sage unpickling mechanism, so if the Sage source code organization changes, register_unpickle_override can allow old pickles to continue to work.

EXAMPLES:

sage: from sage.structure.sage_object import unpickle_override, register_unpickle_override
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.integer.Integer'>

Now we horribly break the pickling system:

sage: register_unpickle_override('sage.rings.integer', 'Integer', Rational, call_name=('sage.rings.rational', 'Rational'))
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.rational.Rational'>

and we reach into the internals and put it back:

sage: del unpickle_override[('sage.rings.integer', 'Integer')]
sage: unpickle_global('sage.rings.integer', 'Integer')
<type 'sage.rings.integer.Integer'>

Previous topic

Basic Structures

Next topic

Base class for objects of a category

This Page