Структурированный вывод с помощью Pretty Print Python

Структурированный вывод с помощью Pretty Print

На просторах Internet сейчас можно найти бескрайные залежи финансов-технической и нормативно-распорядительной документации, разные скопления отчетов, сводок и прочих наборов открытых данных, являющихся плодом бурной деятельности наших органов власти и коммерческих структур. Все эти огромные информационные россыпи, наделенные общедоступным и хорошо задокументированным API, представляют собой совершеннейшей Клондайк для программирования все новых и новых прикладных приложений в сфере предоставления различных сервисов, анализа данных и математического моделирования. Вместе с тем, одной из основных проблем, сдерживающих разработку ПО на основе открытых данных, является крайне затрудненный обор необходимой вам информации из сложнейших многоуровневых структур (словарей), получаемых в результате соответствующих API запросов при отладке программ. Чего стоит, например, задача отбора технико-экономических показателей по кровлям в проектно-строительной документации для жилых домов со 2-м классом последствий. Ведь в цифровом представлении проектно-строительная документации для одного такого дома, представляет собой массив громадных словарей с десятками уровней вложенности и с тысячами ключей в них.

К сожалению, так любимая нами функция print(), позволяющая выводит словари с API запросами, а также иные структуры данных Python только лишь в виде одной длинной строки, далеко не является панацеей в решении вышеупомянутой проблемы по отбору необходимых нам данных.

Именно поэтому, в качестве альтернативы print(), в стандартной библиотеке Python был предусмотрен отдельный служебный модуль pprint, предназначаемый для вывода различных структур данных в удобочитаемом, и предельно наглядном виде. Как раз именно этот модуль, описываемый в данной статье, является наиболее оптимальным и полезным инструментарием для отладки Python кода, имеющего дело, как с API запросами и объемными файлами JSON формата, так и со всеми иными возможными вариантами структур данных.

К концу этой статьи вы:

  • Поймете, зачем нужен pprintмодуль
  • Научитесь использовать функцию pprint()и объект PrettyPrinter, а также узнаете, какие у них могут быть параметры
  • Получите возможность создать собственный экземпляр PrettyPrinter
  • Сохраните структурированный вывод строки в переменную для дальнейшего ее использования в различных потоках вывода
  • Узнаете, как функции print() и pprint() распознают и выводят на печать рекурсивные структуры данных

Попутно в этой статье вы также ознакомитесь с примерами кодинга  HTTP-запросов через API и синтаксического анализа JSON структур.

Почему вывод через обычный print() иногда нецелесообразен

Как уже было сказано выше, модуль Python pprint может быть полезен в целом ряде ситуаций, когда дело доходит до выполнения API запросов, работы с JSON файлами или обработки сложных и вложенных данных. Но преимущества данного модуля по сравнению со стандартной Python функцией print() в полной мере можно ощутит только лишь испробовав эти два инструментария на практике. Это мы и попытаемся сделать ниже.

Для начала, давайте получим некоторые, необходимые нам для дальнейшего использования, тестовые данные о неком фиктивном пользователе. Это можно сделать посредством соответствующего HTTP-GET запроса к web-ресурсу JSON Заполнителя, полученный результат от которого в виде JSON документа затем преобразовывается в словарь с помощью метода json.loads().

>>> from urllib import request
>>> response = request.urlopen("https://jsonplaceholder.typicode.com/users")
>>> json_response = response.read()
>>> import json
>>> users = json.loads(json_response)

В этом примере, полученный ответ от базового GET запроса предварительно преобразовавшись в словарь с помощью метода json.loads(), затем заносится в переменную users. Теперь наступило время вывести содержимое этой переменной на печать с помощью стандартной Python функции print():

>>> print(users)
[{'id': 1, 'name': 'Leanne Graham', 'username': 'Bret', 'email': 'Sincere@april.biz', 'address': {'street': 'Kulas Light', 'suite': 'Apt. 556', 'city': 'Gwenborough', 'zipcode': '92998-3874', 'geo': {'lat': '-37.3159', 'lng': '81.1496'}}, 'phone': '1-770-736-8031 x56442', 'website': 'hildegard.org', 'company': {'name': 'Romaguera-Crona', 'catchPhrase': 'Multi-layered client-server neural-net', 'bs': 'harness real-time e-markets'}}, {'id': 2, 'name': 'Ervin Howell', 'username': 'Antonette', 'email': 'Shanna@melissa.tv', 'address': {'street': 'Victor Plains', 'suite': 'Suite 879', 'city': 'Wisokyburgh', 'zipcode': '90566-7771', 'geo': {'lat': '-43.9509', 'lng': '-34.4618'}}, 'phone': '010-692-6593 x09125', 'website': 'anastasia.net', 'company': {'name': 'Deckow-Crist', 'catchPhrase': 'Proactive didactic contingency', 'bs': 'synergize scalable supply-chains'}}, {'id': 3, 'name': 'Clementine Bauch', 'username': 'Samantha', 'email': 'Nathan@yesenia.net', 'address': {'street': 'Douglas Extension', 'suite': 'Suite 847', 'city': 'McKenziehaven', 'zipcode': '59590-4157', … }}]

