Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
gpshead merged 45 commits intopython:mainfromdevdanzin:pprint_dict_view
May 20, 2025

Conversation

devdanzin
Copy link
Contributor

@devdanzindevdanzin commentedDec 16, 2021
edited by bedevere-bot
Loading

This PR adds two methods to PrettyPrinter: one to handledict_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

roguh reacted with hooray emoji
@the-knights-who-say-ni

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 thePSF contributor agreement (CLA).

CLA Missing

Our records indicate the following people have not signed the CLA:

@devdanzin

For legal reasons we need all the people listed to sign the CLA before we can look at your contribution. Please followthe steps outlined in the CPython devguide to rectify this issue.

If you have recently signed the CLA, please wait at least one business day
before our records are updated.

You cancheck yourself to see if the CLA has been received.

Thanks again for the contribution, we look forward to reviewing it!

@AlexWaygoodAlexWaygood added the type-featureA feature request or enhancement labelDec 17, 2021
@devdanzin
Copy link
ContributorAuthor

Thank you for the review, Alex!

AlexWaygood reacted with thumbs up emoji

@AlexWaygood
Copy link
Member

Thank you for the review, Alex!

Pleasure :) You can add a News entry to your PR usinghttps://blurb-it.herokuapp.com

@devdanzin
Copy link
ContributorAuthor

Thank you for reviewing, Éric!

@github-actions
Copy link

This PR is stale because it has been open for 30 days with no activity.

@github-actionsgithub-actionsbot added the staleStale PR or inactive for long period of time. labelJan 18, 2022
Copy link
Contributor

@BvB93BvB93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

There is also thecollections.abc.KeysView,ValuesView andItemsView 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.

@merwok
Copy link
Member

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?

AlexWaygood and BvB93 reacted with thumbs up emoji

@BvB93
Copy link
Contributor

The question should be: what is the feature added by handling dict views ABCs in pprint? Do people pretty-print these ABCs?

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 thecollections.abc 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?

Registering thecollections.abc.MappingView baseclass (which is the one that actually implements__repr__ for the mapping views) should do most of the work.

The only caveat is that because the mapping views share identical__repr__ methods by default it's not possible to distinguish between,e.g.,ItemsView andKeysView simply by using the__repr__ method as a dictionary key. Nevertheless, some minor changes in the logic of_pprint_dict_view should be able to deal with this.

@@ -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

@github-actionsgithub-actionsbot removed the staleStale PR or inactive for long period of time. labelJan 27, 2022
@devdanzin
Copy link
ContributorAuthor

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 callingrepr() only to raise anAttributeError instead of raising it ourselves, I'm not so sure about.

Code and output for pretty printing many kinds of views

importpprintfromcollectionsimportOrderedDict,defaultdict,Counter,ChainMapfromcollections.abcimportMapping,MappingView,ItemsView,KeysView,ValuesViewABC_MAPPING_VIEWS=MappingView,ItemsView,KeysView,ValuesViewDICT_CLASSES=OrderedDict,Counter,ChainMapdefmain():# Simple dictionary with string keys and valuessimple_dict= {"apple":"fruit","carrot":"vegetable","pear":"fruit"}# Dictionary with various types of keys and valuesmixed_dict= {42:"answer", (2,3): [1,2,3],'key': {'nested':'dict'}}# Long dictionary with integer keys and string valueslong_dict= {i:f"number_{i}"foriinrange(20)}# Long recursive dictionaryrecursive_dict=long_dict.copy()recursive_dict[20]=recursive_dict# Dictionary containing viewsviews_dict= {i:cls({i:i})fori,clsinenumerate(ABC_MAPPING_VIEWS)}start=len(ABC_MAPPING_VIEWS)views_dict.update({i+start:cls({i:i}).items()fori,clsinenumerate(DICT_CLASSES)})int_default_dict=defaultdict(int)int_default_dict[0]=0views_dict.update({7:int_default_dict.items()})# Ordered dictionary to maintain insertion orderordered_dict=OrderedDict([(f"key_{i}",i)foriinrange(20)])# Default dictionary with a default factory of listdefault_dict=defaultdict(list)default_dict['fruits'].append('apple')default_dict['fruits'].append('banana')default_dict['vegetables'].append('carrot')# Counter dictionary to count occurrencescounter_dict=Counter('abracadabra')# ChainMap to search multiple dictionarieschain_map=ChainMap(simple_dict,mixed_dict,long_dict)# Nested dictionary viewsnested_dict= {'level1':simple_dict,'level2': {'level2_dict':mixed_dict}}# Printing different dictionaries with pprintprint("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()defclasses():classMappingview_Custom_Repr(MappingView):def__repr__(self):return'*'*len(MappingView.__repr__(self))print("\nCustom __repr__ in Subclass:")pprint.pprint(Mappingview_Custom_Repr({1:1}))classMyMapping(Mapping):def__init__(self,keys=None):self._keys= {}ifkeysisNoneelsedict.fromkeys(keys)def__getitem__(self,item):returnself._keys[item]def__len__(self):returnlen(self._keys)def__iter__(self):returniter(self._keys)def__repr__(self):returnf"{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:

