Skip to content

Commit af61c52

Browse files
committed
fix #274 : implemented get_options() and set_options() to set global printing options for arrays
1 parent 0606e25 commit af61c52

File tree

9 files changed

+250
-23
lines changed

9 files changed

+250
-23
lines changed

doc/source/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,8 @@ Miscellaneous
664664
from_frame
665665
from_series
666666
get_example_filepath
667+
set_options
668+
get_options
667669
labels_array
668670
union
669671
stack

doc/source/changes/version_0_30.rst.inc

+49
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,55 @@ Miscellaneous improvements
224224
* implemented :py:obj:`LArray.reverse()` method to reverse one or several axes of an array (closes :issue:`631`).
225225

226226

227+
* added :py:obj:`set_options` allowing to set options for larray within a ``with`` block or globally:
228+
229+
>>> from larray import *
230+
>>> arr = ndtest((500, 100), dtype=float) + 0.123456
231+
232+
You can use ``set_options`` either as a context manager:
233+
234+
>>> # display_width defines the maximum display width when printing an array
235+
>>> # display_edgeitems defines the number of lines of the header and tail to display
236+
>>> with set_options(display_width=100, display_edgeitems=2):
237+
... print(arr)
238+
a\b b0 b1 b2 ... b97 b98 b99
239+
a0 0.123456 1.123456 2.123456 ... 97.123456 98.123456 99.123456
240+
a1 100.123456 101.123456 102.123456 ... 197.123456 198.123456 199.123456
241+
... ... ... ... ... ... ... ...
242+
a498 49800.123456 49801.123456 49802.123456 ... 49897.123456 49898.123456 49899.123456
243+
a499 49900.123456 49901.123456 49902.123456 ... 49997.123456 49998.123456 49999.123456
244+
245+
Or to set global options:
246+
247+
>>> # display_maxlines defines the maximum number of lines to show
248+
>>> # display_precision defines the number of digits of precision for floating point output
249+
>>> set_options(display_maxlines=10, display_precision=2)
250+
>>> print(arr)
251+
a\b b0 b1 b2 ... b97 b98 b99
252+
a0 0.12 1.12 2.12 ... 97.12 98.12 99.12
253+
a1 100.12 101.12 102.12 ... 197.12 198.12 199.12
254+
a2 200.12 201.12 202.12 ... 297.12 298.12 299.12
255+
a3 300.12 301.12 302.12 ... 397.12 398.12 399.12
256+
a4 400.12 401.12 402.12 ... 497.12 498.12 499.12
257+
... ... ... ... ... ... ... ...
258+
a495 49500.12 49501.12 49502.12 ... 49597.12 49598.12 49599.12
259+
a496 49600.12 49601.12 49602.12 ... 49697.12 49698.12 49699.12
260+
a497 49700.12 49701.12 49702.12 ... 49797.12 49798.12 49799.12
261+
a498 49800.12 49801.12 49802.12 ... 49897.12 49898.12 49899.12
262+
a499 49900.12 49901.12 49902.12 ... 49997.12 49998.12 49999.12
263+
264+
To put back the default options, you can use:
265+
266+
>>> set_options(display_precision=None, display_width=80, display_maxlines=200, display_edgeitems=5)
267+
268+
The :py:obj:`get_options` function returns a view of the current options as a dictionary:
269+
270+
>>> get_options()
271+
{'display_precision': None, 'display_width': 80, 'display_maxlines': 200, 'display_edgeitems': 5}
272+
273+
Closes :issue:`274`.
274+
275+
227276
Fixes
228277
-----
229278

larray/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from larray.inout.sas import read_sas
3131
from larray.inout.xw_excel import open_excel, Workbook
3232

33+
from larray.util.options import get_options, set_options
34+
3335
from larray.viewer import view, edit, compare
3436

3537
from larray.extra.ipfp import ipfp
@@ -68,6 +70,8 @@
6870
# inout
6971
'from_lists', 'from_string', 'from_frame', 'from_series', 'read_csv', 'read_tsv',
7072
'read_eurostat', 'read_excel', 'read_hdf', 'read_sas', 'open_excel', 'Workbook',
73+
# utils
74+
'get_options', 'set_options',
7175
# viewer
7276
'view', 'edit', 'compare',
7377
# ipfp

larray/core/array.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from larray.util.misc import (table2str, size2str, basestring, izip, rproduct, ReprString, duplicates,
6666
float_error_handler_factory, _isnoneslice, light_product, unique_list, common_type,
6767
renamed_to, deprecate_kwarg, LHDFStore, lazy_attribute)
68+
from larray.util.options import _OPTIONS, DISPLAY_MAXLINES, DISPLAY_EDGEITEMS, DISPLAY_WIDTH, DISPLAY_PRECISION
6869