В результате вывода вышеназванной переменной с помощью print() у вас в консоли выведется длиннущая строка, которая в зависимости от настроек вашей операционной системы может быть либо бесконечно длинной одной строкой, либо разбитой на несколько десятков строк, как в примере выше.

Примечание. В примере выше, на самом деле приведена лишь часть (в несколько раз урезанный фрагмент) той информации, которая на самом деле должна быть реально выведена. Как видим из примера выше, даже то, что информация размещается в несколько строк, не делает ее читабельной.

По первому и последнему символу в выведенном фрагменте информации – квадратным скобкам, можно догадаться, что перед нами список. Следовательно, мы могли бы попробовать распечатать элементы этого списка через соответствующий цикл:

for user in users:
    print(user)

Благодаря этому for циклу, каждый объект нашего списка (будь то словари, подсписки, кортежи или наборы) будет выводится на отдельной строке. Но, даже при этом, довольно часто будут возникать ситуации, когда те или иные объекты при печати не будут вмещаться на одну строку. Да, печать через цикл несколько улучшает читабельность данных, но это ни в коем случае не решает проблемы их представления в максимально наглядном виде. Мы увидели некоторые преимущества печати через цикл лишь на этом фрагменте информации со сравнительно простой структурой, но что было бы, если бы вы попытались распечатать в сотни раз более объемный дамп данных с гораздо более глубоким уровнем вложенности. С увеличением сложности структур данных, целесообразность их печати в цикле через обычную функцию print(), практически нивелируется.

Конечно, для того чтобы более-менее читабельно вывести на печать содержимое объекта со сложной структурой данных, вы можете написать свою собственную, использующую рекурсию функцию, или же даже сформировать целый модуль из многочисленных функций, обрабатывающих структуры однотипных или сверхсложных объектов. Но, имеет ли смысл это делать и, тратить время на сугубо специализированный кодинг, если в стандартной библиотеке Python уже есть соответствующий универсальный модуль pprint, который обеспечивает печать или вывод в любой другой поток содержимого самых сложных и разнообразных структур данных?

Основы работы с модулем pprint

Как уже было сказано выше, pprint — это модуль Python, предназначенный для вывода на печать или в любой другой поток содержимого разнообразных структур данных в максимально визуализированной по структуре и читабельной форме. Этот модуль уже давно является частью стандартной библиотеки Python и, поэтому не требует своей отдельной установки. В принципе, все что от вас требуется для начала работы с этим модулем, это импортировать его функцию pprint():

>>> from pprint import pprint

После того, как из модуля pprint импортирована соответствующая одноименная функция, вы сможете ею воспользоваться в любом месте программы абсолютно также как и обычной функцией print(). Так, чтобы сделать вывод нашего, полученного по GET запросу фрагмента информации гораздо более читабельным, нам следует всего лишь воспользоваться следующей командой:

>>> pprint(users)

Вышеприведенный вызов функции pprint(), распечатает содержимое нашего объекта в переменной users в гораздо более читабельном и привлекательном виде:

>>> pprint(users)
[{'address': {'city': 'Gwenborough',
              'geo': {'lat': '-37.3159', 'lng': '81.1496'},
              'street': 'Kulas Light',
              'suite': 'Apt. 556',
              'zipcode': '92998-3874'},
  'company': {'bs': 'harness real-time e-markets',
              'catchPhrase': 'Multi-layered client-server neural-net',
              'name': 'Romaguera-Crona'},
  'email': 'Sincere@april.biz',
  'id': 1,
  'name': 'Leanne Graham',
  'phone': '1-770-736-8031 x56442',
  'username': 'Bret',
  'website': 'hildegard.org'},
 {'address': {'city': 'Wisokyburgh',
              'geo': {'lat': '-43.9509', 'lng': '-34.4618'},
              'street': 'Victor Plains',
              'suite': 'Suite 879',
              'zipcode': '90566-7771'},
  'company': {'bs': 'synergize scalable supply-chains',
              'catchPhrase': 'Proactive didactic contingency',
              'name': 'Deckow-Crist'},
  'email': 'Shanna@melissa.tv',
  'id': 2,
  'name': 'Ervin Howell',
  'phone': '010-692-6593 x09125',
  'username': 'Antonette',
  'website': 'anastasia.net'},

  ...

 {'address': {'city': 'Lebsackbury',
              'geo': {'lat': '-38.2386', 'lng': '57.2232'},
              'street': 'Kattie Turnpike',
              'suite': 'Suite 198',
              'zipcode': '31428-2261'},
  'company': {'bs': 'target end-to-end models',
              'catchPhrase': 'Centralized empowering task-force',
              'name': 'Hoeger LLC'},
  'email': 'Rey.Padberg@karina.biz',
  'id': 10,
  'name': 'Clementina DuBuque',
  'phone': '024-648-3804',
  'username': 'Moriah.Stanton',
  'website': 'ambrose.net'}]