Simple Dict Keys:dict_keys(['apple', 'carrot', 'pear'])Mixed Dict Items:dict_items([(42, 'answer'), ('key', {'nested': 'dict'}), ((2, 3), [1, 2, 3])])Long Dict Values:dict_values(['number_0', 'number_1', 'number_10', 'number_11', 'number_12', 'number_13', 'number_14', 'number_15', 'number_16', 'number_17', 'number_18', 'number_19', 'number_2', 'number_3', 'number_4', 'number_5', 'number_6', 'number_7', 'number_8', 'number_9'])Ordered Dict Items:odict_items([('key_0', 0), ('key_1', 1), ('key_10', 10), ('key_11', 11), ('key_12', 12), ('key_13', 13), ('key_14', 14), ('key_15', 15), ('key_16', 16), ('key_17', 17), ('key_18', 18), ('key_19', 19), ('key_2', 2), ('key_3', 3), ('key_4', 4), ('key_5', 5), ('key_6', 6), ('key_7', 7), ('key_8', 8), ('key_9', 9)])Default Dict Items:dict_items([('fruits', ['apple', 'banana']), ('vegetables', ['carrot'])])Counter Dict Items:dict_items([('a', 5), ('b', 2), ('c', 1), ('d', 1), ('r', 2)])ChainMap Items:ItemsView(ChainMap({'apple': 'fruit', 'carrot': 'vegetable', 'pear': 'fruit'},         {42: 'answer', 'key': {'nested': 'dict'}, (2, 3): [1, 2, 3]},         {0: 'number_0',          1: 'number_1',          2: 'number_2',          3: 'number_3',          4: 'number_4',          5: 'number_5',          6: 'number_6',          7: 'number_7',          8: 'number_8',          9: 'number_9',          10: 'number_10',          11: 'number_11',          12: 'number_12',          13: 'number_13',          14: 'number_14',          15: 'number_15',          16: 'number_16',          17: 'number_17',          18: 'number_18',          19: 'number_19'}))Nested Dict Views:dict_keys(['level1', 'level2'])dict_items([('level1', {'apple': 'fruit', 'carrot': 'vegetable', 'pear': 'fruit'}), ('level2',  {'level2_dict': {42: 'answer',                   'key': {'nested': 'dict'},                   (2, 3): [1, 2, 3]}})])dict_items([('level1', {...}), ('level2', {...})])Recursive Dict Views:dict_items([(0, 'number_0'), (1, 'number_1'), (2, 'number_2'), (3, 'number_3'), (4, 'number_4'), (5, 'number_5'), (6, 'number_6'), (7, 'number_7'), (8, 'number_8'), (9, 'number_9'), (10, 'number_10'), (11, 'number_11'), (12, 'number_12'), (13, 'number_13'), (14, 'number_14'), (15, 'number_15'), (16, 'number_16'), (17, 'number_17'), (18, 'number_18'), (19, 'number_19'), (20,  {0: 'number_0',   1: 'number_1',   2: 'number_2',   3: 'number_3',   4: 'number_4',   5: 'number_5',   6: 'number_6',   7: 'number_7',   8: 'number_8',   9: 'number_9',   10: 'number_10',   11: 'number_11',   12: 'number_12',   13: 'number_13',   14: 'number_14',   15: 'number_15',   16: 'number_16',   17: 'number_17',   18: 'number_18',   19: 'number_19',   20: <Recursion on dict with id=2200369163696>})])ABC Mapping Views (should pretty print a repr of the mapping):MappingView({0: 'number_0', 1: 'number_1', 2: 'number_2', 3: 'number_3', 4: 'number_4', 5: 'number_5', 6: 'number_6', 7: 'number_7', 8: 'number_8', 9: 'number_9', 10: 'number_10', 11: 'number_11', 12: 'number_12', 13: 'number_13', 14: 'number_14', 15: 'number_15', 16: 'number_16', 17: 'number_17', 18: 'number_18', 19: 'number_19', 20: <Recursion on dict with id=2200369163696>})ItemsView({'level1': {'apple': 'fruit', 'carrot': 'vegetable', 'pear': 'fruit'}, 'level2': {'level2_dict': {42: 'answer',                            'key': {'nested': 'dict'},                            (2, 3): [1, 2, 3]}}})KeysView(ChainMap({'apple': 'fruit', 'carrot': 'vegetable', 'pear': 'fruit'},         {42: 'answer', 'key': {'nested': 'dict'}, (2, 3): [1, 2, 3]},         {0: 'number_0',          1: 'number_1',          2: 'number_2',          3: 'number_3',          4: 'number_4',          5: 'number_5',          6: 'number_6',          7: 'number_7',          8: 'number_8',          9: 'number_9',          10: 'number_10',          11: 'number_11',          12: 'number_12',          13: 'number_13',          14: 'number_14',          15: 'number_15',          16: 'number_16',          17: 'number_17',          18: 'number_18',          19: 'number_19'}))ValuesView(OrderedDict([('key_0', 0),             ('key_1', 1),             ('key_2', 2),             ('key_3', 3),             ('key_4', 4),             ('key_5', 5),             ('key_6', 6),             ('key_7', 7),             ('key_8', 8),             ('key_9', 9),             ('key_10', 10),             ('key_11', 11),             ('key_12', 12),             ('key_13', 13),             ('key_14', 14),             ('key_15', 15),             ('key_16', 16),             ('key_17', 17),             ('key_18', 18),             ('key_19', 19)]))Dict With Views:dict_items([(0, MappingView({0: 0})), (1, ItemsView({1: 1})), (2, KeysView({2: 2})), (3, ValuesView({3: 3})), (4, odict_items([(0, 0)])), (5, dict_items([(1, 1)])), (6, ItemsView(ChainMap({2: 2}))), (7, dict_items([(0, 0)]))])Custom __repr__ in Subclass:*******************************Custom __repr__ in _mapping:MappingView(MyMapping(['test', 1]))ItemsView(MyMapping(['test', 1]))

