Ask Your Question
11

importing .sage files

asked 2011-01-13 10:19:28 +0100

DSM gravatar image

updated 2011-01-13 10:21:41 +0100

What's the recommended approach for importing .sage scripts as a module, i.e. into their own namespace? I have a file with ~10k functions and it'd be much nicer to keep them filed tidily away.

I've written a hack sage_import wrapper which seems to work for my use case, but hopefully there's a better way.

edit retag flag offensive close merge delete

Comments

Maybe you can post your hack as an edit to your post?

kcrisman gravatar imagekcrisman ( 2011-01-13 10:39:12 +0100 )edit

3 Answers

Sort by ยป oldest newest most voted
7

answered 2011-01-13 11:25:32 +0100

niles gravatar image

updated 2011-01-15 10:31:56 +0100

You could instead apply the sage preparser, resulting in a .py file which you can then import with no trouble.

If you call "sage filename.sage", sage will preparse and store filename.py in the same directory (as well as carrying out all the commands in the file, which will be problematic in some cases). @ivan-andrus points out that

"sage --preparse filename.sage"

will do just the preparsing :)

Niles

edit flag offensive delete link more

Comments

I like this, though I don't know if it is answering DSM's question. It would be nice to have something like `attach('foo.sage',global_namespace=False)` available for those who use it from the command line - if that's what DSM is asking.

kcrisman gravatar imagekcrisman ( 2011-01-13 13:34:08 +0100 )edit

I haven't seen any problems with just running `sage --preparse FILE.sage`. What problems do you have?

Ivan Andrus gravatar imageIvan Andrus ( 2011-01-15 09:03:57 +0100 )edit

Ah *that's* the command I was looking for! `sage-preparse` is a separate executable which needs to be called in the right way, which I couldn't figure out; passing the `--preparse` option to sage seems to do the trick. Thanks!

niles gravatar imageniles ( 2011-01-15 10:27:47 +0100 )edit
1

Well, invoking the functions in sage.misc.preparser is what my hack currently does. One problem with preparser approaches -- mine included, at least so far -- is that the resulting code shows up in a mymodule.somefunc?? call in its postprocessed form, which is often hard to read. (Enough __sage_const_ bits floating around and even simple formulae look confusing.) This gives writing Sage code _in Sage code_ a weirdly second-class status. I'm leaning towards modifying the .func_code.co_* data so that I can see what I actually wrote [possibly putting the post-processed version below it]; this is a little tricky, though.

DSM gravatar imageDSM ( 2011-01-15 11:13:59 +0100 )edit

ah; that is annoying

niles gravatar imageniles ( 2011-01-15 11:22:58 +0100 )edit
2

answered 2019-11-27 16:34:18 +0100

Hilder Vitor Lima Pereira gravatar image

updated 2019-11-27 16:34:51 +0100

Just to make @niles' answer more explicit: You can add the following function to the beginning of your main sage script.

## Hack to import my own sage scripts
def my_import(module_name, func_name='*'):
    import os
    os.system('sage --preparse ' + module_name + '.sage')
    os.system('mv ' + module_name + '.sage.py ' + module_name + '.py')

    from sage.misc.python import Python
    python = Python()
    python.eval('from ' + module_name + ' import ' + func_name, globals())

Then, to import all functions from my_lib.sage, you can do

my_import("my_lib")  # from my_lib import *

To import a particular function, say my_func, you can do

my_import("my_lib", "my_func") # from my_lib import my_func

(I created this function using this solution)

edit flag offensive delete link more

Comments

You can access the Sage preparser as a function call--no need to use os.system for any of this. I'll post an answer with a simplified solution.

Iguananaut gravatar imageIguananaut ( 2019-12-05 12:12:41 +0100 )edit

Note that the object returned by globals() in different contexts might be distinct (I was surprised to find that by comparing them). I had to add a parameter globals_used (I made it default to globals()) which is assigned by globals() upon calling like so: my_import("my_lib", "my_func", globals()). This globals_used parameter is passed as the argument for the eval at the end of the function like so: python.eval('from ' + module_name + ' import ' + func_name, globals_used). This way the imported functions are actually added to the correct globals() object and thus are available to use in the context in which the import was called.

et_l gravatar imageet_l ( 2021-07-07 14:42:23 +0100 )edit
1

answered 2019-12-05 12:45:44 +0100

Iguananaut gravatar image

Here's a rough sketch of a sage_import that works similarly to the Python import statement on .sage files, including searching sys.path for the file (which by default includes your current working directory), but this part is optional I suppose.

# sage_import.py
import imp
import inspect
import os
import sys

import sage.all


def sage_import(modname, fromlist=None, namespace=None):
    """
    Import a .sage module from the filename <modname>.sage

    Returns the resulting Python module.  If ``fromlist`` is given, returns
    just those members of the module into the global namespace where the
    function was called, or the given namespace.
    """

    filename = modname + '.sage'

    for path in sys.path:
        modpath = os.path.join(path, filename)
        if os.path.isfile(modpath):
            break
    else:
        raise ImportError('no file {} on sys.path'.format(filename))

    with open(modpath) as fobj:
        code = sage.all.preparse(fobj.read())
        mod = imp.new_module(modname)
        mod.__file__ = modpath
        # Fill with all the default Sage globals
        # We could just do a dict.update but we want to exclude dunder
        # and private attributes I guess
        for k, v in sage.all.__dict__.items():
            if not k.startswith('_'):
                mod.__dict__[k] = v

        exec code in mod.__dict__

    if namespace is None:
        namespace = inspect.currentframe().f_back.f_globals


    if fromlist is not None:
        # First check that each name in fromlist exists before adding
        # any of them to the given namespace.
        for name in fromlist:
            if name not in mod.__dict__:
                raise ImportError('cannot import name {!r} from {}'.format(
                     name, filename))

        for name in fromlist:
            namespace[name] = mod.__dict__[name]
    else:
        namespace[modname] = mod

Given a file in my current directory named a.sage containing:

# a.sage
a = 1 / 2

I can use this either in Sage or in a standard Python interpreter like

>>> from sage_import import sage_import
>>> sage_import('a', fromlist=['a'])
>>> a
1/2
>>> type(a)
<type 'sage.rings.rational.Rational'>

This version works for Python 2. It can be adapted to Python 3 with some small tweaks.

There is an open issue to build this functionality directly into Sage so that you can use the standard import statement to import .sage modules. This can be done but it requires a bit of care. I think a prototype was started at one point but it still hasn't been submitted.

edit flag offensive delete link more

Comments

See a somewhat related comment in the ticket.

Emmanuel Charpentier gravatar imageEmmanuel Charpentier ( 2019-12-05 17:23:15 +0100 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

2 followers

Stats

Asked: 2011-01-13 10:19:28 +0100

Seen: 7,040 times

Last updated: Dec 05 '19