Неправда ли, мило! Тут даже ключи словарей имеют визуальный отступ! Такой вывод значительно упрощает изучение содержимого и проведение визуального анализа структуры данных в выведенном фрагменте информации.

Примечание. Вывод, который вы реально увидите по результатам выполнения pprint(users) на вашем интерпретаторе будет намного длиннее того, что приведен выше. Такое усечение вывода переменной users в тексте данной статьи осуществлено только лишь по соображениям удобочитаемости.

Более кратким (удобным для быстрого ввода) псевдонимом функции pprint() является pp():

>>> from pprint import pp
>>> pp(users)

Псевдоним pp() является просто оберткой для pprint(), и ведет себя абсолютно идентично с этой функцией.

Примечание. В Python можно использовать вышеназванный псевдоним, начиная с версии 3.8.0 alpha 2.

Вместе с тем, обычный вывод pprint() сложных структур данных без уточняющих параметров, далеко не всегда может быть для вас приемлем.  Так, вполне возможно, что, когда вам нужно будет просто узнать структуру интересуемого объекта, вывод его вышеназванной функцией без параметров, окажется для вас чересчур избыточным.

Именно для таких ситуаций, когда необходимо сделать вывод сложно структурированных данных более лаконичным или специализированным исходя из заданных критериев, вместе с функцией pprint() может применятся целый ряд соответствующих параметров.

Дополнительные параметры функции pprint()

Из данного раздела нашей статьи вы узнаете обо всех семи, доступных на сегодняшний день, дополнительных параметрах функции pprint(). Все эти параметры в произвольном сочетании вы можете использовать для настройки различных аспектов отображения интересующих вас структурированных данных с тем, чтобы выводит их содержимое в наиболее читабельной и привлекательной форме. Естественно, некоторые из рассматриваемых параметров будут для вас более полезны, другие – менее. Но, одним из тех параметров, без которых вы вряд ли обойдетесь, обязательно будет для вас depth.

Вывод данных до заданной глубины вложенности при помощи depth

Одним из самых популярных параметров, применяемых в функции pprint() априори, почти всегда является depth. Суть работы данного параметра заключается в том, что он позволяет вывести содержимое структурированных данных лишь до заданной глубины вложенности. Все же остальные данные, находящиеся на более глубоких уровнях вложенности благодаря данному параметру выводятся просто в виде трех точек. По умолчанию уровень вложенности в depth не ограничен. Вот почему pprint() без данного параметра печатает содержимое, переданных в нее структур данных в полном объеме. Но, давайте попробуем распечатать содержимое нашей подопытной переменной users с приравниванием значения параметра depth к единице. Другими словами, попросим pprint() выводить содержимое этой переменной в виде трех точек сразу же начиная с 1-го уровня вложенности:

>>> pprint(users, depth=1)
[{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}]

Из выведенного результата сразу стало видно, что в переменной users мы имеем дело со списком словарей. Теперь, давайте посмотрим, какие же ключи или словари в целом содержатся в нашей переменной на самом верхнем уровне ее структуры данных. Для этого просто приравняем параметр depth к двум и, тем самым потребуем от нашей функции pprint() сокращать вывод данных переменной users в виде трех точек начиная со 2-го уровня вложенности этих данных:

>>> pprint(users, depth=2)
[{'address': {...},
  'company': {...},
  'email': 'Sincere@april.biz',
  'id': 1,
  'name': 'Leanne Graham',
  'phone': '1-770-736-8031 x56442',
  'username': 'Bret',
  'website': 'hildegard.org'},
 {'address': {...},
  'company': {...},
  'email': 'Shanna@melissa.tv',
  'id': 2,
  'name': 'Ervin Howell',
  'phone': '010-692-6593 x09125',
  'username': 'Antonette',
  'website': 'anastasia.net'},

   ...

 {'address': {...},
  'company': {...},
  'email': 'Rey.Padberg@karina.biz',
  'id': 10,
  'name': 'Clementina DuBuque',
  'phone': '024-648-3804',
  'username': 'Moriah.Stanton',
  'website': 'ambrose.net'}]