@devdanzindevdanzin marked this pull request as ready for reviewApril 26, 2024 11:59
Copy link
Contributor

@davidlowrydudadavidlowryduda left a 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.

@python-cla-bot
Copy link

python-cla-botbot commentedApr 18, 2025
edited
Loading

All commit authors signed the Contributor License Agreement.

CLA signed

Copy link
Contributor

@AlexKautzAlexKautz left a 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!

@gpsheadgpshead self-assigned thisMay 20, 2025
@gpsheadgpshead added 3.15new features, bugs and security fixes and removed 3.13bugs and security fixes labelsMay 20, 2025
Copy link
Member

@gpsheadgpshead left a 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.

@gpsheadgpshead merged commitc7f8e70 intopython:mainMay 20, 2025
44 of 47 checks passed
@merwok
Copy link
Member

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.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@merwokmerwokmerwok left review comments

@sobolevnsobolevnsobolevn left review comments

@BvB93BvB93BvB93 left review comments

@AlexWaygoodAlexWaygoodAlexWaygood left review comments

@gpsheadgpsheadgpshead approved these changes

@davidlowrydudadavidlowrydudadavidlowryduda approved these changes

@AlexKautzAlexKautzAlexKautz approved these changes

@furkanonderfurkanonderfurkanonder approved these changes

@rhettingerrhettingerAwaiting requested review from rhettinger

Assignees

@gpsheadgpshead

Labels
3.15new features, bugs and security fixesstdlibPython modules in the Lib dirtype-featureA feature request or enhancement
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

14 participants
@devdanzin@the-knights-who-say-ni@AlexWaygood@merwok@BvB93@arhadthedev@gpshead@davidlowryduda@sobolevn@AlexKautz@furkanonder@ezio-melotti@bedevere-bot@AA-Turner

[8]ページ先頭

©2009-2025 Movatter.jp