astropy:docs

Source code for astropy.table.pprint

# Licensed under a 3-clause BSD style license - see LICENSE.rst
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
from ..extern import six
from ..extern.six import text_type
from ..extern.six.moves import zip as izip
from ..extern.six.moves import xrange

import os
import sys

import numpy as np

from .. import log
from ..utils.console import Getch, color_print, terminal_size, conf

if six.PY3:
    def default_format_func(format_, val):
        if isinstance(val, bytes):
            return val.decode('utf-8')
        else:
            return str(val)
    _format_funcs = {None: default_format_func}
elif six.PY2:
    _format_funcs = {None: lambda format_, val: text_type(val)}


def _auto_format_func(format_, val):
    """Format ``val`` according to ``format_`` for both old- and new-
    style format specifications or using a user supplied function.
    More importantly, determine and cache (in _format_funcs) a function
    that will do this subsequently.  In this way this complicated logic is
    only done for the first value.

    Returns the formatted value.
    """
    if six.callable(format_):
        format_func = lambda format_, val: format_(val.tolist())
        try:
            out = format_func(format_, val)
            if not isinstance(out, six.string_types):
                raise ValueError('Format function for value {0} returned {1} instead of string type'
                                 .format(val, type(val)))
        except Exception as err:
            raise ValueError('Format function for value {0} failed: {1}'
                             .format(val, err))
    else:
        try:
            # Convert val to Python object with tolist().  See
            # https://github.com/astropy/astropy/issues/148#issuecomment-3930809
            out = format_.format(val.tolist())
            # Require that the format statement actually did something
            if out == format_:
                raise ValueError
            format_func = lambda format_, val: format_.format(val.tolist())
        except:  # Not sure what exceptions might be raised
            try:
                out = format_ % val
                if out == format_:
                    raise ValueError
                format_func = lambda format_, val: format_ % val
            except:
                raise ValueError('Unable to parse format string {0}'
                                 .format(format_))
    _format_funcs[format_] = format_func
    return out


