-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-90117: handle dict and mapping views in pprint #30135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…d ._pprint_dict_items_view.
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept this contribution by verifying everyone involved has signed the PSF contributor agreement (CLA). CLA MissingOur records indicate the following people have not signed the CLA: For legal reasons we need all the people listed to sign the CLA before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue. If you have recently signed the CLA, please wait at least one business day You can check yourself to see if the CLA has been received. Thanks again for the contribution, we look forward to reviewing it! |
Thank you for the review, Alex! |
Pleasure :) You can add a News entry to your PR using https://blurb-it.herokuapp.com |
…int_dict_items_view.
Thank you for reviewing, Éric! |
This PR is stale because it has been open for 30 days with no activity. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also the collections.abc.KeysView
, ValuesView
and ItemsView
classes, all of which should be able to reuse the logic used here for the various dict views. For the sake of consistency, I think it would be worthwhile to include them as well.
The sake of consistency is not a sufficient reason to do things. The question should be: what is the feature added by handling dict views ABCs in pprint? Do people pretty-print these ABCs? Would registering them automatically handle instance of concrete subclasses of the ABCs? In that case, should that replace the code added in this PR? |
Data point of 1, but I've personally run into this exact issue before, both with normal dict views as well as custom mapping views derived from the
Registering the The only caveat is that because the mapping views share identical @@ -233,8 +233,11 @@ def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level
- def _pprint_dict_view(self, object, stream, indent, allowance, context, level, items=False):
- key = _safe_tuple if items else _safe_key
+ def _pprint_dict_view(self, object, stream, indent, allowance, context, level):
+ if isinstance(object, (self._dict_items_view, _collections.abc.ItemsView)):
+ key = _safe_tuple
+ else:
+ key = _safe_key
@@ -255,11 +258,10 @@ def _pprint_dict_view(self, object, stream, indent, allowance, context, level, i
- def _pprint_dict_items_view(self, object, stream, indent, allowance, context, level):
- self._pprint_dict_view(object, stream, indent, allowance, context, level, items=True)
-
_dict_items_view = type({}.items())
- _dispatch[_dict_items_view.__repr__] = _pprint_dict_items_view
+ _dispatch[_dict_items_view.__repr__] = _pprint_dict_view
+
+ _dispatch[_collections.abc.MappingView.__repr__] = _pprint_dict_view |
…nto pprint_dict_view
…nto pprint_dict_view
I believe this PR is now ready for review. Any feedback is most welcome! It tries to keep the changes self-contained and use the same code style as the rest of the module, matching the code for pretty printing of dicts as closely as possible. It also adds and adapts many tests, to ensure full coverage of the changes. I now believe supporting ABC mapping views is worth it: it adds little code and broadens the kinds of mapping for which we can pretty print views. Some choices, like calling Code and output for pretty printing many kinds of views
import pprint
from collections import OrderedDict, defaultdict, Counter, ChainMap
from collections.abc import Mapping, MappingView, ItemsView, KeysView, ValuesView
ABC_MAPPING_VIEWS = MappingView, ItemsView, KeysView, ValuesView
DICT_CLASSES = OrderedDict, Counter, ChainMap
def main():
# Simple dictionary with string keys and values
simple_dict = {"apple": "fruit", "carrot": "vegetable", "pear": "fruit"}
# Dictionary with various types of keys and values
mixed_dict = {42: "answer", (2, 3): [1, 2, 3], 'key': {'nested': 'dict'}}
# Long dictionary with integer keys and string values
long_dict = {i: f"number_{i}" for i in range(20)}
# Long recursive dictionary
recursive_dict = long_dict.copy()
recursive_dict[20] = recursive_dict
# Dictionary containing views
views_dict = {i: cls({i: i}) for i, cls in enumerate(ABC_MAPPING_VIEWS)}
start = len(ABC_MAPPING_VIEWS)
views_dict.update({i + start: cls({i: i}).items() for i, cls in enumerate(DICT_CLASSES)})
int_default_dict = defaultdict(int)
int_default_dict[0] = 0
views_dict.update({7: int_default_dict.items()})
# Ordered dictionary to maintain insertion order
ordered_dict = OrderedDict([(f"key_{i}", i) for i in range(20)])
# Default dictionary with a default factory of list
default_dict = defaultdict(list)
default_dict['fruits'].append('apple')
default_dict['fruits'].append('banana')
default_dict['vegetables'].append('carrot')
# Counter dictionary to count occurrences
counter_dict = Counter('abracadabra')
# ChainMap to search multiple dictionaries
chain_map = ChainMap(simple_dict, mixed_dict, long_dict)
# Nested dictionary views
nested_dict = {'level1': simple_dict, 'level2': {'level2_dict': mixed_dict}}
# Printing different dictionaries with pprint
print("Simple Dict Keys:")
pprint.pprint(simple_dict.keys())
print("\nMixed Dict Items:")
pprint.pprint(mixed_dict.items())
print("\nLong Dict Values:")
pprint.pprint(long_dict.values(), width=50)
print("\nOrdered Dict Items:")
pprint.pprint(ordered_dict.items())
print("\nDefault Dict Items:")
pprint.pprint(default_dict.items())
print("\nCounter Dict Items:")
pprint.pprint(counter_dict.items())
print("\nChainMap Items:")
pprint.pprint(chain_map.items())
print("\nNested Dict Views:")
pprint.pprint(nested_dict.keys())
pprint.pprint(nested_dict.items())
pprint.pprint(nested_dict.items(), depth=2)
print("\nRecursive Dict Views:")
pprint.pprint(recursive_dict.items())
print("\nABC Mapping Views (should pretty print a repr of the mapping):")
pprint.pprint(MappingView(recursive_dict))
pprint.pprint(ItemsView(nested_dict))
pprint.pprint(KeysView(chain_map))
pprint.pprint(ValuesView(ordered_dict))
print("\nDict With Views:")
pprint.pprint(views_dict.items())
classes()
def classes():
class Mappingview_Custom_Repr(MappingView):
def __repr__(self):
return '*' * len(
67E6
MappingView.__repr__(self))
print("\nCustom __repr__ in Subclass:")
pprint.pprint(Mappingview_Custom_Repr({1: 1}))
class MyMapping(Mapping):
def __init__(self, keys=None):
self._keys = {} if keys is None else dict.fromkeys(keys)
def __getitem__(self, item):
return self._keys[item]
def __len__(self):
return len(self._keys)
def __iter__(self):
return iter(self._keys)
def __repr__(self):
return f"{self.__class__.__name__}([{', '.join(map(repr, self._keys.keys()))}])"
print("\nCustom __repr__ in _mapping:")
my_mapping = MyMapping(["test", 1])
pprint.pprint(MappingView(my_mapping))
pprint.pprint(my_mapping.items())
if __name__ == "__main__":
main() Output:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirm that the changes perform as indicated. This all works well.
I think the only oddity is the recursive printing, but I don't think that should be a block. Instead, if someone has a great idea for how to better handle recursively printing blocks (or, for example, asking if this is useful enough to warrant more attention), then they can do that later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Hello from PyCon's CPython sprint!]
I looked it over and did some manual testing and is working correctly. Good work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed (pairing) - this is in a good state and is an improvement over existing pprint behavior.
A small thing: github unhelpfully defaults merge commit message to the concatenation of all commits in the branch. One needs to delete it before merging. The browser extension Refined Github helps with this. |
This PR adds two methods to PrettyPrinter: one to handle
dict_keys
anddict_values
(sorted with_safe_key
), another to handledict_items
(sorted using_safe_tuple
).Design and implementation open for discussion, thanks in advance.
https://bugs.python.org/issue45959