Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add %precision magic #280

Closed
wants to merge 4 commits into from

4 participants

@minrk
Owner

As requested in #190, this adds a magic %precision, which sets floating point precision for use in pretty printing. Argument can be an integer or a raw format string. If it's an integer and numpy has been imported, numpy printing precision will also be set. If no argument is given, precision will be restored to defaults (repr for float, 8 for numpy).

Examples:

    In [1]: from math import pi

    In [2]: %precision 3

    In [3]: pi
    Out[3]: 3.142

    In [4]: %precision %i

    In [5]: pi
    Out[5]: 3

    In [6]: %precision %e

    In [7]: pi**10
    Out[7]: 9.364805e+04

    In [8]: %precision

    In [9]: pi**10
    Out[9]: 93648.047476082982
IPython/core/magic.py
((34 lines not shown))
+ In [7]: pi**10
+ Out[7]: 9.364805e+04
+
+ In [8]: %precision
+
+ In [9]: pi**10
+ Out[9]: 93648.047476082982
+
+ """
+
+ if '%' in s:
+ # got explicit format string
+ fmt = s
+ try:
+ fmt%3.14159
+ except:
@takluyver Owner

Do we know which exceptions can be raised here? My informal tests suggest that, so long as we're getting a string, we can only get ValueError and TypeError, unless there's a possibility I've missed.

@minrk Owner
minrk added a note

I think that's likely, I was just not sufficiently confident that I knew what all the bases were. I can put that in (and ValueError,AssertionError at L3514).

@takluyver Owner

I understand that it's good practice to specify something, even if it's just Exception, so that you don't catch KeyboardInterrupt and SystemExit. I don't think it makes much difference for such a trivial try block, though.

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

Changed the format check to except Exception, and check specifically for ValueError,AssertionError in the integer branch.

Also changed the exception that actually raises from TypeError to ValueError, since I think that's more accurate.

@takluyver
Owner

OK, looks good to me.

@fperez
Owner

Idea and code looks good, but we shouldn't commit this without any kind of tests. I realize that in this case, a good example will likely make a doctest brittle, hence the @skip_doctest is OK. But some kind of test is necessary. An easy solution would be to make a simple doctest-only test, along the lines of the doctest_foo() tests in test_magic.py (note that those must be called doctest_foo(), and should be docstring-only functions with no code).

I also have another thought worth pausing on for a second: I'm concerned about accreting more functionality in purely magics, instead of in library code that's easier to use/test on its own, aside from the magic machinery and calling syntax. In the long run, I think we should strive to make most magics be very small wrappers that process dashed command-line flags (for convenient interactive use) and do little else beyond creating a dict of flags and calling some underlying routine, be it something standalone or some method of the various ipython objects lying around.

After we flush all the work we have on 0.11, one of the big pieces of cleanup we'll need to do is the magic system, so I think the less we continue to grow it in this direction, the easier our cleanup will be.

I have to say that I'm the most guilty party on all of this: the old magics like %run and %edit are a complete nightmare. But even if it will take us a while to clean those up, we should try to write new code in a slightly more testable manner.

@minrk
Owner

I was actually trying to use the examples for a doctest, but the test runner seems to ignore or reinitialize the DisplayFormatter for some reason, and I couldn't figure it out. Do you know why this would be?

Your comment about not putting real code in the magics makes perfect sense. Should I then place the implementation inside PlainTextFormatter (likely into a configurable Trait, like float_precision), and replace the magic with a very thin wrapper on that?

@fperez
Owner

No, no idea what can be going on with the doctest, sorry. All the more reason to make it a more library-style piece we can test functionally, without relying on the running environment. Still, I know we need to clean and simplify all that so the test environment is as normal as possible. I've made some improvements, but there's a ton more to do.

Yes, that sounds to me as good a place as any to put the implementation in.

@minrk minrk add `float_precision` trait to PlainTextFormatter
* moved content of %precision into _float_precision_changed
* %precision is now 2 lines that simply sets PTF.float_precision
* added doctests for %precision
* added tests for PTF.float_precision
a41b73b
@minrk
Owner

changes made.

  • moved content of %precision into PlainTextFormatter_float_precision_changed configurable trait
  • %precision is now 2 lines that simply sets PTF.float_precision
  • added doctests for %precision
  • added tests for PTF.float_precision
IPython/core/formatters.py
@@ -20,6 +20,7 @@ Authors:
#-----------------------------------------------------------------------------
# Stdlib imports
+import sys
@fperez Owner
fperez added a note

Let's keep import statements alphabetically ordered, it makes it easier later to find if something has already been imported or not.

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

Thanks! Other than the trivial ordering of the imports, merge away. Great job.

@minrk
Owner

Okay, thanks.

I'll go a head and merge.

@minrk
Owner

reorder stdlib imports

closed by 8e2b825

@markvoorhies markvoorhies referenced this pull request from a commit in markvoorhies/ipython
@minrk minrk reorder stdlib imports
closes gh-280
8e2b825
@ellisonbg ellisonbg referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@rominf

I try to teach my friend to make scientific computations with IPython Qt console (pylab mode). As a programmer I understand difference between float and sympy.core.numbers.Float and why printing of some numbers changes after execution:

%precision 3

and some do not, for example:

var('x')
solve(x**2-2,x)[0].n()

But it's hard to describe the difference to newbie. Calling n method with the same argument isn't pretty solution.

I think %precision function should change printing precision for Sympy's floats too.

By the way, I didn't find method for changing printing precision for sympy numbers at all. As I understood after 10-minute googling I should use sympy's printer classes, but that's very unintuitive!

PS: IMHO, problems appears because of 3 different float types exists: native Python's float, Numpy's float, Sympy's float (as I understood it uses mpmath for numbers, sigh). Are there chances for unifying floats to at most 2 different float (native and mathematical)?

@minrk
Owner

numpy actually handles its own representations of floats, the %precision magic just calls the numpy api function to change this (numpy.set_printoptions). If sympy has a similar API function, we can do the same, but display precision is more subtle in sympy.

@mattvonrocketstein mattvonrocketstein referenced this pull request from a commit in mattvonrocketstein/ipython
@minrk minrk reorder stdlib imports
closes gh-280
7b9658c
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 22, 2011
  1. @minrk

    add %precision magic

    minrk authored
    closes gh-190
  2. @minrk

    tweak exceptions in %precision

    minrk authored
  3. @minrk

    add `float_precision` trait to PlainTextFormatter

    minrk authored
    * moved content of %precision into _float_precision_changed
    * %precision is now 2 lines that simply sets PTF.float_precision
    * added doctests for %precision
    * added tests for PTF.float_precision
  4. @minrk

    reorder stdlib imports

    minrk authored
This page is out of date. Refresh to see the latest.
View
57 IPython/core/formatters.py
@@ -21,13 +21,14 @@
# Stdlib imports
import abc
+import sys
# We must use StringIO, as cStringIO doesn't handle unicode properly.
from StringIO import StringIO
# Our own imports
from IPython.config.configurable import Configurable
from IPython.external import pretty
-from IPython.utils.traitlets import Bool, Dict, Int, Str
+from IPython.utils.traitlets import Bool, Dict, Int, Str, CStr
#-----------------------------------------------------------------------------
@@ -352,13 +353,65 @@ def dtype_pprinter(obj, p, cycle):
# The newline character.
newline = Str('\n', config=True)
+
+ # format-string for pprinting floats
+ float_format = Str('%r')
+ # setter for float precision, either int or direct format-string
+ float_precision = CStr('', config=True)
+
+ def _float_precision_changed(self, name, old, new):
+ """float_precision changed, set float_format accordingly.
+
+ float_precision can be set by int or str.
+ This will set float_format, after interpreting input.
+ If numpy has been imported, numpy print precision will also be set.
+
+ integer `n` sets format to '%.nf', otherwise, format set directly.
+
+ An empty string returns to defaults (repr for float, 8 for numpy).
+
+ This parameter can be set via the '%precision' magic.
+ """
+
+ if '%' in new:
+ # got explicit format string
+ fmt = new
+ try:
+ fmt%3.14159
+ except Exception:
+ raise ValueError("Precision must be int or format string, not %r"%new)
+ elif new:
+ # otherwise, should be an int
+ try:
+ i = int(new)
+ assert i >= 0
+ except ValueError:
+ raise ValueError("Precision must be int or format string, not %r"%new)
+ except AssertionError:
+ raise ValueError("int precision must be non-negative, not %r"%i)
+
+ fmt = '%%.%if'%i
+ if 'numpy' in sys.modules:
+ # set numpy precision if it has been imported
+ import numpy
+ numpy.set_printoptions(precision=i)
+ else:
+ # default back to repr
+ fmt = '%r'
+ if 'numpy' in sys.modules:
+ import numpy
+ # numpy default is 8
+ numpy.set_printoptions(precision=8)
+ self.float_format = fmt
# Use the default pretty printers from IPython.external.pretty.
def _singleton_printers_default(self):
return pretty._singleton_pprinters.copy()
def _type_printers_default(self):
- return pretty._type_pprinters.copy()
+ d = pretty._type_pprinters.copy()
+ d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
+ return d
def _deferred_printers_default(self):
return pretty._deferred_type_pprinters.copy()
View
47 IPython/core/magic.py
@@ -3459,5 +3459,52 @@ def magic_tb(self, s):
See %xmode for changing exception reporting modes."""
self.shell.showtraceback()
+
+ @testdec.skip_doctest
+ def magic_precision(self, s=''):
+ """Set floating point precision for pretty printing.
+
+ Can set either integer precision or a format string.
+
+ If numpy has been imported and precision is an int,
+ numpy display precision will also be set, via ``numpy.set_printoptions``.
+
+ If no argument is given, defaults will be restored.
+
+ Examples
+ --------
+ ::
+
+ In [1]: from math import pi
+
+ In [2]: %precision 3
+ Out[2]: '%.3f'
+
+ In [3]: pi
+ Out[3]: 3.142
+
+ In [4]: %precision %i
+ Out[4]: '%i'
+
+ In [5]: pi
+ Out[5]: 3
+
+ In [6]: %precision %e
+ Out[6]: '%e'
+
+ In [7]: pi**10
+ Out[7]: 9.364805e+04
+
+ In [8]: %precision
+ Out[8]: '%r'
+
+ In [9]: pi**10
+ Out[9]: 93648.047476082982
+
+ """
+
+ ptformatter = self.shell.display_formatter.formatters['text/plain']
+ ptformatter.float_precision = s
+ return ptformatter.float_format
# end Magic
View
50 IPython/core/tests/test_formatters.py
@@ -1,9 +1,15 @@
"""Tests for the Formatters.
"""
+from math import pi
+
+try:
+ import numpy
+except:
+ numpy = None
import nose.tools as nt
-from IPython.core.formatters import FormatterABC, DefaultFormatter
+from IPython.core.formatters import FormatterABC, PlainTextFormatter
class A(object):
def __repr__(self):
@@ -17,7 +23,7 @@ def foo_printer(obj, pp, cycle):
pp.text('foo')
def test_pretty():
- f = DefaultFormatter()
+ f = PlainTextFormatter()
f.for_type(A, foo_printer)
nt.assert_equals(f(A()), 'foo')
nt.assert_equals(f(B()), 'foo')
@@ -26,5 +32,43 @@ def test_pretty():
nt.assert_equals(f(B()), 'B()')
def test_deferred():
- f = DefaultFormatter()
+ f = PlainTextFormatter()
+
+def test_precision():
+ """test various values for float_precision."""
+ f = PlainTextFormatter()
+ nt.assert_equals(f(pi), repr(pi))
+ f.float_precision = 0
+ if numpy:
+ po = numpy.get_printoptions()
+ nt.assert_equals(po['precision'], 0)
+ nt.assert_equals(f(pi), '3')
+ f.float_precision = 2
+ if numpy:
+ po = numpy.get_printoptions()
+ nt.assert_equals(po['precision'], 2)
+ nt.assert_equals(f(pi), '3.14')
+ f.float_precision = '%g'
+ if numpy:
+ po = numpy.get_printoptions()
+ nt.assert_equals(po['precision'], 2)
+ nt.assert_equals(f(pi), '3.14159')
+ f.float_precision = '%e'
+ nt.assert_equals(f(pi), '3.141593e+00')
+ f.float_precision = ''
+ if numpy:
+ po = numpy.get_printoptions()
+ nt.assert_equals(po['precision'], 8)
+ nt.assert_equals(f(pi), repr(pi))
+
+def test_bad_precision():
+ """test various invalid values for float_precision."""
+ f = PlainTextFormatter()
+ def set_fp(p):
+ f.float_precision=p
+ nt.assert_raises(ValueError, set_fp, '%')
+ nt.assert_raises(ValueError, set_fp, '%.3f%i')
+ nt.assert_raises(ValueError, set_fp, 'foo')
+ nt.assert_raises(ValueError, set_fp, -1)
+
View
21 IPython/core/tests/test_magic.py
@@ -386,4 +386,23 @@ def doctest_who():
In [7]: %who_ls
Out[7]: ['alpha', 'beta']
- """
+ """
+
+def doctest_precision():
+ """doctest for %precision
+
+ In [1]: f = get_ipython().shell.display_formatter.formatters['text/plain']
+
+ In [2]: %precision 5
+ Out[2]: '%.5f'
+
+ In [3]: f.float_format
+ Out[3]: '%.5f'
+
+ In [4]: %precision %e
+ Out[4]: '%e'
+
+ In [5]: f(3.1415927)
+ Out[5]: '3.141593e+00'
+ """
+
Something went wrong with that request. Please try again.