6970

7071
def all(values, axis=None):
@@ -2277,8 +2278,9 @@ def __str__(self):
22772278
elif not len(self):
22782279
return 'LArray([])'
22792280
else:
2280-
table = list(self.as_table(maxlines=200, edgeitems=5))
2281-
return table2str(table, 'nan', fullinfo=True, maxwidth=200, keepcols=self.ndim - 1)
2281+
table = list(self.as_table(_OPTIONS[DISPLAY_MAXLINES], _OPTIONS[DISPLAY_EDGEITEMS]))
2282+
return table2str(table, 'nan', maxwidth=_OPTIONS[DISPLAY_WIDTH], keepcols=self.ndim - 1,
2283+
precision=_OPTIONS[DISPLAY_PRECISION])
22822284
__repr__ = __str__
22832285

22842286
def __iter__(self):
@@ -2287,19 +2289,22 @@ def __iter__(self):
22872289
def __contains__(self, key):
22882290
return any(key in axis for axis in self.axes)
22892291

2290-
def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_name='value'):
2291-
"""
2292+
def as_table(self, maxlines=-1, edgeitems=5, light=False, wide=True, value_name='value'):
2293+
r"""
22922294
Generator. Returns next line of the table representing an array.
22932295
22942296
Parameters
22952297
----------
22962298
maxlines : int, optional
2297-
Maximum number of lines to show.
2299+
Maximum number of lines to show. Defaults to -1 (all lines are shown).
22982300
edgeitems : int, optional
22992301
If number of lines to display is greater than `maxlines`,
23002302
only the first and last `edgeitems` lines are displayed.
2301-
Only active if `maxlines` is not None.
2302-
Equals to 5 by default.
2303+
Only active if `maxlines` is not -1.
2304+
Defaults to 5.
2305+
light : bool, optional
2306+
Whether or not to hide repeated labels. In other words, only show a label if it is different from the
2307+
previous one. Defaults to False.
23032308
wide : boolean, optional
23042309
Whether or not to write arrays in "wide" format. If True, arrays are exported with the last axis
23052310
represented horizontally. If False, arrays are exported in "narrow" format: one column per axis plus one
@@ -2317,13 +2322,13 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23172322
--------
23182323
>>> arr = ndtest((2, 2, 3))
23192324
>>> list(arr.as_table()) # doctest: +NORMALIZE_WHITESPACE
2320-
[['a', 'b\\\\c', 'c0', 'c1', 'c2'],
2325+
[['a', 'b\\c', 'c0', 'c1', 'c2'],
23212326
['a0', 'b0', 0, 1, 2],
23222327
['a0', 'b1', 3, 4, 5],
23232328
['a1', 'b0', 6, 7, 8],
23242329
['a1', 'b1', 9, 10, 11]]
23252330
>>> list(arr.as_table(light=True)) # doctest: +NORMALIZE_WHITESPACE
2326-
[['a', 'b\\\\c', 'c0', 'c1', 'c2'],
2331+
[['a', 'b\\c', 'c0', 'c1', 'c2'],
23272332
['a0', 'b0', 0, 1, 2],
23282333
['', 'b1', 3, 4, 5],
23292334
['a1', 'b0', 6, 7, 8],
@@ -2379,7 +2384,7 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23792384
other_colnames = self.axes[-1].labels.tolist() if wide else [value_name]
23802385
yield axes_names + other_colnames
23812386
# summary if needed
2382-
if maxlines is not None and height > maxlines:
2387+
if maxlines >= 0 and height > maxlines:
23832388
# replace middle lines of the table by '...'.
23842389
# We show only the first and last edgeitems lines.
23852390
startticks = islice(ticks, edgeitems)
@@ -2398,7 +2403,8 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23982403
yield list(tick) + dataline.tolist()
23992404

24002405
def dump(self, header=True, wide=True, value_name='value'):
2401-
"""Dump array as a 2D nested list
2406+
"""
2407+
Dump array as a 2D nested list
24022408
24032409
Parameters
24042410
----------
@@ -2420,7 +2426,7 @@ def dump(self, header=True, wide=True, value_name='value'):
24202426
# flatten all dimensions except the last one
24212427
return self.data.reshape(-1, self.shape[-1]).tolist()
24222428
else:
2423-
return list(self.as_table(wide=wide, value_name=value_name))
2429+
return list(self.as_table(maxlines=-1, wide=wide, value_name=value_name))
24242430

24252431
# XXX: should filter(geo=['W']) return a view by default? (collapse=True)
24262432
# I think it would be dangerous to make it the default

larray/tests/test_array.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,8 @@ def test_str(small_array, array):
309309
115 A21 F 153105.0 153106.0 153107.0"""
310310
# too many columns
311311
assert str(array['P01', 'A11', 'M']) == """\
312-
age 0 1 2 3 4 5 6 7 8 ... \
313-
106 107 108 109 110 111 112 113 114 115
314-
0.0 1320.0 2640.0 3960.0 5280.0 6600.0 7920.0 9240.0 10560.0 ... \
315-
139920.0 141240.0 142560.0 143880.0 145200.0 146520.0 147840.0 149160.0 150480.0 151800.0"""
312+
age 0 1 2 ... 112 113 114 115
313+
0.0 1320.0 2640.0 ... 147840.0 149160.0 150480.0 151800.0"""
316314

317315
arr = LArray([0, ''], Axis(['a0', ''], 'a'))
318316
assert str(arr) == "a a0 \n 0 "

larray/tests/test_options.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import absolute_import, division, print_function
2+
import pytest
3+
import larray
4+
5+
6+
def test_invalid_option_raises():
7+
with pytest.raises(ValueError):
8+
larray.set_options(not_a_valid_options=True)
9+
10+
11+
def test_set_options_as_global():
12+
original_ops = larray.get_options()
13+
arr = larray.ndtest((500, 100))
14+
larray.set_options(display_width=40, display_maxlines=10)
15+
expected = """\
16+
a\\b b0 b1 ... b98 b99
17+
a0 0 1 ... 98 99
18+
a1 100 101 ... 198 199
19+
a2 200 201 ... 298 299
20+
a3 300 301 ... 398 399
21+
a4 400 401 ... 498 499
22+
... ... ... ... ... ...
23+
a495 49500 49501 ... 49598 49599
24+
a496 49600 49601 ... 49698 49699
25+
a497 49700 49701 ... 49798 49799
26+
a498 49800 49801 ... 49898 49899
27+
a499 49900 49901 ... 49998 49999"""
28+
assert str(arr) == expected
29+
larray.set_options(**original_ops)

larray/util/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from __future__ import absolute_import, division, print_function

larray/util/misc.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,15 @@ def prod(values):
8181
return reduce(operator.mul, values, 1)
8282

8383

84-
def format_value(value, missing, fullinfo=False):
85-
if isinstance(value, float) and not fullinfo:
84+
def format_value(value, missing, precision=None):
85+
if isinstance(value, float):
8686
# nans print as "-1.#J", let's use something nicer
8787
if value != value:
8888
return missing
89+
elif precision is not None:
90+
return '{:.{precision}f}'.format(value, precision=precision)
8991
else:
90-
return '%2.f' % value
92+
return str(value)
9193
elif isinstance(value, np.ndarray) and value.shape:
9294
# prevent numpy's default wrapping
9395
return str(list(value)).replace(',', '')
@@ -131,8 +133,8 @@ def get_min_width(table, index):
131133
return max(longest_word(row[index]) for row in table)
132134

133135

134-
def table2str(table, missing, fullinfo=False, summarize=True, maxwidth=80, numedges='auto', sep=' ', cont='...',
135-
keepcols=0):
136+
def table2str(table, missing, summarize=True, maxwidth=200, numedges='auto', sep=' ', cont='...',
137+
keepcols=0, precision=None):
136138
"""
137139
table is a list of lists
138140
:type table: list of list
@@ -144,7 +146,7 @@ def table2str(table, missing, fullinfo=False, summarize=True, maxwidth=80, numed
144146
for row in table:
145147
if len(row) < numcol:
146148
row.extend([''] * (numcol - len(row)))
147-
formatted = [[format_value(value, missing, fullinfo) for value in row]
149+
formatted = [[format_value(value, missing, precision) for value in row]
148150
for row in table]
149151
maxwidths = [get_col_width(formatted, i) for i in range(numcol)]
150152

larray/util/options.py

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
from __future__ import absolute_import, division, print_function
2+
3+
4+
DISPLAY_PRECISION = 'display_precision'
5+
DISPLAY_WIDTH = 'display_width'
6+
DISPLAY_MAXLINES = 'display_maxlines'
7+
DISPLAY_EDGEITEMS = 'display_edgeitems'
8+
9+
10+
_OPTIONS = {
11+
DISPLAY_PRECISION: None,
12+
DISPLAY_WIDTH: 80,
13+
DISPLAY_MAXLINES: 200,
14+
DISPLAY_EDGEITEMS: 5,
15+
}
16+
17+
18+
def _positive_integer(value):
19+
if not (isinstance(value, int) and value > 0):
20+
raise ValueError("Expected positive integer")
21+
22+
23+
def _integer_maxlines(value):
24+
if not isinstance(value, int) and value >= -1:
25+
raise ValueError("Expected integer")
26+
27+
28+
def _positive_integer_or_none(value):
29+
if value is None:
30+
return
31+
else:
32+
_positive_integer(value)
33+
34+
35+
_VALIDATORS = {
36+
DISPLAY_PRECISION: _positive_integer_or_none,
37+
DISPLAY_WIDTH: _positive_integer,
38+
DISPLAY_MAXLINES: _integer_maxlines,
39+
DISPLAY_EDGEITEMS: _positive_integer,
40+
}
41+
42+
43+
# idea taken from xarray. See xarray/core/options.py module for original implementation.
44+
class set_options(object):
45+
r"""Set options for larray in a controlled context.
46+
47+
Currently supported options:
48+
49+
- ``display_precision``: number of digits of precision for floating point output.
50+
Print as many digits as necessary to uniquely specify the value by default (None).
51+
- ``display_width``: maximum display width for ``repr`` on larray objects. Defaults to 80.
52+
- ``display_maxlines``: Maximum number of lines to show. All lines are shown if -1.
53+
Defaults to 200.
54+
- ``display_edgeitems`` : if number of lines to display is greater than ``display_maxlines``,
55+
only the first and last ``display_edgeitems`` lines are displayed.
56+
Only active if ``display_maxlines`` is not -1. Defaults to 5.
57+
58+
Examples
59+
--------
60+
>>> from larray import *
61+
>>> arr = ndtest((500, 100), dtype=float) + 0.123456
62+
63+
You can use ``set_options`` either as a context manager:
64+
65+
>>> with set_options(display_width=100, display_edgeitems=2):
66+
... print(arr)
67+
a\b b0 b1 b2 ... b97 b98 b99
68+
a0 0.123456 1.123456 2.123456 ... 97.123456 98.123456 99.123456
69+
a1 100.123456 101.123456 102.123456 ... 197.123456 198.123456 199.123456
70+
... ... ... ... ... ... ... ...
71+
a498 49800.123456 49801.123456 49802.123456 ... 49897.123456 49898.123456 49899.123456
72+
a499 49900.123456 49901.123456 49902.123456 ... 49997.123456 49998.123456 49999.123456
73+
74+
Or to set global options:
75+
76+
>>> set_options(display_maxlines=10, display_precision=2) # doctest: +SKIP
77+
>>> print(arr) # doctest: +SKIP
78+
a\b b0 b1 b2 ... b97 b98 b99
79+
a0 0.12 1.12 2.12 ... 97.12 98.12 99.12
80+
a1 100.12 101.12 102.12 ... 197.12 198.12 199.12
81+
a2 200.12 201.12 202.12 ... 297.12 298.12 299.12
82+
a3 300.12 301.12 302.12 ... 397.12 398.12 399.12
83+
a4 400.12 401.12 402.12 ... 497.12 498.12 499.12
84+
... ... ... ... ... ... ... ...
85+
a495 49500.12 49501.12 49502.12 ... 49597.12 49598.12 49599.12
86+
a496 49600.12 49601.12 49602.12 ... 49697.12 49698.12 49699.12
87+
a497 49700.12 49701.12 49702.12 ... 49797.12 49798.12 49799.12
88+
a498 49800.12 49801.12 49802.12 ... 49897.12 49898.12 49899.12
89+
a499 49900.12 49901.12 49902.12 ... 49997.12 49998.12 49999.12
90+
91+
To put back the default options, you can use:
92+
93+
>>> set_options(display_precision=None, display_width=80, display_maxlines=200, display_edgeitems=5)
94+
... # doctest: +SKIP
95+
"""
96+
97+
def __init__(self, **kwargs):
98+
self.old = {}
99+
for k, v in kwargs.items():
100+
if k not in _OPTIONS:
101+
raise ValueError('Argument {} is not in the set of valid options {}'.format(k, set(_OPTIONS)))
102+
if k in _VALIDATORS:
103+
_VALIDATORS[k](v)
104+
self.old[k] = _OPTIONS[k]
105+
_OPTIONS.update(kwargs)
106+
107+
def __enter__(self):
108+
return
109+
110+
def __exit__(self, type, value, traceback):
111+
_OPTIONS.update(self.old)
112+
113+
114+
def get_options():
115+
"""
116+
Return the current options.
117+
118+
Returns
119+
-------
120+
Dictionary of current print options with keys
121+
122+
- display_precision: int or None
123+
- display_width: int
124+
- display_maxlines: int
125+
- display_edgeitems : int
126+
127+
For a full description of these options, see :py:obj:`set_options`.
128+
129+
See Also
130+
--------
131+
set_options
132+
133+
Examples
134+
--------
135+
>>> get_options() # doctest: +SKIP
136+
{'display_precision': None, 'display_width': 80, 'display_maxlines': 200, 'display_edgeitems': 5}
137+
"""
138+
return _OPTIONS.copy()

0 commit comments

Comments
 (0)