Теперь, исходя из содержимого полученного вывода, вы сможете быстро понять из каких макроэлементов (словарей верхнего уровня) выстроена структура данных в вашей переменной. Следовательно, вы легко сориентируетесь, где именно, в каком словаре или в какой его ветви необходимо искать то заветное значение, с которым ваша программа планирует работать в последующем. Таким образом, параметр depth, это весьма ценный инструментарий для изучения структуры тех данных, на базе которых вам поручено разработать то или иное ПО.

Отступ для каждого уровня вложенности данных, задаваемый indent

Для визуального структурирования данных а, следовательно и, для обеспечения их надлежащей читабельности, немаловажное значение имеет величина отступа, задаваемая для каждого уровня вложенности таких данных. Ответственным за задания этого отступа в функции pprint() является ее параметр indent, значение которого по умолчанию приравнивается к одному пробелу:

>>> pprint(users[0], depth=1)
{'address': {...},
 'company': {...},
 'email': 'Sincere@april.biz',
 'id': 1,
 'name': 'Leanne Graham',
 'phone': '1-770-736-8031 x56442',
 'username': 'Bret',
 'website': 'hildegard.org'}

>>> pprint(users[0], depth=1, indent=4)
{   'address': {...},
    'company': {...},
    'email': 'Sincere@april.biz',
    'id': 1,
    'name': 'Leanne Graham',
    'phone': '1-770-736-8031 x56442',
    'username': 'Bret',
    'website': 'hildegard.org'}

Главным образом, задание отступа в параметре indent обеспечивает визуальное вертикальное выравнивание ключей для словарей определенного уровня вложенности при их выводе посредством pprint(). Таким образом, реальный размер отступа от левого края вывода данных зависит не только от заданного отступа в параметре indent, но и от уровня вложенности этих выводимых данных. Величина данного реального отступа для конкретного уровня вложенности данных определяется просто путем умножения значения в параметре indent на значение интересующего вас уровня. Например, при значении в indent равном 4-м, реальный отступ при выводе данных для второго уровня вложенности будет составлять 8-мь пробелов.

