"""This module provides BeautifulTable class intended for printing Tabular data to terminals.
Example
-------
>>> from beautifultable import BeautifulTable
>>> table = BeautifulTable()
>>> table.column_headers = ['1st column', '2nd column']
>>> for i in range(5):
... table.append_row([i, i*i])
...
>>> print(table)
+------------+------------+
| 1st column | 2nd column |
+------------+------------+
| 0 | 0 |
+------------+------------+
| 1 | 1 |
+------------+------------+
| 2 | 4 |
+------------+------------+
| 3 | 9 |
+------------+------------+
| 4 | 16 |
+------------+------------+
"""
import itertools
import copy
import operator
import collections
from .utils import convert_to_numeric
from .rows import RowData, HeaderData
from .meta import AlignmentMetaData, PositiveIntegerMetaData
from .enums import WidthExceedPolicy, Alignment, SignMode
__all__ = ['BeautifulTable']
class Column:
def __init__(self, table, column):
self._column = column
self._table = table
def __getitem__(self, key):
if isinstance(key, int):
return next(itertools.islice(self._column, key, None))
elif isinstance(key, slice):
return itertools.islice(self._column, key.start, key.stop, key.step)
else:
raise TypeError("column indices must be integers or slices, not {}".format(type(key).__name__))
def __setitem__(self, key, value):
if isinstance(key, int):
self._column[key] = value
else:
raise TypeError("column indices must be integers or slices, not {}".format(type(key).__name__))
[docs]class BeautifulTable:
"""Utility Class to print data in tabular format to terminal.
The instance attributes can be used to customize the look of the
table. To disable a behaviour, just set its corresponding attribute
to an empty string. For example, if Top border should not be drawn,
set `top_border_char` to ''.
Parameters
----------
max_width: int, optional
maximum width of the table in number of characters.
default_alignment : int, optional
Default alignment for new columns.
default_padding : int, optional
Default width of the left and right padding for new columns.
Attributes
----------
sign_mode
width_exceed_policy
default_alignment
default_padding
column_widths
column_headers
column_alignments
left_padding_widths
right_padding_widths
left_border_char : str
Character used to draw the left border.
right_border_char : str
Character used to draw the right border.
top_border_char : str
Character used to draw the top border.
bottom_border_char : str
Character used to draw the bottom border.
header_seperator_char : str
Character used to draw the line seperating Header from data.
row_seperator_char : str
Character used to draw the line seperating two rows.
column_seperator_char : str
Character used to draw the line seperating two columns.
intersection_char : str
Character used to draw intersection of a vertical and horizontal
line. Disabling it just draws the horizontal line char in it's place.
numeric_precision : int
All float values will have maximum number of digits after the decimal,
capped by this value(Default 3).
"""
WEP_WRAP = WidthExceedPolicy.WEP_WRAP
WEP_STRIP = WidthExceedPolicy.WEP_STRIP
WEP_ELLIPSIS = WidthExceedPolicy.WEP_ELLIPSIS
SM_PLUS = SignMode.SM_PLUS
SM_MINUS = SignMode.SM_MINUS
SM_SPACE = SignMode.SM_SPACE
ALIGN_LEFT = Alignment.ALIGN_LEFT
ALIGN_CENTER = Alignment.ALIGN_CENTER
ALIGN_RIGHT = Alignment.ALIGN_RIGHT
def __init__(self, max_width=80, default_alignment=ALIGN_CENTER,
default_padding=1):
self.left_border_char = '|'
self.right_border_char = '|'
self.top_border_char = '-'
self.bottom_border_char = '-'
self.header_seperator_char = '-'
self.column_seperator_char = '|'
self.row_seperator_char = '-'
self.intersection_char = '+'
self.numeric_precision = 3
self._sign_mode = BeautifulTable.SM_MINUS
self._width_exceed_policy = BeautifulTable.WEP_WRAP
self._default_alignment = default_alignment
self._default_padding = default_padding
self._column_pad = " "
self._max_table_width = max_width
self._initialize_table(0)
self._table = []
def __setattr__(self, name, value):
if name in ('left_border_char', 'right_border_char', 'top_border_char',
'bottom_border_char', 'header_seperator_char',
'column_seperator_char', 'row_seperator_char',
'intersection_char') and not isinstance(value, str):
raise TypeError("Expected {attr} to be of type 'str', got {attr_type}".format(attr=name, attr_type=type(value).__name__))
super().__setattr__(name, value)
# ****************************Properties Begin Here*****************************
@property
def column_count(self):
"""Get the number of columns in the table(read only)"""
return self._column_count
@property
def sign_mode(self):
"""Attribute to control how signs are displayed for numerical data.
It can be one of the following:
======================== ==============================================
Option Meaning
======================== ==============================================
BeautifulTable.SM_PLUS A sign should be used for both +ve and -ve
numbers.
BeautifulTable.SM_MINUS A sign should only be used for -ve numbers.
BeautifulTable.SM_SPACE A leading space should be used for +ve
numbers and a minus sign for -ve numbers.
======================== ==============================================
"""
return self._sign_mode
@sign_mode.setter
def sign_mode(self, value):
if not isinstance(value, SignMode):
error_msg = ("allowed values for sign_mode are: "
+ ', '.join("{}.{}".format(type(self).__name__, i.name) for i in SignMode))
raise ValueError(error_msg)
self._sign_mode = value
@property
def width_exceed_policy(self):
"""Attribute to control the behaviour of table when items exceed the column width.
It can be one of the following:
============================ ==========================================
Option Meaning
============================ ==========================================
BeautifulTable.WEP_WRAP An item is wrapped so every line fits
within it's column width.
BeautifulTable.WEP_STRIP An item is stripped to fit in it's
column.
BeautifulTable.WEP_ELLIPSIS An item is stripped to fit in it's
column and appended with ...(Ellipsis).
============================ ==========================================
"""
return self._width_exceed_policy
@width_exceed_policy.setter
def width_exceed_policy(self, value):
if not isinstance(value, WidthExceedPolicy):
error_msg = ("allowed values for width_exceed_policy are: "
+ ', '.join("{}.{}".format(type(self).__name__, i.name) for i in WidthExceedPolicy))
raise ValueError(error_msg)
self._width_exceed_policy = value
@property
def default_alignment(self):
"""Attribute to control the alignment of newly created columns.
It can be one of the following:
============================ ==========================================
Option Meaning
============================ ==========================================
BeautifulTable.ALIGN_LEFT New columns are left aligned.
BeautifulTable.ALIGN_CENTER New columns are center aligned.
BeautifulTable.ALIGN_RIGHT New columns are right aligned.
============================ ==========================================
"""
return self._default_alignment
@default_alignment.setter
def default_alignment(self, value):
if not isinstance(value, Alignment):
error_msg = ("allowed values for default_alignment are: "
+ ', '.join("{}.{}".format(type(self).__name__, i.name) for i in Alignment))
raise ValueError(error_msg)
self._default_alignment = value
@property
def default_padding(self):
"""Initial value for Left and Right padding widths for new columns."""
return self._default_padding
@default_padding.setter
def default_padding(self, value):
if not isinstance(value, int):
raise TypeError("padding must be an integer")
elif value <= 0:
raise ValueError("padding must be more than 0")
else:
self._default_padding = value
@property
def column_widths(self):
"""get/set width for the columns of the table.
Width of the column specifies the max number of characters
a column can contain. Larger characters are handled according to
the value of `width_exceed_policy`.
"""
return self._column_widths
@column_widths.setter
def column_widths(self, value):
width = self._validate_row(value)
self._column_widths = PositiveIntegerMetaData(self, width)
@property
def column_headers(self):
"""get/set titles for the columns of the table.
It can be any iterable having all memebers an instance of `str`.
"""
return self._column_headers
@column_headers.setter
def column_headers(self, value):
header = self._validate_row(value)
for i in header:
if not isinstance(i, str):
raise TypeError("Headers should be of type 'str'")
self._column_headers = HeaderData(self, header)
@property
def column_alignments(self):
"""get/set alignment of the columns of the table.
It can be any iterable containing only the following:
* BeautifulTable.ALIGN_LEFT
* BeautifulTable.ALIGN_CENTER
* BeautifulTable.ALIGN_RIGHT
"""
return self._column_alignments
@column_alignments.setter
def column_alignments(self, value):
alignment = self._validate_row(value)
self._column_alignments = AlignmentMetaData(self, alignment)
@property
def left_padding_widths(self):
"""get/set width for left padding of the columns of the table.
Left Width of the padding specifies the number of characters
on the left of a column reserved for padding. By Default It is 1.
"""
return self._left_padding_widths
@left_padding_widths.setter
def left_padding_widths(self, value):
pad_width = self._validate_row(value)
self._left_padding_widths = PositiveIntegerMetaData(self, pad_width)
@property
def right_padding_widths(self):
"""get/set width for right padding of the columns of the table.
Right Width of the padding specifies the number of characters
on the rigth of a column reserved for padding. By default It is 1.
"""
return self._right_padding_widths
@right_padding_widths.setter
def right_padding_widths(self, value):
pad_width = self._validate_row(value)
self._right_padding_widths = PositiveIntegerMetaData(self, pad_width)
# *****************************Properties End Here******************************
def _initialize_table(self, column_count: int):
"""Sets the column count of the table.
This method is called to set the number of columns for the first time.
It should only be called when `self._column_count` is 0.
Parameters
----------
column_count : int
number of columns in the table
"""
self._column_count = column_count
self._column_headers = HeaderData(self, [''] * column_count)
self._column_alignments = AlignmentMetaData(self, [self.default_alignment] * column_count)
self._column_widths = PositiveIntegerMetaData(self, [0] * column_count)
self._left_padding_widths = PositiveIntegerMetaData(self, [self.default_padding] * column_count)
self._right_padding_widths = PositiveIntegerMetaData(self, [self.default_padding] * column_count)
def _validate_row(self, value, init_table_if_required=True):
# TODO: Rename this method
# str is also an iterable but it is not a valid row, so
# an extra check is required for str
if not isinstance(value, collections.Iterable) or isinstance(value, str):
raise TypeError("parameter must be an iterable")
row = list(value)
if init_table_if_required and self._column_count == 0:
self._initialize_table(len(row))
if len(row) != self._column_count:
raise ValueError("'Expected iterable of length {}, got {}".format(self._column_count, len(row)))
return row
[docs] def auto_calculate_width(self):
"""Calculate width of column automatically based on data."""
table_width = self.get_table_width()
offset = table_width - sum(self._column_widths)
widths = [(self._left_padding_widths[index] + self._right_padding_widths[index]) for index in range(self._column_count)]
for index, column in enumerate(zip(*self._table)):
max_length = (max(len(str(convert_to_numeric(i, self.numeric_precision)))
for i in column))
max_length = max(max_length, len(str(self._column_headers[index])))
widths[index] += max_length
sum_ = sum(widths)
desired_sum = self._max_table_width - offset
temp_sum = 0
flag = [0] * len(widths)
for i, width in enumerate(widths):
if width < desired_sum / self._column_count:
temp_sum += width
flag[i] = 1
avail_space = desired_sum - temp_sum
actual_space = sum_ - temp_sum
for i, _ in enumerate(widths):
if not flag[i]:
widths[i] = round(widths[i] * avail_space / actual_space)
self.column_widths = widths
[docs] def set_padding_widths(self, pad_width):
"""Set width for left and rigth padding of the columns of the table.
Parameters
----------
pad_width : array_like
pad widths for the columns.
"""
self.left_padding_widths = pad_width
self.right_padding_widths = pad_width
def __getitem__(self, key):
"""Get a row, or a column, or a new table by slicing.
Parameters
----------
key : int, slice, str
If key is an `int`, returns a row.
If key is an `str`, returns iterator to a column with heading `key`.
If key is a slice object, returns a new table sliced according to rows.
Raises
------
TypeError
If key is not of type int, slice or str.
IndexError
If `int` key is out of range.
KeyError
If `str` key is not found in headers.
"""
if isinstance(key, slice):
new_table = copy.copy(self)
# All child of BaseRow class needs to be reassigned so that
# They contain reference of the new table rather than the old
# This was a cause of a nasty bug once.
new_table.column_headers = self.column_headers
new_table.column_alignments = self.column_alignments
new_table.column_widths = self.column_widths
new_table.left_padding_widths = self.left_padding_widths
new_table.right_padding_widths = self.left_padding_widths
new_table._table = []
for row in self._table[key]:
new_table.append_row(row)
return new_table
elif isinstance(key, int):
return self._table[key]
elif isinstance(key, str):
return self.get_column(key)
else:
raise TypeError("table indices must be integers or slices, not {}".format(type(key).__name__))
def __delitem__(self, key):
"""Delete a row, or a column, or multiple rows by slicing.
Parameters
----------
key : int, slice, str
If key is an `int`, deletes a row.
If key is a slice object, deletes multiple rows.
If key is an `str`, delete the first column with heading `key`
Raises
------
TypeError
If key is not of type int, slice or str.
IndexError
If `int` key is out of range.
KeyError
If `str` key is not found in headers.
"""
if isinstance(key, int) or isinstance(key, slice):
del self._table[key]
elif isinstance(key, str):
return self.pop_column(key)
else:
raise TypeError("table indices must be integers or slices, not {}".format(type(key).__name__))
def __setitem__(self, key, value):
"""Update a row, or a column, or multiple rows by slicing.
Parameters
----------
key : int, slice, str
If key is an `int`, updates a row.
If key is an `str`, appends `column` to the list with header as `key`.
If key is a slice object, updates multiple rows according to slice rules.
Raises
------
TypeError
If key is not of type int, slice or str.
IndexError
If `int` key is out of range.
"""
if isinstance(key, (int, slice)):
self.update_row(key, value)
elif isinstance(key, str):
self.update_column(key, value)
else:
raise TypeError("table indices must be integers or slices, not {}".format(type(key).__name__))
def __len__(self):
return len(self._table)
def __contains__(self, key):
if isinstance(key, str):
return key in self._column_headers
elif isinstance(key, collections.Iterable):
return key in self._table
else:
raise TypeError("'key' must be str or Iterable, not {}".format(type(key).__name__))
def __iter__(self):
return iter(self._table)
def __next__(self):
return next(self._table)
def __repr__(self):
return repr(self._table)
def __str__(self):
return self.get_string()
[docs] def sort(self, key):
"""Stable sort of the table *IN-PLACE* with respect to a column.
Parameters
----------
index:
index of the column. Normal list rules apply.
"""
if isinstance(key, int):
index = key
elif isinstance(key, str):
index = self.get_column_index(key)
else:
raise TypeError("'key' must either be 'int' or 'str'")
self._table.sort(key=operator.itemgetter(index))
[docs] def copy(self):
"""Return a shallow copy of the table.
Returns
-------
BeautifulTable:
shallow copy of the BeautifulTable instance.
"""
return self[:]
[docs] def get_column_header(self, index):
"""Get header of a column from it's index.
Parameters
----------
index: int
Normal list rules apply.
"""
return self._column_headers[index]
[docs] def get_column_index(self, header):
"""Get index of a column from it's header.
Parameters
----------
header: str
header of the column.
Raises
------
ValueError:
If no column could be found corresponding to `header`.
"""
try:
index = self._column_headers.index(header)
return index
except ValueError:
raise KeyError("'{}' is not a header for any column".format(header)) from None
[docs] def get_column(self, key):
"""Return an iterator to a column.
Parameters
----------
key : int, str
index of the column, or the header of the column.
If index is specified, then normal list rules apply.
Raises
------
TypeError:
If key is not of type `int`, or `str`.
Returns
-------
iter:
Iterator to the specified column.
"""
if isinstance(key, int):
index = key
elif isinstance(key, str):
index = self.get_column_index(key)
else:
raise TypeError("key must be an int or str, not {}".format(type(key).__name__))
return iter(map(operator.itemgetter(index), self._table))
[docs] def reverse(self):
"""Reverse the table row-wise *IN PLACE*."""
self._table.reverse()
[docs] def pop_row(self, index=-1):
"""Remove and return row at index (default last).
Parameters
----------
index : int
index of the row. Normal list rules apply.
"""
row = self._table.pop(index)
return row
[docs] def pop_column(self, index=-1):
"""Remove and return row at index (default last).
Parameters
----------
index : int, str
index of the column, or the header of the column.
If index is specified, then normal list rules apply.
Raises
------
TypeError:
If index is not an instance of `int`, or `str`.
IndexError:
If Table is empty.
"""
if isinstance(index, int):
pass
elif isinstance(index, str):
index = self.get_column_index(index)
else:
raise TypeError("column index must be an integer or a string, not {}".format(type(index).__name__))
if self._column_count == 0:
raise IndexError("pop from empty table")
if self._column_count == 1:
# This is the last column. So we should clear the table to avoid empty rows
self.clear(clear_column_properties=True)
else:
# Not the last column. safe to pop from row
self._column_count -= 1
self._column_alignments._pop(index)
self._column_widths._pop(index)
self._left_padding_widths._pop(index)
self._right_padding_widths._pop(index)
self._column_headers._pop(index)
for row in self._table:
row._pop(index)
[docs] def insert_row(self, index, row):
"""Insert a row before index in the table.
Parameters
----------
index : int
List index rules apply
row : iterable
Any iterable of appropriate length.
Raises
------
TypeError:
If `row` is not an iterable.
ValueError:
If size of `row` is inconsistent with the current number
of columns.
"""
row = self._validate_row(row)
row_obj = RowData(self, row)
self._table.insert(index, row_obj)
[docs] def append_row(self, row):
"""Append a row to end of the table.
Parameters
----------
row : iterable
Any iterable of appropriate length.
"""
self.insert_row(len(self._table), row)
[docs] def update_row(self, key, value):
"""Update a column named `header` in the table.
If length of column is smaller than number of rows, lets say
`k`, only the first `k` values in the column is updated.
Parameters
----------
key : int or slice
index of the row, or a slice object.
value : iterable
If an index is specified, `value` should be an iterable
of appropriate length. Instead if a slice object is
passed as key, value should be an iterable of rows.
Raises
------
IndexError:
If index specified is out of range.
TypeError:
If `value` is of incorrect type.
ValueError:
If length of row does not matches number of columns.
"""
if isinstance(key, int):
row = self._validate_row(value, init_table_if_required=False)
row_obj = RowData(self, row)
self._table[key] = row_obj
elif isinstance(key, slice):
row_obj_list = []
for row in value:
row_ = self._validate_row(row, init_table_if_required=True)
row_obj_list.append(RowData(self, row_))
self._table[key] = row_obj_list
else:
raise TypeError("key must be an integer or a slice object")
[docs] def update_column(self, header, column):
"""Update a column named `header` in the table.
If length of column is smaller than number of rows, lets say
`k`, only the first `k` values in the column is updated.
Parameters
----------
header : str
Header of the column
column : iterable
Any iterable of appropriate length.
Raises
------
TypeError:
If length of `column` is shorter than number of rows.
ValueError:
If no column exists with title `header`.
"""
index = self.get_column_index(header)
if not isinstance(header, str):
raise TypeError("header must be of type str")
for row, new_item in zip(self._table, column):
row[index] = new_item
[docs] def insert_column(self, index, header, column):
"""Insert a column before `index` in the table.
If length of column is bigger than number of rows, lets say
`k`, only the first `k` values of `column` is considered.
If column is shorter than 'k', ValueError is raised.
Note that Table remains in consistent state even if column
is too short. Any changes made by this method is rolled back
before raising the exception.
Parameters
----------
index : int
List index rules apply.
header : str
Title of the column.
column : iterable
Any iterable of appropriate length.
Raises
------
TypeError:
If `header` is not of type `str`.
ValueError:
If length of `column` is shorter than number of rows.
"""
if self._column_count == 0:
self.column_headers = HeaderData(self, [header])
self._table = [RowData(self, [i]) for i in column]
else:
if not isinstance(header, str):
raise TypeError("header must be of type str")
column_length = 0
for i, (row, new_item) in enumerate(zip(self._table, column)):
row._insert(index, new_item)
column_length = i
if column_length == len(self._table) - 1:
self._column_count += 1
self._column_headers._insert(index, header)
self._column_alignments._insert(index, self.default_alignment)
self._column_widths._insert(index, 0)
self._left_padding_widths._insert(index, self.default_padding)
self._right_padding_widths._insert(index, self.default_padding)
else:
# Roll back changes so that table remains in consistent state
for j in range(column_length, -1, -1):
self._table[j]._pop(index)
raise ValueError("length of 'column' should be atleast {}, got {}".format(len(self._table), column_length + 1))
[docs] def append_column(self, header, column):
"""Append a column to end of the table.
Parameters
----------
header : str
Title of the column
column : iterable
Any iterable of appropriate length.
"""
self.insert_column(self._column_count, header, column)
[docs] def clear(self, clear_column_properties=False):
"""Clear the contents of the table.
Clear all rows of the table, and if specified clears all column specific data.
Parameters
----------
clear_column_properties : bool, optional
If it is true(default False), all metadata of columns such as their alignment,
padding, width, etc. are also cleared and number of columns is set to 0.
"""
self._table.clear()
if clear_column_properties:
self._initialize_table(0)
def _get_horizontal_line(self, char):
"""Get a horizontal line for the table.
Internal method used to actually get all horizontal lines in the table.
Column width should be set prior to calling this method. This method
detects intersection and handles it according to the value of
`intersection_char`.
Parameters
----------
char : str
Character used to draw the line.
Returns
-------
str
String which will be printed as the Top border of the table.
"""
table_width = self.get_table_width()
intersection = self.intersection_char
try:
line = list(char * (int(table_width/len(char)) + 1))[:table_width]
except ZeroDivisionError:
line = [' '] * table_width
# Only if Special Intersection is enabled and horizontal line is visible
if self.intersection_char and not char.isspace():
# If left border is enabled and it is visible
if self.left_border_char and not self.left_border_char.isspace():
length = min(len(self.left_border_char), len(self.intersection_char))
for i in range(length):
line[i] = intersection[i]
# If right border is enabled and it is visible
if self.right_border_char and not self.right_border_char.isspace():
length = min(len(self.right_border_char), len(self.intersection_char))
for i in range(length):
line[-i-1] = intersection[-i-1]
# If column seperator is enabled and it is visible
if self.column_seperator_char and not self.column_seperator_char.isspace():
index = len(self.left_border_char)
for i in range(self._column_count-1):
index += (self._column_widths[i])
length = min(len(self.column_seperator_char), len(self.intersection_char))
for i in range(length):
line[index+i] = intersection[i]
index += len(self.column_seperator_char)
return ''.join(line)
[docs] def get_top_border(self):
"""Get the Top border of table.
Column width should be set prior to calling this method.
Returns
-------
str
String which will be printed as the Top border of the table.
"""
return self._get_horizontal_line(self.top_border_char)
[docs] def get_row_seperator(self):
"""Get the Row seperator of table.
Column width should be set prior to calling this method.
Returns
-------
str
String which will be printed as Row seperator of the table.
"""
return self._get_horizontal_line(self.row_seperator_char)
[docs] def get_bottom_border(self):
"""Get the Bottom border of table.
Column width should be set prior to calling this method.
Returns
-------
str
String which will be printed as the Bottom border of the table.
"""
return self._get_horizontal_line(self.bottom_border_char)
[docs] def get_table_width(self):
"""Get the width of the table as number of characters.
Column width should be set prior to calling this method.
Returns
-------
int
Width of the table as number of characters.
"""
width = sum(self._column_widths)
width += ((self._column_count - 1)
* len(self.column_seperator_char))
width += len(self.left_border_char)
width += len(self.right_border_char)
return width
[docs] def get_number_of_columns(self):
"""Get the current number of columns.
Returns
-------
int:
Current number of columns in the Table.
"""
return self._column_count
[docs] def get_string(self, recalculate_width=True):
"""Get the table as a String.
Parameters
----------
recalculate_width : bool, optional
If width for each column should be recalculated(default True).
Note that width is always calculated if it wasn't set
explicitly when this method is called for the first time ,
regardless of the value of `recalculate_width`.
Returns
-------
str:
Table as a string.
"""
if recalculate_width or sum(self._column_widths) == 0:
self.auto_calculate_width()
string_ = []
if self.top_border_char:
string_.append(
self.get_top_border())
headers = str(self._column_headers)
string_.append(headers)
if self.header_seperator_char:
string_.append(
self.get_header_seperator())
first_row_encountered = False
for row in self._table:
if first_row_encountered and self.row_seperator_char:
string_.append(
self.get_row_seperator())
first_row_encountered = True
content = str(row)
string_.append(content)
if self.bottom_border_char:
string_.append(
self.get_bottom_border())
return '\n'.join(string_)
class _BufferedPrinter:
"""Utility Class to print to terminal with a fixed buffer size.
Parameters
----------
max_buffer_size
maximum size of the internal buffer after which buffer is
emptied by flushing it's content. Larger buffer size usually leads
to efficient printing.
"""
def __init__(self, max_buffer_size: int):
self._max_buffer_size = max_buffer_size
self._buffer = []
def set_max_buffer_size(self, max_buffer_size: int):
"""Set max buffer size.
Parameters
----------
max_buffer_size
new size for the internal buffer.
"""
self._max_buffer_size = max_buffer_size
def flush(self):
"""flush the buffer to a stream, or to sys.stdout by default.
Note
----
It is recommended to always call this method before exiting otherwise
you could lose important information.
"""
print(''.join(self._buffer), end='', flush=True)
self._buffer = []
def print(self, *args, sep=' ', end='\n'):
"""Prints the values to the internal buffer.
Parameters
----------
*args
values to be printed
sep : str, optional
string inserted between values, default a space.
end : str, optional
string appended after the last value, default a newline.
"""
self._buffer.append(sep.join(args) + end)
if len(self._buffer) == self._max_buffer_size:
self.flush()