Ask Your Question
1

"from __future__ import print_function" fails on Sage scripts

asked 2019-04-24 19:14:49 +0100

dsejas gravatar image

updated 2019-04-24 19:21:17 +0100

Hello, Sage Community.

I am trying to use the "print" function in a .sage script, so I have added the line

from __future__ import print_function

as the first line to be included in my .sage file. Unfortunately, when running sage test.sage, Sage preparses the document and creates an auxiliary file "test.sage.py", which makes an import, then predefines some constants, and finally adds my preparsed code. As a consequence, the from __future__ import print_function is not the first line, and I get the following error message:

File "test.sage.py", line 6
from __future__ import print_function
SyntaxError: from __future__ imports must occur at the beginning of the file

Here is a MWE. The file "test.sage" containing:

from __future__ import print_function
print(1+1)

is preparsed to "test.sage.py" containing:

# This file was *autogenerated* from the file test.sage
from sage.all_cmdline import *   # import sage library

_sage_const_1 = Integer(1)
from __future__ import print_function

print(_sage_const_1 +_sage_const_1 )

Of course, I could manually add this line to the .sage.py file and then execute it, but this could be tedious in my case for two reasons: 1. I have a lot of files which I have to modify and rerun every 15 minutes. 2. I also need this process to be automatic to be able to execute it with automatically generated script and even sageTeX.

Thanks in advance for your answers!

edit retag flag offensive close merge delete

Comments

1

Just use .py files. Preparsing is not really useful once you are used to sage.

FrédéricC gravatar imageFrédéricC ( 2019-04-24 19:42:26 +0100 )edit

The problem is that Sage executes .py files as purely Python files, so commands like plot are not recognized. Of course I can use Sage as a library an import it into the .py file, but unfortunately, the software I am working with require to first build .sage files.

dsejas gravatar imagedsejas ( 2019-04-24 21:16:32 +0100 )edit

Then just import what you need. The command "import_statements" is very useful to find the required imports.

FrédéricC gravatar imageFrédéricC ( 2019-04-24 22:18:42 +0100 )edit
1

This seems like something that could be fixed in the .sage file preparser.

John Palmieri gravatar imageJohn Palmieri ( 2019-04-24 22:51:28 +0100 )edit

Do you need to include the line from __future__ import print_function? print(x) should work fine without it.

John Palmieri gravatar imageJohn Palmieri ( 2019-04-24 22:55:44 +0100 )edit

1 Answer

Sort by » oldest newest most voted
2

answered 2019-04-25 05:26:08 +0100

updated 2019-04-25 05:28:35 +0100

Here is a patch posted to https://trac.sagemath.org/ticket/27719 (plus I added a doctest there, too):

diff --git a/src/bin/sage-preparse b/src/bin/sage-preparse
index 2d87e73dca..5103fe657b 100755
--- a/src/bin/sage-preparse
+++ b/src/bin/sage-preparse
@@ -61,6 +61,7 @@ AUTOGEN_MSG = "# This file was *autogenerated* from the file "
 # We want to save the leading white space so that we can maintain
 # correct indentation in the preparsed file.
 load_or_attach = re.compile(r"^(?P<lws>\s*)(load|attach)\s+(?P<files>.*)$")
+future_imports = re.compile("^\s*(from __future__ import .*)$")

 def do_preparse(f, files_before=[]):
     """
@@ -126,6 +127,12 @@ def do_preparse(f, files_before=[]):
     # Preparse the body
     body = preparse_file(body)

+    # Check for "from __future__ import ..." statements. Those
+    # statements need to come at the top of the file (after the
+    # module-level docstring is okay), so we separate them from the
+    # body.
+    future_imports, body = find_future_imports(body)
+
     # Check for load/attach commands.
     body = do_load_and_attach(body, f, files_before)

@@ -138,6 +145,8 @@ def do_preparse(f, files_before=[]):
         f.write(coding)
         f.write(header)
         f.write('\n')
+        f.write(future_imports)
+        f.write('\n')
         f.write(sage_incl)
         f.write('\n')
         f.write(body)
@@ -196,6 +205,26 @@ def find_position_right_after_module_docstring(G):
         return pos_after_line(i)


+def find_future_imports(G):
+    """
+    Parse a file G, looking for "from __future import ...".
+
+    Return a tuple: (the import statements, the file G with those
+    statements removed)
+
+    INPUT:
+        G -- a string; a file loaded in from disk
+    """
+    import_statements = ''
+    new_G = ''
+    for t in G.split('\n'):
+        z = future_imports.match(t)
+        if z:
+            import_statements += z.group(1) + '\n'
+        else:
+            new_G += t + '\n'
+    return (import_statements, new_G)
+

 def do_load_and_attach(G, file, files_before):
     """
edit flag offensive delete link more

Comments

Thank you very much. I really have to learn how to make commits.

I have one observation, though: The first line of the docstring for find_future_imports says "__future" instead of "__future__"

dsejas gravatar imagedsejas ( 2019-04-25 06:11:22 +0100 )edit

Thank you, fixed.

John Palmieri gravatar imageJohn Palmieri ( 2019-04-25 16:39:00 +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

1 follower

Stats

Asked: 2019-04-24 19:14:49 +0100

Seen: 1,722 times

Last updated: Apr 25 '19