Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Autorat #1470

Merged
merged 7 commits into from

5 participants

@asmeurer
Owner

This fixes http://code.google.com/p/sympy/issues/detail?id=3182. There is now a -i option to isympy, which allows automatic wrapping of integer literals with Integer. The result is that things like 1/2 will be converted to Rational(1, 2) automatically. For example:

$./bin/isympy  -i
IPython console for SymPy 0.7.1-git (Python 2.7.3-64-bit) (ground types: python)

These commands were executed:
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)

Documentation can be found at http://www.sympy.org

In [1]: 1/2
Out[1]: 1/2

In [2]: x**(1/2)
Out[2]: 
  ___
╲╱ x 

To get a Python int, you can wrap the literal with int(). This works by preprocessing the code with tokenize and wrapping all non-float number literals with Integer. There was some discussion about the best way to make IPython do this (see the commit message and the discussions linked to there).

Still todo: tests.

asmeurer added some commits
@asmeurer asmeurer Add -i, --int-to-Integer option to isympy
This makes isympy automatically wrap integer literals with Integer.  The
result is that things like 1/2 are computed as Rational(1, 2) instead of 0.5.
For example:

$./bin/isympy -i
IPython console for SymPy 0.7.1-git (Python 2.7.3-64-bit) (ground types:
python)

These commands were executed:
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)

Documentation can be found at http://www.sympy.org

In [1]: 1/2
Out[1]: 1/2

In [2]: x**(1/2)
Out[2]:
    ___
  ╲╱ x

You can still get a int literal by wrapping the integer with int()

In [3]: int(1)/int(2)
Out[3]: 0.5

Note that this requires IPython.

Also added -I, --interactive, which combines this option with -a,
--auto-symbols (which has been renamed from --auto).  Additional such
interactive modifications should be included in this command line argument.

Note that this had to be done by monkey-patching IPython's run_cell. IPython's
current API for preprocessing lines is not expressive enough to handle
this--in particular, it does not allow modifying multiple line inputs.  See
http://mail.scipy.org/pipermail/ipython-user/2012-August/010846.html and
ipython/ipython#1491 for more information.

Thanks to http://stackoverflow.com/a/11735604/161801 for suggesting the
tokenize method of doing this.

This fixes issue 3182.
a4ebdab
@asmeurer asmeurer Use ast instead of compiler, which was removed in Python 3 a8eb22c
@asmeurer asmeurer Add some more tests for isympy -a 116e910
@asmeurer asmeurer Add some tests for isympy -i 42fdfe2
@asmeurer
Owner

OK, some tests added.

@travisbot

This pull request fails (merged a8eb22c into 3c2c3c7).

@asmeurer asmeurer Remove a binary literal from a doctest
They are not supported in Python 2.5
f90ea7e
@travisbot

This pull request fails (merged 42fdfe2 into 3c2c3c7).

@asmeurer
Owner

Huh, apparently binary literals were added in Python 2.6. I did not know that.

@travisbot

This pull request passes (merged f90ea7e into 3c2c3c7).

@certik certik commented on the diff
sympy/interactive/session.py
((28 lines not shown))
+ from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP
+ from StringIO import StringIO
+
+ result = []
+ g = generate_tokens(StringIO(s).readline) # tokenize the string
+ for toknum, tokval, _, _, _ in g:
+ if toknum == NUMBER and '.' not in tokval: # replace NUMBER tokens
+ result.extend([
+ (NAME, 'Integer'),
+ (OP, '('),
+ (NUMBER, tokval),
+ (OP, ')')
+ ])
+ else:
+ result.append((toknum, tokval))
+ return untokenize(result)
@certik Owner
certik added a note

Looks simple enough and should work in Python 3.x as well:

http://docs.python.org/dev/library/tokenize.html#examples

The only difference in there is the use of BytesIO instead of StringIO.

@asmeurer Owner
asmeurer added a note

I guess 2to3 changes this automatically, because I tested in Python 3 and it works.

@asmeurer Owner
asmeurer added a note

Actually, just checked, and it changes it to from io import StringIO.