[docs]class TableFormatter(object): @staticmethod def _get_pprint_size(max_lines=None, max_width=None): """Get the output size (number of lines and character width) for Column and Table pformat/pprint methods. If no value of ``max_lines`` is supplied then the height of the screen terminal is used to set ``max_lines``. If the terminal height cannot be determined then the default will be determined using the ``astropy.table.conf.max_lines`` configuration item. If a negative value of ``max_lines`` is supplied then there is no line limit applied. The same applies for max_width except the configuration item is ``astropy.table.conf.max_width``. Parameters ---------- max_lines : int or None Maximum lines of output (header + data rows) max_width : int or None Maximum width (characters) output Returns ------- max_lines, max_width : int """ if max_lines is None: max_lines = conf.max_lines if max_width is None: max_width = conf.max_width if max_lines is None or max_width is None: lines, width = terminal_size() if max_lines is None: max_lines = lines elif max_lines < 0: max_lines = sys.maxsize if max_lines < 6: max_lines = 6 if max_width is None: max_width = width elif max_width < 0: max_width = sys.maxsize if max_width < 10: max_width = 10 return max_lines, max_width def _pformat_col(self, col, max_lines=None, show_name=True, show_unit=None): """Return a list of formatted string representation of column values. Parameters ---------- max_lines : int Maximum lines of output (header + data rows) show_name : bool Include column name (default=True) show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. Returns ------- lines : list List of lines with formatted column values n_header : int Number of lines in the header """ outs = {} # Some values from _pformat_col_iter iterator that are needed here col_strs = list(self._pformat_col_iter(col, max_lines, show_name, show_unit, outs)) col_width = max(len(x) for x in col_strs) # Center line content and generate dashed headerline for i in outs['i_centers']: col_strs[i] = col_strs[i].center(col_width) if outs['i_dashes'] is not None: col_strs[outs['i_dashes']] = '-' * col_width # Now bring all the column string values to the same fixed width for i, col_str in enumerate(col_strs): col_strs[i] = col_str.rjust(col_width) return col_strs, outs['n_header'] def _pformat_col_iter(self, col, max_lines, show_name, show_unit, outs): """Iterator which yields formatted string representation of column values. Parameters ---------- max_lines : int Maximum lines of output (header + data rows) show_name : bool Include column name (default=True) show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. out : dict Must be a dict which is used to pass back additional values defined within the iterator. """ max_lines, _ = self._get_pprint_size(max_lines, -1) multidims = col.shape[1:] if multidims: multidim0 = tuple(0 for n in multidims) multidim1 = tuple(n - 1 for n in multidims) trivial_multidims = np.prod(multidims) == 1 i_dashes = None i_centers = [] # Line indexes where content should be centered n_header = 0 if show_name: i_centers.append(n_header) # Get column name (or 'None' if not set) col_name = six.text_type(col.name) if multidims: col_name += ' [{0}]'.format( ','.join(six.text_type(n) for n in multidims)) n_header += 1 yield col_name if show_unit: i_centers.append(n_header) n_header += 1 yield six.text_type(col.unit or '') if show_unit or show_name: i_dashes = n_header n_header += 1 yield '---' max_lines -= n_header n_print2 = max_lines // 2 n_rows = len(col) format_func = _format_funcs.get(col.format, _auto_format_func) if len(col) > max_lines: i0 = n_print2 i1 = n_rows - n_print2 - max_lines % 2 else: i0 = len(col) i1 = 0 # Add formatted values if within bounds allowed by max_lines for i in xrange(n_rows): if i < i0 or i > i1: if multidims: # Prevents colums like Column(data=[[(1,)],[(2,)]], name='a') # with shape (n,1,...,1) from being printed as if there was # more than one element in a row if trivial_multidims: col_str = format_func(col.format, col[(i,) + multidim0]) else: col_str = (format_func(col.format, col[(i,) + multidim0]) + ' .. ' + format_func(col.format, col[(i,) + multidim1])) else: col_str = format_func(col.format, col[i]) yield col_str elif i == i0: yield '...' outs['n_header'] = n_header outs['i_centers'] = i_centers outs['i_dashes'] = i_dashes def _pformat_table(self, table, max_lines=None, max_width=None, show_name=True, show_unit=None, html=False, tableid=None): """Return a list of lines for the formatted string representation of the table. Parameters ---------- max_lines : int or None Maximum number of rows to output max_width : int or None Maximum character width of output show_name : bool Include a header row for column names (default=True) show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. html : bool Format the output as an HTML table (default=False) tableid : str or None An ID tag for the table; only used if html is set. Default is "table{id}", where id is the unique integer id of the table object, id(table) Returns ------- out : str Formatted table as a single string n_header : int Number of lines in the header """ # "Print" all the values into temporary lists by column for subsequent # use and to determine the width max_lines, max_width = self._get_pprint_size(max_lines, max_width) cols = [] if show_unit is None: show_unit = any([col.unit for col in six.itervalues(table.columns)]) for col in six.itervalues(table.columns): lines, n_header = self._pformat_col(col, max_lines, show_name, show_unit) cols.append(lines) if not cols: return [] n_rows = len(cols[0]) outwidth = lambda cols: sum(len(c[0]) for c in cols) + len(cols) - 1 dots_col = ['...'] * n_rows middle = len(cols) // 2 while outwidth(cols) > max_width: if len(cols) == 1: break if len(cols) == 2: cols[1] = dots_col break if cols[middle] is dots_col: cols.pop(middle) middle = len(cols) // 2 cols[middle] = dots_col # Now "print" the (already-stringified) column values into a # row-oriented list. rows = [] if html: from ..utils.xml.writer import xml_escape if tableid is None: tableid = 'table{id}'.format(id=id(table)) rows.append('<table id="{tid}">'.format(tid=tableid)) for i in range(n_rows): # _pformat_col output has a header line '----' which is not needed here if i == n_header - 1: continue td = 'th' if i < n_header else 'td' vals = ('<{0}>{1}</{2}>'.format(td, xml_escape(col[i].strip()), td) for col in cols) row = ('<tr>' + ''.join(vals) + '</tr>') if i < n_header: row = ('<thead>' + row + '</thead>') rows.append(row) rows.append('</table>') else: for i in range(n_rows): row = ' '.join(col[i] for col in cols) rows.append(row) return rows, n_header def _more_tabcol(self, tabcol, max_lines=None, max_width=None, show_name=True, show_unit=None): """Interactive "more" of a table or column. Parameters ---------- max_lines : int or None Maximum number of rows to output max_width : int or None Maximum character width of output show_name : bool Include a header row for column names (default=True) show_unit : bool Include a header row for unit. Default is to show a row for units only if one or more columns has a defined value for the unit. """ allowed_keys = 'f br<>qhpn' # Count the header lines n_header = 0 if show_name: n_header += 1 if show_unit: n_header += 1 if show_name or show_unit: n_header += 1 # Set up kwargs for pformat call. Only Table gets max_width. kwargs = dict(max_lines=-1, show_name=show_name, show_unit=show_unit) if hasattr(tabcol, 'columns'): # tabcol is a table kwargs['max_width'] = max_width # If max_lines is None (=> query screen size) then increase by 2. # This is because get_pprint_size leaves 6 extra lines so that in # ipython you normally see the last input line. max_lines1, max_width = self._get_pprint_size(max_lines, max_width) if max_lines is None: max_lines1 += 2 delta_lines = max_lines1 - n_header # Set up a function to get a single character on any platform inkey = Getch() i0 = 0 # First table/column row to show showlines = True while True: i1 = i0 + delta_lines # Last table/col row to show if showlines: # Don't always show the table (e.g. after help) try: os.system('cls' if os.name == 'nt' else 'clear') except: pass # No worries if clear screen call fails lines = tabcol[i0:i1].pformat(**kwargs) colors = ('red' if i < n_header else 'default' for i in xrange(len(lines))) for color, line in izip(colors, lines): color_print(line, color) showlines = True print() print("-- f, <space>, b, r, p, n, <, >, q h (help) --", end=' ') # Get a valid key while True: try: key = inkey().lower() except: print("\n") log.error('Console does not support getting a character' ' as required by more(). Use pprint() instead.') return if key in allowed_keys: break print(key) if key.lower() == 'q': break elif key == ' ' or key == 'f': i0 += delta_lines elif key == 'b': i0 = i0 - delta_lines elif key == 'r': pass elif key == '<': i0 = 0 elif key == '>': i0 = len(tabcol) elif key == 'p': i0 -= 1 elif key == 'n': i0 += 1 elif key == 'h': showlines = False print(""" Browsing keys: f, <space> : forward one page b : back one page r : refresh same page n : next row p : previous row < : go to beginning > : go to end q : quit browsing h : print this help""", end=' ') if i0 < 0: i0 = 0 if i0 >= len(tabcol) - delta_lines: i0 = len(tabcol) - delta_lines print("\n")

Page Contents