Поскольку в приведенных выше двух примерах нет вложенности, то величина отступа в них полностью зависит от параметра indent. Обратите внимание, что в обоих примерах открывающаяся фигурная скобка { считается единицей отступа для первого ключа. Так, в первом примере открывающаяся одинарная кавычка для первого ключа идет сразу же после фигурной скобки { без пробелов между ними, потому что отступ по умолчанию установлен в 1.

Посмотрим теперь, как будет выглядеть вывод структуры с двойным уровнем вложенности при заданном отступе в 4-ре пробела. В подтверждение вышесказанному, вывод из нижеследующего примера наглядно показывает, что отступ для данных второго уровня вложенности будет всегда в 2 раза больше, чем отступ для данных первого ее уровня:

>>> pprint(users[0], depth=2, indent=4)
{   'address': {   'city': 'Gwenborough',
                   'geo': {...},
                   'street': 'Kulas Light',
                   'suite': 'Apt. 556',
                   'zipcode': '92998-3874'},
    'company': {   'bs': 'harness real-time e-markets',
                   'catchPhrase': 'Multi-layered client-server neural-net',
                   'name': 'Romaguera-Crona'},
    'email': 'Sincere@april.biz',
    'id': 1,
    'name': 'Leanne Graham',
    'phone': '1-770-736-8031 x56442',
    'username': 'Bret',
    'website': 'hildegard.org'}

Не правда ли, такой вывод pprint() с использованием indent имеет намного более структурированный и изящный вид!

Ограничение количества символов в строке с помощью width

По умолчанию функция pprint() при выводе может разрезать одну строку на несколько подстрок только в том случае, если эта строка превышает 80 символов. Но вы можете изменить это значение с помощью параметра width. В этом случае, если содержимое выводимой структуры данных превысит заданный вами в width предел, то оставшаяся часть этой структуры будет выведена в одной новой строке или в нескольких новых строках (в случае, если длина вашей строки в несколько раз превышает установленный в width предел):

>>> pprint(users[0])
{'address': {'city': 'Gwenborough',
             'geo': {'lat': '-37.3159', 'lng': '81.1496'},
             'street': 'Kulas Light',
             'suite': 'Apt. 556',
             'zipcode': '92998-3874'},
 'company': {'bs': 'harness real-time e-markets',
             'catchPhrase': 'Multi-layered client-server neural-net',
             'name': 'Romaguera-Crona'},
 'email': 'Sincere@april.biz',
 'id': 1,
 'name': 'Leanne Graham',
 'phone': '1-770-736-8031 x56442',
 'username': 'Bret',
 'website': 'hildegard.org'}

Из приведенного выше примера видно, что при заданном по умолчании пределе длины строки в 80 символов, все два компонента словаря users[0]['address']['geo'] со значениями для ключей  'lat' и 'lng' записаны в одну строку. Это означает, что общее количество символов для вывода данного словаря, включая пробелы и левый его отступ, не превышает 80-ти символов. Соответственно, именно по этой причине функция pprint() выводит словарь 'geo' в одну строку.

Однако, количество символов для всех элементов словаря users[0]['company'], находящегося в той же структуре данных, превышает наш предел по умолчанию. Именно поэтому, каждый компонент (пара – ключ, значение) для данного словаря помещается в новую строку. Этот принцип верен для всех структур данных в Python, таких как: словари списки, кортежи и наборы.

>>> pprint(users[0], width=160)
{'address': {'city': 'Gwenborough', 'geo': {'lat': '-37.3159', 'lng': '81.1496'}, 'street': 'Kulas Light', 'suite': 'Apt. 556', 'zipcode': '92998-3874'},
 'company': {'bs': 'harness real-time e-markets', 'catchPhrase': 'Multi-layered client-server neural-net', 'name': 'Romaguera-Crona'},
 'email': 'Sincere@april.biz',
 'id': 1,
 'name': 'Leanne Graham',
 'phone': '1-770-736-8031 x56442',
 'username': 'Bret',
 'website': 'hildegard.org'}

При установлении параметра width, например, в 160 символов, функция pprint() попытается уместить все вложенные словари, имеющиеся в выводимой ею структуре данных, в одну строку. Если же попытаться в width установить еще больший предел и, например, довести его до 500 символов, то pprint() выведет все, заданные ей первым аргументом, структурированные данные в одну строку:

>>> pprint(users[0], width=500)
{'address': {'city': 'Gwenborough', 'geo': {'lat': '-37.3159', 'lng': '81.1496'}, 'street': 'Kulas Light', 'suite': 'Apt. 556', 'zipcode': '92998-3874'}, 'company': {'bs': 'harness real-time e-markets', 'catchPhrase': 'Multi-layered client-server neural-net', 'name': 'Romaguera-Crona'}, 'email': 'Sincere@april.biz', 'id': 1, 'name': 'Leanne Graham', 'phone': '1-770-736-8031 x56442', 'username': 'Bret', 'website': 'hildegard.org'}

В вышеприведенном примере мы с вами посмотрели, что будет при установке в параметре width относительно большого значения. Но, как же будет вести себя функция pprint() при задании этому параметру минимального значения, например, приравненного к 1. Это отнюдь не приведет к ожидаемому посимвольному выводу содержимого нашей переменной в одну колонку. На самом деле, напротив, задание таких нереально низких (бесполезных с практической точки зрения) приделов, приводит нас к весьма полезному для анализа отображению каждого компонента из выводимой структуры данных в отдельной строке. При этом, вы по-прежнему получаете необходимый визуальный отступ, адекватно выравнивающий все компоненты в выводимой вами структуре данных:

>>> pprint(users[0], width=5)
{'address': {'city': 'Gwenborough',
             'geo': {'lat': '-37.3159',
                     'lng': '81.1496'},
             'street': 'Kulas '
                       'Light',
             'suite': 'Apt. '
                      '556',
             'zipcode': '92998-3874'},
 'company': {'bs': 'harness '
                   'real-time '
                   'e-markets',
             'catchPhrase': 'Multi-layered '
                            'client-server '
                            'neural-net',
             'name': 'Romaguera-Crona'},
 'email': 'Sincere@april.biz',
 'id': 1,
 'name': 'Leanne '
         'Graham',
 'phone': '1-770-736-8031 '
          'x56442',
 'username': 'Bret',
 'website': 'hildegard.org'}

Все-таки, трудно заставить Python pprint() печатать уродливо. Эта функция сделает все возможное, чтобы вывести содержимой переданной ей структуры данных в предельно изящной и читабельной форме!

Этот пример, помимо надлежащего поведения width, также показывает нам, как pprint() разбивает длинные строки текста. Обратите внимание, как значение ключа ["catchPhrase"] в словаре users[0]["company"], которое изначально было приравнено к довольно длинной строке 'Multi-layered client-server neural-net', при выводе в данном примере очень корректно было разделено на несколько подстрок. Следовательно, на основе этого можно констатировать, что функция pprint() автоматически переносит строки только по целым словам, предотвращая тем самым разрывы слов и обеспечивая лучшую читабельность длинных (переносимых) строк.

Разбиение последовательностей по строкам параметром compact

На первый взгляд можно подумать, что параметр compact чем-то напоминает раннее рассмотренный нами width, так как также как и он, отвечает за переносы строк при выводе структур данных. Однако compact влияет на вывод содержимого структурированных данных (последовательностей) только лишь после применения к соответствующему выводу значения параметра width.

Примечание: Параметр compact не влияет на вывод словарей и применим только для изменения отображения таких последовательностей, как: списки, наборы и кортежи. Это сделано преднамеренно, хотя и вызывает множество нареканий, с ходом обсуждения которых можно ознакомится в Python Issue #34798.

Параметр compact принимает лишь логические значения True или False. При значении True вывод последовательностей данных (списков, наборов и кортеже) может быть перенесен на новые строки только лишь в зависимости от значения параметра width. Если же параметр compact принимает значение False (по умолчанию) и структура данных длиннее, чем это задано в width, то каждый элемент последовательностей в этой структуре отображается на отдельной строке:

>>> pprint(users, depth=1)
[{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}]

>>> pprint(users, depth=1, width=40)
[{...},
 {...},
 {...},
 {...},
 {...},
 {...},
 {...},
 {...},
 {...},
 {...}]

>>> pprint(users, depth=1, width=40, compact=True)
[{...}, {...}, {...}, {...}, {...},
 {...}, {...}, {...}, {...}, {...}]

При выводе структуры списка в нашей переменной users (depth=1) без указания значений для всех остальных параметров при помощи 1-го (сокращенного) варианта вызов функции pprint(), мы получаем соответствующее отображение этого списка в виде одной строки. При добавлении же к pprint() параметра width с ограничением в 40 символов (2-й вызов функции), элементы списка в нашей переменной users будут выведены в виде отдельных строк. Если же мы затем к pprint() еще и добавим параметр compact=True (3-й вызов функции), то строка с элементами списка будет разбита на несколько подстрок исходя из значения width=40, что сделает ее намного компактнее нежели она была при 2-м вызове функции pprint().

Примечание. Следует учесть, что при установке параметра width в значение меньшее, чем 7 символов (по числу символов – 7 в данном случае эквивалентно [{...}, выводу), параметр depth с любым своим значением, функцией pprint() полностью игнорируется.  В итоге, при такой ситуации, pprint() отображает всю выводимую ей структуру данных без сворачивания. О данной проблеме сообщается как об  ошибке #45611.

compact наиболее полезен для длинных последовательностей с короткими элементами, вывод которых без данного параметра занял бы очень много строк и, следовательно, сделал бы отображения этих последовательностей менее читабельными.

Перенаправление вывода pprint() благодаря параметру: stream

Параметр stream в функции pprint() аналогично параметру file в print(), указывает на файловый объект Python, куда производится вывод данных из первого ее аргумента. По умолчанию stream, точно также, как и предустановленный вывод print(), указывает на специальный файловый объект *sys.stdout/, обеспечивающий стандартный вывод информации от интерпретатора на консоль. Вместе с тем, аналогично применению file в print(), вы можете перенаправить стандартный вывод из функции pprint() в любой необходимый вам файловый объект, указав его в параметре stream.

>>> with open("output.txt", mode="w") as file_object:
...     pprint(users, stream=file_object)

В этом примере с помощью функции open() в переменной _file_object_создается  файловый объект на основе файла  output.txt. Затем параметр stream в pprint() принимает значение переменной соответствующего файлового объекта. В итоге, если вы затем откроете файл output.txt, то увидите там изящно напечатанное содержимое так полюбившейся нам переменной users.

Хотя в Python есть собственный модуль для ведения различных журналов – logging, тем не менее, для отправки изящно структурированных выходных данных в файлы, используемые в качестве журналов, вы также можете легко применять pprint() в сочетании с параметром stream.

Предотвращение сортировки словарей за счет sort_dicts

Хотя словари обычно считаются неупорядоченными структурами данных, но начиная с Python 3.6 с целью экономии памяти эти данные все-таки автоматически упорядочиваются по вставке. Вместе с тем, отображение компонентов словарей упорядоченных по вставке не всегда удобочитаемо, малопригодно в анализе и, зачастую различается порядком в компонентах от вывода к выводу. Именно поэтому, благодаря параметру sort_dicts, установленному по умолчанию в True, функция pprint() упорядочивает вывод компонентов словарей в алфавитном порядке следования их ключей.

>>> pprint(users[0], depth=1)
{'address': {...},
 'company': {...},
 'email': 'Sincere@april.biz',
 'id': 1,
 'name': 'Leanne Graham',
 'phone': '1-770-736-8031 x56442',
 'username': 'Bret',
 'website': 'hildegard.org'}

>>> pprint(users[0], depth=1, sort_dicts=False)
{'id': 1,
 'name': 'Leanne Graham',
 'username': 'Bret',
 'email': 'Sincere@april.biz',
 'address': {...},
 'phone': '1-770-736-8031 x56442',
 'website': 'hildegard.org',
 'company': {...}}

Вместе с тем, иногда возникают случае, когда нам бывает необходимо проанализировать компоненты словарей именно по времени их вставки в эти словари. Как раз при таких ситуациях мы можем приравнять sort_dicts к False и получить нужный нам словарь в первозданном виде с упорядочиванием компонентов по вставке.

Обеспечение читабельности чисел с помощью underscore_number

Параметр underscore_number, это сравнительно новая функциональность pprint(), которая предоставляется лишь начиная с Python 3.10. Данный параметр при его установке в True позволяет разбить вывод больших чисел на триады и, тем самым обеспечивает улучшение их читабельность. К сожалению, так как наша излюбленная переменная users не содержит больших чисел, для практического рассмотрения underscore_number, нам придется дополнительно написать пару строк следующего кода:

>>> number_list = [123456789, 10000000000000]
>>> pprint(number_list, underscore_numbers=True)
[123_456_789, 10_000_000_000_000

Вполне возможно, что, даже используя Python 3.10 при запуске данного кода вы получите ошибку. Да, действительно, по состоянию на октябрь 2021 года этот параметр не работал при прямом вызове pprint(). Но, уже в Python 3.10.1 от декабря 2021 года, эта проблема уже должна быть решена.

Однако, если underscore_numbers при прямом вызове pprint() по каким-то причинам у вас все еще не работает, а вам действительно требуется функциональность данного параметра, то вы всегда можете создать свой собственный PrettyPrinter объект, в котором вышеназванный параметр должен гарантированно работать.

О том, как создать и работать с PrettyPrinter объектом вы узнаете далее.

Создание и работа с пользовательским объектом PrettyPrinter

В модули pprint предусмотрен класс PrettyPrinter, благодаря которому вы можете создавать свои собственные пользовательские объекты со всеми необходимыми вам параметрами для вывода структурированных данных. Эти параметры из созданных вами объектов в виде параметров по умолчанию затем могут быть использованы при вызове, предусматриваемого данными объектами, метода .pprint(), который по факту аналогичен функции pprint():

>>> from pprint import PrettyPrinter
>>> custom_printer = PrettyPrinter(
...     indent=4,
...     width=100,
...     depth=2,
...     compact=True,
...     sort_dicts=False,
...     underscore_numbers=True
... )
...
>>> custom_printer.pprint(users[0])
{   'id': 1,
    'name': 'Leanne Graham',
    'username': 'Bret',
    'email': 'Sincere@april.biz',
    'address': {   'street': 'Kulas Light',
                   'suite': 'Apt. 556',
                   'city': 'Gwenborough',
                   'zipcode': '92998-3874',
                   'geo': {...}},
    'phone': '1-770-736-8031 x56442',
    'website': 'hildegard.org',
    'company': {   'name': 'Romaguera-Crona',
                   'catchPhrase': 'Multi-layered client-server neural-net',
                   'bs': 'harness real-time e-markets'}}
>>> number_list = [123456789, 10000000000000]
>>> custom_printer.pprint(number_list)
[123_456_789, 10_000_000_000_000]

В процессе выполнения кода из вышеприведенного кода, мы с вами:

  • Импортировали из модуля pprint класс PrettyPrinter
  • Создали на основе импортированного нами класса новый объект, который занесли в переменную custom_printer, предварительно инициализировав в этом объекте необходимые нам параметры для отображения соответствующих структур данных
  • На основе параметров, заданных в созданном нами объекте custom_printer, осуществили вывод сведений о первом пользователе, которые хранились в переменной users[0]
  • Определили список, состоящий из двух больших чисел, и занесли его в переменную number_list
  • Осуществили вывод переменной number_list исходя из параметров объекта custom_printer, одним из которых является underscore_numbers, позволяющий отображать большие числа в читабельном виде.

Обратите внимание, что инициализатор PrettyPrinter для создания нового объекта принимает абсолютно такие же аргументы (параметры), что и функция pprint(), за исключением первого аргумента, который в pprint() всегда указывает на структуру данных, содержимое которой необходимо отобразить.

Таким образом, благодаря имеющимся в модули pprint возможностям по созданию PrettyPrinter объектов с пользовательскими предустановками, вы сможете собрать все необходимые вам универсальные предустановки в одном PrettyPrinter объекте и, использовать его для отображения тех или иных данных, даже тогда, когда эти данные будут разнится по структуре, типу и содержанию.

Сохранение отображения вывода данных в переменной с помощью pformat()

Часто возникают ситуации, когда выведенное функцией pprint() отображение данных нужно также еще и подвергнуть определенной дополнительной обработки. Например, вам может понадобится заменить (перевести с английского) некоторые ключи в словарях, откорректировать их содержание посредством регулярных выражений, или же просто удалить в этих словарях скобки и кавычки для того, чтоб они стали еще более удобочитаемыми.

Что бы мы ни хотели сделать дополнительно с результирующим отображением данных, нам в любом случае следует воспользоваться функцией pformat(), которая абсолютно аналогична pprint() за исключением того, что осуществляет свой вывод не в поток (файл), а в переменную:

>>> from pprint import pformat
>>> address = pformat(users[0]["address"])
>>> chars_to_remove = ["{", "}", "'"]
>>> for char in chars_to_remove:
...     address = address.replace(char, "")
...
>>> print(address)
city: Gwenborough,
 geo: lat: -37.3159, lng: 81.1496,
 street: Kulas Light,
 suite: Apt. 556,
 zipcode: 92998-3874

Функцию pformat() можно воспринимать в качестве своеобразного инструментария, позволяющего адаптировать уже имеющейся изящный и читабельный вывод содержимого данных к еще более углубленной обработки всеми возможными дополнительными средствами программирования.

Особую ценность использование pformat() может иметь в сфере бэкенд при создании собственного API и формировании ответов на соответствующие запросы в виде информационно-емких и четко структурированных JSON строк. Наверняка при наличии качественного и понятного API в такой ситуации, функцию pformat() косвенно, но по достоинству смогут оценить все ваши  конечные пользователи.

Обработка рекурсивных структур данных

Функция Python pprint() сама по себе является рекурсивной, так как призвана одинаково хорошо и в соответствии с заданными параметрами выводит содержимое всех родительских, дочерних, внучатых и т.п. компонентов (словарей, списков и т.д.) своей отображаемой структуры данных, невзирая от уровня их вложенности.

Но, что будет, если вышеназванная рекурсивная функция столкнется с отображаемой ее же рекурсивной структурой данных. Представьте себе, что у вас есть словарь A и словарь B, которые взаимно пересекаются следующим образом:

  • Словарь A имеет один ключ link, который указывает на словарь B.
  • Словарь B также имеет один ключ с тем же именем link, который, в свою очередь, указывает на словарь A.

Обычно в таких ситуациях, если наша воображаемая рекурсивная функция не предусматривает обработку подобных циклических ссылок, то программа при ее вызове зависает. В данном конкретном случае предположим, что наша воображаемая рекурсивная функция призвана распечатать словарь А. Но тогда, распечатывая словарь А, эта функция должна будет еще и распечатать его дочерний элемент – словарь B, который, в свою очередь, обратно ссылается на словарь А. Таким образом, эта наша печать будет продолжаться до бесконечности.

К счастью, функция pprint(), также как и обычная функция print(), справляется с этой проблемой весьма изящно:

>>> A = {}
>>> B = {"link": A}
>>> A["link"] = B
>>> print(A)
{'link': {'link': {...}}}
>>> from pprint import pprint
>>> pprint(A)
{'link': {'link': <Recursion on dict with id=3032338942464>}}

При этом, если обычная Python функция print() просто урезает отображение выводимой рекурсивной структуры тремя точками, то pprint() в такой же ситуации явно уведомляет вас о рекурсии, а также добавляет идентификатор выводимой рекурсивной структуры данных.

Выводы

В рамках этой статьи вы изучили основные нюансы применения Python модуля pprint, а также довольно подробно познакомились с возможностями использования таких составляющих этого модуля, как pprint() и PrettyPrinter. В частности, на множестве примеров в статье вы могли убедиться, что функция pprint() особенно полезна при разработке всевозможных программ, связанных с обработкой сложных и многоуровневых структур данных. Данная функция, как, впрочем, и другие составляющие модуля pprint, безусловно обязательно должны вам пригодиться в разработке приложений, использующих незнакомые API, формирующих эти API заново, либо работающих с базами данных, содержащими многократно вложенные JSON структуры.

Из этой статьи вы узнали, как:

  • Импортировать модуль pprint для последующего использования в ваших программах
  • Использовать функцию pprint()вместо обычной Python функции print()
  • Работать с параметрами этой функции с целью настройки читабельного и изящного вывода необходимых вам структур данных различной сложности
  • Сохранять предварительно отформатированный вывод структур данных в переменную для последующей ее дополнительной обработки перед печатью
  • Создавать пользовательский объект PrettyPrinter, где можно собрать универсальные параметры для последующего вывода содержимого самых разнообразных структур данных
  • Ведет себя функция pprint() в случае обработки ею рекурсивных структур данных.

В практических примерах для данной статьи, как правило, использовалась тестовая (взятая по API из специализированного JSON Заполнителя) структура данных со сведениями о вымышленных пользователях, позволившая вам воочию разобраться со всеми основными возможностями модуля pprint.

Поздравляем! Теперь, обладая знаниями из этой статьи, вы сможете максимально эффективно работать со сложными структурами данных используя весь спектр возможностей Python модуля pprint.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09