@certik Owner
certik added a note

Cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@certik certik commented on the diff
sympy/interactive/session.py
((95 lines not shown))
+ # Check the cell for syntax errors. This way, the syntax error
+ # will show the original input, not the transformed input. The
+ # downside here is that IPython magic like %timeit will not work
+ # with transformed input (but on the other hand, IPython magic
+ # that doesn't expect transformed input will continue to work).
+ ast.parse(cell)
+ except SyntaxError:
+ pass
+ else:
+ cell = int_to_Integer(cell)
+ old_run_cell(cell, *args, **kwargs)
+
+ if hasshell:
+ app.shell.run_cell = my_run_cell
+ else:
+ app.run_cell = my_run_cell
@certik Owner
certik added a note

Again, simple enough to maintain the monkey patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@certik certik commented on the diff
sympy/interactive/session.py
((12 lines not shown))
+ Example
+ =======
+
+ >>> from sympy.interactive.session import int_to_Integer
+ >>> from sympy import Integer
+ >>> s = '1.2 + 1/2 - 0x12 + a1'
+ >>> int_to_Integer(s)
+ '1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 '
+ >>> s = 'print (1/2)'
+ >>> int_to_Integer(s)
+ 'print (Integer (1 )/Integer (2 ))'
+ >>> exec(s) #doctest: +SKIP
+ 0.5
+ >>> exec(int_to_Integer(s))
+ 1/2
+ """
@certik Owner
certik added a note

This doctest is the only test that we have for this function. Can you also add a regular test for this?

@asmeurer Owner
asmeurer added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@smichr smichr commented on the diff
bin/isympy
@@ -129,6 +129,27 @@ COMMAND LINE OPTIONS
example, if you define a function in isympy with an undefined
Symbol, it will not work.
+ See also the -i and -I options.
+
+-i, --int-to-Integer
+
+ Automatically wrap int literals with Integer. This makes it so that
+ things like 1/2 will come out as Rational(1, 2), rather than 0.5. This
+ works by preprocessing the source and wrapping all int literals with
+ Integer. Note that this will not change the behavior of int literals
+ assigned to variables, and it also won't change the behavior of functions
+ that return int literals.
@smichr Collaborator
smichr added a note

Should a note be added that this overrides "from future import division"? It seems pretty clear that this is the case, though.

@asmeurer Owner
asmeurer added a note

It doesn't, though. It just wraps integer literals in Integer. int(1)/int(2) will still give 0.5 (instead of 0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@travisbot

This pull request passes (merged c1aaae1 into 3c2c3c7).

@Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: bd49e6a
branch hash: c1aaae1

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYwrYiDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYnu0iDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjJQjDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYm_AhDA

Automatic review by SymPy Bot.

@asmeurer
Owner

SymPy Bot Summary: :red_circle: There were test failures (merged asmeurer/autorat (c1aaae1) into master (bd49e6a)).

@asmeurer: Please fix the test failures.

Interpreter 1: :red_circle: There were test failures.
Interpreter: None (2.5.0-final-0)
Architecture: Darwin (32-bit)

Interpreter 2: :red_circle: There were test failures.
Interpreter: None (2.6.6-final-0)
Architecture: Darwin (32-bit)

Interpreter 3: :red_circle: There were test failures.
Interpreter: None (2.7.2-final-0)
Architecture: Darwin (32-bit)

Interpreter 4: :eight_spoked_asterisk: All tests have passed.
Interpreter: /sw/bin//python2.6 (2.6.8-final-0)
Architecture: Darwin (64-bit)

Interpreter 5: :eight_spoked_asterisk: All tests have passed.
Interpreter: /sw/bin//python2.7 (2.7.3-final-0)
Architecture: Darwin (64-bit)

Interpreter 6: :eight_spoked_asterisk: All tests have passed.
Interpreter: None (3.2.2-final-0)
Architecture: Darwin (32-bit)

Interpreter 7: :red_circle: There were test failures.
Interpreter: None (3.3.0-beta-1)
Architecture: Darwin (32-bit)

Interpreter 8: :eight_spoked_asterisk: All tests have passed.
Interpreter: /sw/bin//python3.2 (3.2.3-final-0)
Architecture: Darwin (64-bit)

Interpreter 9: :red_circle: There were test failures.
Interpreter: None (3.3.0-beta-1)
Architecture: Darwin (64-bit)

Build HTML Docs: :red_circle: There were test failures.
Sphinx version: 1.1.3

@certik
Owner

There is currently a big bug:

In [1]: e = 1e-100

In [2]: e                                                                      
Out[2]: 0

The reason being (by looking into ipython history):


In [1]: e =Integer (1e-100 )

In [2]: e                                                                      
Out[2]: 0

This bit me today...

@certik certik commented on the diff
sympy/interactive/tests/test_interactive.py
@@ -0,0 +1,14 @@
+import sys
+
+from sympy.interactive.session import int_to_Integer
+
+def test_int_to_Integer():
+ assert int_to_Integer("1 + 2.2 + 0x3 + 40") == \
+ 'Integer (1 )+2.2 +Integer (0x3 )+Integer (40 )'
+ if sys.version_info[0] == 2:
+ assert int_to_Integer("1l") == 'Integer (1l )'
+ if sys.version_info[1] > 5 or sys.version_info[0] == 3:
+ # Binary literals were added in Python 2.6
+ assert int_to_Integer("0b101") == 'Integer (0b101 )'
+ assert int_to_Integer("ab1 + 1 + '1 + 2'") == "ab1 +Integer (1 )+'1 + 2'"
+ assert int_to_Integer("(2 + \n3)") == '(Integer (2 )+\nInteger (3 ))'
@certik Owner
certik added a note

It's missing a test for "1e-100", which will fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@asmeurer
Owner

Ahh, good catch. I made the assumption that a floating point literal must contain a ., but this is clearly not true. I guess I also missed complex literals. Can you think of any other cases of a float literal? Or maybe there is something in the standard library that automates this.

@asmeurer
Owner

Pushed a fix.

@travisbot

This pull request passes (merged 64366c2 into 3c2c3c7).

@asmeurer
Owner

I guess I can just eval it and check the type. Let me see if that is not too slow.

@asmeurer
Owner

The way I'm doing it seems to be much faster, so I'll just stick with it for now.

@Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 15c2c89
branch hash: 64366c2

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYzrYiDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY7dUiDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYrO0iDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYyo8iDA

Automatic review by SymPy Bot.

@smichr

from http://stackoverflow.com/questions/379906/python-parse-string-to-float-or-int

try:
    i = int(num)
except ValueError:
    return False
return True

The same link suggests using ast.literal_eval so you could do return type(ast.literal_eval(num)) is int.

Owner

That doesn't work for non-base ten literals. See http://stackoverflow.com/questions/11874087/determine-the-kind-int-float-of-number-a-python-numeric-literal-represents. I left it as it is because it's faster this way (and also ast is not in Python 2.5, but I just realized this won't work in Python 2.5 anyway because IPython is 2.6+).

@certik
Owner

This looks good enough, so I am merging it. We can improve upon the master if needed.

@certik certik merged commit 08617ed into from
@asmeurer
Owner

Thanks. I also added this to the release notes for the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 7, 2012
  1. @asmeurer

    Add -i, --int-to-Integer option to isympy

    asmeurer authored
    This makes isympy automatically wrap integer literals with Integer.  The
    result is that things like 1/2 are computed as Rational(1, 2) instead of 0.5.
    For example:
    
    $./bin/isympy -i
    IPython console for SymPy 0.7.1-git (Python 2.7.3-64-bit) (ground types:
    python)
    
    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    
    Documentation can be found at http://www.sympy.org
    
    In [1]: 1/2
    Out[1]: 1/2
    
    In [2]: x**(1/2)
    Out[2]:
        ___
      ╲╱ x
    
    You can still get a int literal by wrapping the integer with int()
    
    In [3]: int(1)/int(2)
    Out[3]: 0.5
    
    Note that this requires IPython.
    
    Also added -I, --interactive, which combines this option with -a,
    --auto-symbols (which has been renamed from --auto).  Additional such
    interactive modifications should be included in this command line argument.
    
    Note that this had to be done by monkey-patching IPython's run_cell. IPython's
    current API for preprocessing lines is not expressive enough to handle
    this--in particular, it does not allow modifying multiple line inputs.  See
    http://mail.scipy.org/pipermail/ipython-user/2012-August/010846.html and
    ipython/ipython#1491 for more information.
    
    Thanks to http://stackoverflow.com/a/11735604/161801 for suggesting the
    tokenize method of doing this.
    
    This fixes issue 3182.
  2. @asmeurer
  3. @asmeurer
  4. @asmeurer
  5. @asmeurer

    Remove a binary literal from a doctest

    asmeurer authored
    They are not supported in Python 2.5
Commits on Aug 8, 2012
  1. @asmeurer
  2. @asmeurer
This page is out of date. Refresh to see the latest.
View
55 bin/isympy
@@ -103,7 +103,7 @@ COMMAND LINE OPTIONS
This is equivalent to setting the environment variable
SYMPY_USE_CACHE='no'.
--a, --auto
+-a, --auto-symbols
Automatically create missing symbols. Normally, typing a name of a
Symbol that has not been instantiated first would raise NameError,
@@ -129,6 +129,27 @@ COMMAND LINE OPTIONS
example, if you define a function in isympy with an undefined
Symbol, it will not work.
+ See also the -i and -I options.
+
+-i, --int-to-Integer
+
+ Automatically wrap int literals with Integer. This makes it so that
+ things like 1/2 will come out as Rational(1, 2), rather than 0.5. This
+ works by preprocessing the source and wrapping all int literals with
+ Integer. Note that this will not change the behavior of int literals
+ assigned to variables, and it also won't change the behavior of functions
+ that return int literals.
@smichr Collaborator
smichr added a note

Should a note be added that this overrides "from future import division"? It seems pretty clear that this is the case, though.

@asmeurer Owner
asmeurer added a note

It doesn't, though. It just wraps integer literals in Integer. int(1)/int(2) will still give 0.5 (instead of 0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ If you want an int, you can wrap the literal in int().
+
+ Note that this requires IPython.
+
+-I, --interactive
+
+ This is equivalent to --auto-symbols --int-to-Integer. Future options
+ designed for ease of interactive use may be added to this. Note that this
+ requires IPython.
+
-D, --debug
Enable debugging output. This is the same as setting the
@@ -137,10 +158,11 @@ COMMAND LINE OPTIONS
-- IPython options
- Additionally you can pass command line options directly to the
- IPython interpreter (the standard Python shell is not supported).
- However you need to add '--' separator between two types of options.
- To run SymPy without startup banner and colors, for example, issue in IPython 0.11 or higher:
+ Additionally you can pass command line options directly to the IPython
+ interpreter (the standard Python shell is not supported). However you
+ need to add '--' separator between two types of options. To run SymPy
+ without startup banner and colors, for example, issue in IPython 0.11 or
+ higher:
$isympy -q -- --colors=NoColor
@@ -149,6 +171,7 @@ COMMAND LINE OPTIONS
$isympy -q -- -colors NoColor
See also isympy --help.
+
"""
import os, sys, warnings
@@ -240,13 +263,27 @@ def main():
help='disable caching mechanism')
parser.add_option(
- '-a', '--auto',
- dest='auto',
+ '-a', '--auto-symbols',
+ dest='auto_symbols',
action='store_true',
default=False,
help='automatically construct missing symbols')
parser.add_option(
+ '-i', '--int-to-Integer',
+ dest='auto_int_to_Integer',
+ action='store_true',
+ default=False,
+ help="automatically wrap int literals with Integer")
+
+ parser.add_option(
+ '-I', '--interactive',
+ dest='interactive',
+ action='store_true',
+ default=False,
+ help="equivalent to -a -i")
+
+ parser.add_option(
'-D', '--debug',
dest='debug',
action='store_true',
@@ -293,7 +330,9 @@ def main():
args['order'] = options.order
args['quiet'] = options.quiet
- args['auto'] = options.auto
+ args['auto_symbols'] = options.auto_symbols or options.interactive
+ args['auto_int_to_Integer'] = options.auto_int_to_Integer or options.interactive
+
from sympy.utilities.exceptions import SymPyDeprecationWarning
warnings.simplefilter("always", SymPyDeprecationWarning)
View
181 sympy/interactive/session.py
@@ -68,8 +68,162 @@ def _make_message(ipython=True, quiet=False, source=None):
return message
+def int_to_Integer(s):
+ """
+ Wrap integer literals with Integer.
+
+ This is based on the decistmt example from
+ http://docs.python.org/library/tokenize.html.
+
+ Only integer literals are converted. Float literals are left alone.
+ Example
+ =======
+
+ >>> from sympy.interactive.session import int_to_Integer
+ >>> from sympy import Integer
+ >>> s = '1.2 + 1/2 - 0x12 + a1'
+ >>> int_to_Integer(s)
+ '1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 '
+ >>> s = 'print (1/2)'
+ >>> int_to_Integer(s)
+ 'print (Integer (1 )/Integer (2 ))'
+ >>> exec(s) #doctest: +SKIP
+ 0.5
+ >>> exec(int_to_Integer(s))
+ 1/2
+ """
@certik Owner
certik added a note

This doctest is the only test that we have for this function. Can you also add a regular test for this?

@asmeurer Owner
asmeurer added a note

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP
+ from StringIO import StringIO
+
+ def _is_int(num):
+ """
+ Returns true if string value num (with token NUMBER) represents an integer.
+ """
+ # XXX: Is there something in the standard library that will do this?
+ if '.' in num or 'j' in num.lower() or 'e' in num.lower():
+ return False
+ return True
+
+ result = []
+ g = generate_tokens(StringIO(s).readline) # tokenize the string
+ for toknum, tokval, _, _, _ in g:
+ if toknum == NUMBER and _is_int(tokval): # replace NUMBER tokens
+ result.extend([
+ (NAME, 'Integer'),
+ (OP, '('),
+ (NUMBER, tokval),
+ (OP, ')')
+ ])
+ else:
+ result.append((toknum, tokval))
+ return untokenize(result)
@certik Owner
certik added a note

Looks simple enough and should work in Python 3.x as well:

http://docs.python.org/dev/library/tokenize.html#examples

The only difference in there is the use of BytesIO instead of StringIO.

@asmeurer Owner
asmeurer added a note

I guess 2to3 changes this automatically, because I tested in Python 3 and it works.

@asmeurer Owner
asmeurer added a note

Actually, just checked, and it changes it to from io import StringIO.

@certik Owner
certik added a note

Cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+# XXX: Something like this might be used, but it only works on single line
+# inputs. See
+# http://mail.scipy.org/pipermail/ipython-user/2012-August/010846.html and
+# https://github.com/ipython/ipython/issues/1491. So instead we are forced to
+# just monkey-patch run_cell until IPython builds a better API.
+#
+# class IntTransformer(object):
+# """
+# IPython command line transformer that recognizes and replaces int
+# literals.
+#
+# Based on
+# https://bitbucket.org/birkenfeld/ipython-physics/src/71b2d850da00/physics.py.
+#
+# """
+# priority = 99
+# enabled = True
+# def transform(self, line, continue_prompt):
+# import re
+# from tokenize import TokenError
+# leading_space = re.compile(' *')
+# spaces = re.match(leading_space, line).span()[1]
+# try:
+# return ' '*spaces + int_to_Integer(line)
+# except TokenError:
+# return line
+#
+# int_transformer = IntTransformer()
+#
+# def enable_automatic_int_sympification(app):
+# """
+# Allow IPython to automatically convert integer literals to Integer.
+#
+# This lets things like 1/2 be executed as (essentially) Rational(1, 2).
+# """
+# app.shell.prefilter_manager.register_transformer(int_transformer)
+
+def enable_automatic_int_sympification(app):
+ """
+ Allow IPython to automatically convert integer literals to Integer.
+ """
+ hasshell = hasattr(app, 'shell')
+
+ import ast
+ if hasshell:
+ old_run_cell = app.shell.run_cell
+ else:
+ old_run_cell = app.run_cell
+ def my_run_cell(cell, *args, **kwargs):
+ try:
+ # Check the cell for syntax errors. This way, the syntax error
+ # will show the original input, not the transformed input. The
+ # downside here is that IPython magic like %timeit will not work
+ # with transformed input (but on the other hand, IPython magic
+ # that doesn't expect transformed input will continue to work).
+ ast.parse(cell)
+ except SyntaxError:
+ pass
+ else:
+ cell = int_to_Integer(cell)
+ old_run_cell(cell, *args, **kwargs)
+
+ if hasshell:
+ app.shell.run_cell = my_run_cell
+ else:
+ app.run_cell = my_run_cell
@certik Owner
certik added a note

Again, simple enough to maintain the monkey patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
def enable_automatic_symbols(app):
"""Allow IPython to automatially create symbols (``isympy -a``). """
+ # XXX: This should perhaps use tokenize, like int_to_Integer() above.
+ # This would avoid re-executing the code, which can lead to subtle
+ # issues. For example:
+ #
+ # In [1]: a = 1
+ #
+ # In [2]: for i in range(10):
+ # ...: a += 1
+ # ...:
+ #
+ # In [3]: a
+ # Out[3]: 11
+ #
+ # In [4]: a = 1
+ #
+ # In [5]: for i in range(10):
+ # ...: a += 1
+ # ...: print b
+ # ...:
+ # b
+ # b
+ # b
+ # b
+ # b
+ # b
+ # b
+ # b
+ # b
+ # b
+ #
+ # In [6]: a
+ # Out[6]: 12
+ #
+ # Note how the for loop is executed again because `b` was not defined, but `a`
+ # was already incremented once, so the result is that it is incremented
+ # multiple times.
+
import re
re_nameerror = re.compile("name '(?P<symbol>[A-Za-z_][A-Za-z0-9_]*)' is not defined")
@@ -100,7 +254,7 @@ def _handler(self, etype, value, tb, tb_offset=None):
# This was restructured in IPython 0.13
app.set_custom_exc((NameError,), _handler)
-def init_ipython_session(argv=[], auto=False):
+def init_ipython_session(argv=[], auto_symbols=False, auto_int_to_Integer=False):
"""Construct new IPython session. """
import IPython
@@ -113,8 +267,10 @@ def init_ipython_session(argv=[], auto=False):
app.display_banner = False
app.initialize(argv)
- if auto:
+ if auto_symbols:
enable_automatic_symbols(app)
+ if auto_int_to_Integer:
+ enable_automatic_int_sympification(app)
return app.shell
else:
@@ -154,7 +310,7 @@ def __init__(self):
return SymPyConsole()
def init_session(ipython=None, pretty_print=True, order=None,
- use_unicode=None, quiet=False, auto=False, argv=[]):
+ use_unicode=None, quiet=False, auto_symbols=False, auto_int_to_Integer=False, argv=[]):
"""
Initialize an embedded IPython or Python session.
@@ -177,9 +333,15 @@ def init_session(ipython=None, pretty_print=True, order=None,
quiet: boolean
If True, init_session will not print messages regarding its status;
if False, init_session will print messages regarding its status.
- auto: boolean
- If True, init_session will automatically create symbols for you;
- if False, it will not.
+ auto_symbols: boolean
+ If True, IPython will automatically create symbols for you.
+ If False, it will not.
+ The default is False.
+ auto_int_to_Integer: boolean
+ If True, IPython will automatically wrap int literals with Integer, so
+ that things like 1/2 give Rational(1, 2).
+ If False, it will not.
+ The default is False.
ipython: boolean or None
If True, printing will initialize for an IPython console;
if False, printing will initialize for a normal console;
@@ -260,7 +422,8 @@ def init_session(ipython=None, pretty_print=True, order=None,
if ip is not None:
in_ipython = True
else:
- ip = init_ipython_session(argv=argv, auto=auto)
+ ip = init_ipython_session(argv=argv,
+ auto_symbols=auto_symbols, auto_int_to_Integer=auto_int_to_Integer)
if IPython.__version__ >= '0.11':
# runsource is gone, use run_cell instead, which doesn't
@@ -270,8 +433,10 @@ def init_session(ipython=None, pretty_print=True, order=None,
if not in_ipython:
mainloop = ip.mainloop
- if auto and (not ipython or IPython.__version__ < '0.11'):
+ if auto_symbols and (not ipython or IPython.__version__ < '0.11'):
raise RuntimeError("automatic construction of symbols is possible only in IPython 0.11 or above")
+ if auto_int_to_Integer and (not ipython or IPython.__version__ < '0.11'):
+ raise RuntimeError("automatic int to Integer transformation is possible only in IPython 0.11 or above")
_preexec_source = preexec_source
View
15 sympy/interactive/tests/test_interactive.py
@@ -0,0 +1,15 @@
+import sys
+
+from sympy.interactive.session import int_to_Integer
+
+def test_int_to_Integer():
+ assert int_to_Integer("1 + 2.2 + 0x3 + 40") == \
+ 'Integer (1 )+2.2 +Integer (0x3 )+Integer (40 )'
+ if sys.version_info[0] == 2:
+ assert int_to_Integer("1l") == 'Integer (1l )'
+ if sys.version_info[1] > 5 or sys.version_info[0] == 3:
+ # Binary literals were added in Python 2.6
+ assert int_to_Integer("0b101") == 'Integer (0b101 )'
+ assert int_to_Integer("ab1 + 1 + '1 + 2'") == "ab1 +Integer (1 )+'1 + 2'"
+ assert int_to_Integer("(2 + \n3)") == '(Integer (2 )+\nInteger (3 ))'
@certik Owner
certik added a note

It's missing a test for "1e-100", which will fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ assert int_to_Integer("2 + 2.0 + 2j + 2e-10") == 'Integer (2 )+2.0 +2j +2e-10 '
View
33 sympy/interactive/tests/test_ipython.py
@@ -1,8 +1,9 @@
"""Tests of tools for setting up interactive IPython sessions. """
-from sympy.interactive.session import init_ipython_session, enable_automatic_symbols
+from sympy.interactive.session import (init_ipython_session,
+ enable_automatic_symbols, enable_automatic_int_sympification)
-from sympy.core import Symbol
+from sympy.core import Symbol, Rational, Integer
from sympy.external import import_module
from sympy.utilities.pytest import raises
@@ -31,3 +32,31 @@ def test_automatic_symbols():
app.run_cell(symbol, False)
assert symbol in app.user_ns
assert isinstance(app.user_ns[symbol], Symbol)
+
+ # Check that built-in names aren't overridden
+ app.run_cell("a = all == __builtin__.all", False)
+ assert "all" not in app.user_ns
+ assert app.user_ns['a'] == True
+
+ # Check that sympy names aren't overridden
+ app.run_cell("import sympy")
+ app.run_cell("a = factorial == sympy.factorial")
+ assert app.user_ns['a'] == True
+
+def test_int_to_Integer():
+ # XXX: Warning, don't test with == here. 0.5 == Rational(1, 2) is True!
+ app = init_ipython_session()
+ app.run_cell("a = 1")
+ assert isinstance(app.user_ns['a'], int)
+
+ enable_automatic_int_sympification(app)
+ app.run_cell("a = 1/2")
+ assert isinstance(app.user_ns['a'], Rational)
+ app.run_cell("a = 1")
+ assert isinstance(app.user_ns['a'], Integer)
+ app.run_cell("a = int(1)")
+ assert isinstance(app.user_ns['a'], int)
+ app.run_cell("a = (1/\n2)")
+ assert app.user_ns['a'] == Rational(1, 2)
+ # TODO: How can we test that the output of a SyntaxError is the original
+ # input, not the transformed input?
Something went wrong with that request. Please try again.