Add %precision magic #280

Closed
wants to merge 4 commits into
from
View
@@ -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
@@ -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
@@ -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)
+
@@ -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'
+ """
+