Can I avoid a sorted dictionary output after I've used pprint.pprint, in Python? [duplicate]
Asked Answered
P

5

15

The code is:

from pprint import pprint
d = {"b" : "Maria", "c" : "Helen", "a" : "George"}
pprint(d, width = 1)

The output is:

{'a': 'George',
'b': 'Maria',
'c': 'Helen'}

But, the desired output is:

{'b': 'Maria',
'c': 'Helen',
'a': 'George'}

Could this be done with pprint or is there another way?

Petulia answered 10/8, 2018 at 14:11 Comment(6)
OrderedDict or use a function to sort over keys and print them individually. But as another note, why do you care if a dictionary has an order?Preferment
docs.python.org/3/library/pprint.html says, "Dictionaries are sorted by key before the display is computed." Doesn't make it sound like it's optional.Arboreous
@EdChum, @Francisco, sure, OrderedDict will display the results in ordered order. But if you pass one to pprint, it won't be "pretty": it will look like OrderedDict([items go here]). Doesn't look much like the desired output.Arboreous
@Arboreous I just noticed that bit, you're correct this is unavoidable with pprint so the OP needs to consider a different methoTeryn
You might be able to subclass PrettyPrinter and override pformat to get your desired output. I'm not sure thoughGripper
@Kevin, Sure, but if I use a simple print, then I take an unsorted result, as I want. The problem with that is that I have to print the dict exactly as the output shows above, but simple print prints everything in one line, so I can't use it.Petulia
S
24

Python 3.8 or newer:

You can use sort_dicts=False to prevent it from sorting them alphabetically:

pprint.pprint(data, sort_dicts=False)

Python 3.7 or older:

Since Python 3.7 (or 3.6 in the case of cPython), dict preserves insertion order. For any version prior, you will need to use an OrderedDict to keep keys in order.

Although, from the doc on pprint:

Dictionaries are sorted by key before the display is computed.

This means pprint will break your desired order regardless.

Alternative method:

You can also use json.dumps to pretty print your data.

Code:

import json
from collections import OrderedDict

# For Python 3.6 and prior, use an OrderedDict
d = OrderedDict(b="Maria", c="Helen", a="George")

print(json.dumps(d, indent=1))

Output:

{
 "b": "Maria",
 "c": "Helen",
 "a": "George"
}
Sarraceniaceous answered 10/8, 2018 at 14:50 Comment(2)
Python 3.7 didn't work for that, but the last code is exactly what I was looking for, thank you so much.Petulia
@MariaPantsiou You are correct, pprint sorts your keys, I'll update the answer.Rixdollar
S
4

If you read the source of pprint.py you'll find that in PrettyPrinter._pprint_dict(), the method responsible for formatting dicts:

def _pprint_dict(self, object, stream, indent, allowance, context, level):
    write = stream.write
    write('{')
    if self._indent_per_level > 1:
        write((self._indent_per_level - 1) * ' ')
    length = len(object)
    if length:
        items = sorted(object.items(), key=_safe_tuple)
        self._format_dict_items(items, stream, indent, allowance + 1,
                                context, level)
    write('}')

_dispatch[dict.__repr__] = _pprint_dict

There's this line items = sorted(object.items(), key=_safe_tuple), so dict items are always sorted first before being processed for formatting, and you will have to override it yourself by copying and pasting it and removing the offending line in your own script:

import pprint as pp
def _pprint_dict(self, object, stream, indent, allowance, context, level):
    write = stream.write
    write('{')
    if self._indent_per_level > 1:
        write((self._indent_per_level - 1) * ' ')
    length = len(object)
    if length:
        self._format_dict_items(object.items(), stream, indent, allowance + 1,
                                context, level)
    write('}')
pp.PrettyPrinter._dispatch[dict.__repr__] = _pprint_dict

so that:

pp.pprint({"b" : "Maria", "c" : "Helen", "a" : "George"}, width=1)

will output (in Python 3.6+):

{'b': 'Maria',
 'c': 'Helen',
 'a': 'George'}
Subjoinder answered 10/8, 2018 at 14:43 Comment(2)
The issue I encountered when subclassing is that the private methods are called only when the standard representation exceeds the width. While you overcame the this problem by setting width=1, a general solution would require also overriding the PrettyPrinter.format method.Rixdollar
Indeed. I've added a new answer with a more generic approach.Subjoinder
L
4

As of Python 3.8, pprint has a sort_dicts keyword argument you can set to prevent it from sorting dictionary keys alphabetically. If you set it to false, it will use the default ordering of dictionaries (e.g. insertion order).

Example:

>>> from pprint import pprint
>>> pprint(dict(z=0, a=1), sort_dicts=False)
{'z': 0, 'a': 1}
Lipocaic answered 8/5, 2020 at 15:37 Comment(1)
Thank you, first answer that actually works and is simpleFowliang
S
3

A more generic solution is to use unittest.mock.patch to override the built-in sorted function with a function that does nothing but return the given first argument:

import pprint
from unittest.mock import patch

def unsorted_pprint(*args, **kwargs):
    with patch('builtins.sorted', new=lambda l, **_: l):
        orig_pprint(*args, **kwargs)

orig_pprint = pprint.pprint
pprint.pprint = unsorted_pprint

so that:

pprint.pprint({"b" : "Maria", "c" : "Helen", "a" : "George"})

outputs:

{'b': 'Maria', 'c': 'Helen', 'a': 'George'}
Subjoinder answered 13/4, 2019 at 0:58 Comment(0)
D
2

You should use OrderedDict from the collections library of python to keep the ordering constant

from collections import OrderedDict
from pprint import pprint
d = OrderedDict({"b" : "Maria", "c" : "Helen", "a" : "George"})
pprint(d, width = 1)

UPDATE:

Since output is important, you can use the following code, its a hack but you create a function to implement this feature:

from collections import OrderedDict
d = OrderedDict({"b" : "Maria", "c" : "Helen", "a" : "George"})
print('{', end='')
total_len = len(d)
current_index = 1
for key, value in d.items():
    print('\''+key+'\': \'' + value+ '\'', end='')
    if current_index<total_len:
        print(',')
    else:
        print('}')
    current_index += 1
Deppy answered 10/8, 2018 at 14:18 Comment(6)
The OP wants to modify the behaviour of pprint, this isn't about maintaining insertion orderTeryn
pprint would be printing the output as per the value saved. dict by default doesn't maintain the order or insertion hence the output is printed in that order. Hence I feel using an ordereddict would serve the purposeDeppy
The output of this code doesn't look much like the desired output.Arboreous
It does solve the sorting problem, but the output is quite different comparing to the desired one.Petulia
I have updated the answer to print it as per the requirement. I hope this solves your problemDeppy
@ Arghya Saha, Great, that solves my problem! Thank you!Petulia

© 2022 - 2024 — McMap. All rights reserved.