From 777740f90b7c9d5f1692e0af195afc2f3fdf8b7f Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Tue, 19 Mar 2024 10:52:28 -0800 Subject: [PATCH] BUG: pretty print all Mappings, not just dicts This was discovered in https://github.com/ibis-project/ibis/pull/8693 --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/io/formats/printing.py | 10 +++++----- pandas/tests/io/formats/test_printing.py | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index cb211b0b72dce..f3fcdcdb79ed6 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -357,6 +357,7 @@ MultiIndex I/O ^^^ - Bug in :meth:`DataFrame.to_excel` when writing empty :class:`DataFrame` with :class:`MultiIndex` on both axes (:issue:`57696`) +- Now all ``Mapping`` s are pretty printed correctly. Before only literal ``dict`` s were. (:issue:`57915`) - - diff --git a/pandas/io/formats/printing.py b/pandas/io/formats/printing.py index 214d1d7079fdb..0bd4f2935f4d0 100644 --- a/pandas/io/formats/printing.py +++ b/pandas/io/formats/printing.py @@ -187,8 +187,8 @@ def pprint_thing( _nest_lvl : internal use only. pprint_thing() is mutually-recursive with pprint_sequence, this argument is used to keep track of the current nesting level, and limit it. - escape_chars : list or dict, optional - Characters to escape. If a dict is passed the values are the + escape_chars : list[str] or Mapping[str, str], optional + Characters to escape. If a Mapping is passed the values are the replacements default_escapes : bool, default False Whether the input escape characters replaces or adds to the defaults @@ -204,11 +204,11 @@ def as_escaped_string( thing: Any, escape_chars: EscapeChars | None = escape_chars ) -> str: translate = {"\t": r"\t", "\n": r"\n", "\r": r"\r"} - if isinstance(escape_chars, dict): + if isinstance(escape_chars, Mapping): if default_escapes: translate.update(escape_chars) else: - translate = escape_chars + translate = escape_chars # type: ignore[assignment] escape_chars = list(escape_chars.keys()) else: escape_chars = escape_chars or () @@ -220,7 +220,7 @@ def as_escaped_string( if hasattr(thing, "__next__"): return str(thing) - elif isinstance(thing, dict) and _nest_lvl < get_option( + elif isinstance(thing, Mapping) and _nest_lvl < get_option( "display.pprint_nest_depth" ): result = _pprint_dict( diff --git a/pandas/tests/io/formats/test_printing.py b/pandas/tests/io/formats/test_printing.py index acf2bc72c687d..1009dfec53218 100644 --- a/pandas/tests/io/formats/test_printing.py +++ b/pandas/tests/io/formats/test_printing.py @@ -1,5 +1,6 @@ # Note! This file is aimed specifically at pandas.io.formats.printing utility # functions, not the general printing of pandas objects. +from collections.abc import Mapping import string import pandas._config.config as cf @@ -16,6 +17,17 @@ def test_adjoin(): assert adjoined == expected +class MyMapping(Mapping): + def __getitem__(self, key): + return 4 + + def __iter__(self): + return iter(["a", "b"]) + + def __len__(self): + return 2 + + class TestPPrintThing: def test_repr_binary_type(self): letters = string.ascii_letters @@ -42,6 +54,12 @@ def test_repr_obeys_max_seq_limit(self): def test_repr_set(self): assert printing.pprint_thing({1}) == "{1}" + def test_repr_dict(self): + assert printing.pprint_thing({"a": 4, "b": 4}) == "{'a': 4, 'b': 4}" + + def test_repr_mapping(self): + assert printing.pprint_thing(MyMapping()) == "{'a': 4, 'b': 4}" + class TestFormatBase: def test_adjoin(self):