Работа с данными JSON в Python
С момента своего создания JSON быстро стал стандартом де-факто для обмена информацией. Скорее всего, вы здесь, потому что вам нужно перенести некоторые данные отсюда туда. Возможно, вы собираете информацию через API или храните свои данные в база данных документов. Так или иначе, вы по уши в JSON, и у вас есть выход на Python.
К счастью, это довольно распространенная задача, и, как и в случае большинства обычных задач, Python делает ее почти отвратительно легкой. Не бойтесь, собратья Pythoneers и Pythonistas. Это будет бриз!
_ Итак, мы используем JSON для хранения и обмена данными? Да, вы получили это! Это не более чем стандартный формат, который сообщество использует для передачи данных. Имейте в виду, JSON - не единственный формат, доступный для такой работы, но XML и YAML, вероятно, являются только другие стоит упомянуть на одном дыхании. _
*Бесплатная загрузка PDF:* https://realpython.com/bonus/python-cheat-sheet-short/[Python 3 Шпаргалка]
A (очень) краткая история JSON
Не удивительно, что * J ava S Cript O Bject N * Otation был вдохновлен подмножеством языка программирования JavaScript, имеющим дело с литеральным синтаксисом объекта. У них есть nifty веб-сайт, который объясняет все это. Не беспокойтесь: JSON уже давно стал независимым от языка и существует как собственный стандарт its, поэтому, к счастью, мы можем избежать использования JavaScript ради этого обсуждения.
В конечном счете, сообщество в целом приняло JSON, потому что людям и машинам легко создавать и понимать.
Смотри, это JSON!
Приготовься. Я собираюсь показать вам немного реальной жизни JSON - точно так же, как вы могли бы увидеть в дикой природе. Это нормально: JSON должен быть доступен для чтения любому, кто использовал язык C-стиля, а Python - язык C-стиля … так что это вы!
{
"firstName": "Jane",
"lastName": "Doe",
"hobbies": ["running", "sky diving", "singing"],
"age": 35,
"children": [
{
"firstName": "Alice",
"age": 6
},
{
"firstName": "Bob",
"age": 8
}
]
}
Как видите, JSON поддерживает примитивные типы, такие как строки и числа, а также вложенные списки и объекты.
_ Подождите, это похоже на словарь Python! Я знаю, верно? На данный момент это довольно универсальная система обозначений объектов, но я не думаю, что UON скатывается с языка так же хорошо. Не стесняйтесь обсуждать альтернативы в комментариях. _
Уф! Вы пережили свою первую встречу с каким-то диким JSON. Теперь вам просто нужно научиться приручать его.
Python поддерживает JSON изначально!
Python поставляется со встроенным пакетом https://docs.python.org/3/library/json.html [+ json +
] для кодирования и декодирования данных JSON.
Просто подбросьте этого маленького парня вверху вашего файла:
import json
Немного словарный запас
Процесс кодирования JSON обычно называется сериализацией . Этот термин относится к преобразованию данных в серию байтов (следовательно, serial) для хранения или передачи по сети. Вы также можете услышать термин «маршалинг», но это https://stackoverflow.com/questions/770474/what-is-the-difference-between-serialization-and-marshaling в целом другом обсуждении]. Естественно, десериализация - это взаимный процесс декодирования данных, которые были сохранены или доставлены в стандарте JSON.
_ Хлоп! Это звучит довольно технически. Определенно. Но на самом деле все, о чем мы здесь говорим, это _reading и writing. Подумайте об этом так: encoding предназначен для записи данных на диск, а decoding предназначен для читания данных в память. __
Сериализация JSON
Что происходит после того, как компьютер обрабатывает много информации? Нужно взять дамп данных. Соответственно, библиотека + json +
предоставляет метод + dump () +
для записи данных в файлы. Существует также метод + dumps () +
(произносится как «dump-s») для записи в строку Python.
Простые объекты Python переводятся в JSON в соответствии с довольно интуитивным преобразованием.
Python | JSON |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Простой пример сериализации
Представьте, что вы работаете с объектом Python в памяти, который выглядит примерно так:
data = {
"president": {
"name": "Zaphod Beeblebrox",
"species": "Betelgeusian"
}
}
Крайне важно сохранить эту информацию на диске, поэтому ваша задача - записать ее в файл.
Используя менеджер контекста Python, вы можете создать файл с именем + data_file.json +
и открыть его в режиме записи. (Файлы JSON обычно заканчиваются расширением + .json +
.)
with open("data_file.json", "w") as write_file:
json.dump(data, write_file)
Обратите внимание, что + dump () +
принимает два позиционных аргумента: (1) объект данных, подлежащий сериализации, и (2) файлоподобный объект, в который будут записаны байты.
Или, если вы были так склонны продолжать использовать эти сериализованные данные JSON в своей программе, вы можете записать их в собственный объект Python + str +
.
json_string = json.dumps(data)
Обратите внимание, что файлоподобный объект отсутствует, поскольку вы фактически не записываете на диск. Кроме этого, + dumps () +
аналогично + dump () +
.
Ура! Вы родили какого-то малыша JSON и готовы выпустить его в дикую природу, чтобы стать большим и сильным.
Некоторые полезные аргументы ключевого слова
Помните, что JSON должен легко читаться людьми, но читаемого синтаксиса недостаточно, если все это сжато. Кроме того, у вас, вероятно, другой стиль программирования, чем у меня, и вам может быть проще читать код, когда он отформатирован по вашему вкусу.
_
ПРИМЕЧАНИЕ: Оба метода + dump () +
и + dumps () +
используют одинаковые ключевые аргументы.
_
Первый вариант, который большинство людей хотят изменить - это пробел. Вы можете использовать ключевой аргумент + indent +
, чтобы указать размер отступа для вложенных структур. Проверьте разницу, используя + data +
, который мы определили выше, и запустив в консоли следующие команды:
>>>
>>> json.dumps(data)
>>> json.dumps(data, indent=4)
Другой вариант форматирования - аргумент ключевого слова + separators +
. По умолчанию это кортеж из двух строк-разделителей + (", ",": ") +
, но общей альтернативой для компактного JSON является + (", ",": ") +
. Посмотрите на образец JSON еще раз, чтобы увидеть, где эти разделители вступают в игру.
Есть и другие, например + sort_keys +
, но я понятия не имею, что это делает. Вы можете найти полный список в docs, если вам интересно.
Десериализация JSON
Отлично, похоже, ты запечатлел в себе какой-то дикий JSON! Теперь пришло время привести его в форму. В библиотеке + json +
вы найдете + load () +
и + load () +
для преобразования закодированных данных JSON в объекты Python.
Как и в случае с сериализацией, существует простая таблица преобразования для десериализации, хотя вы, вероятно, уже догадались, как она выглядит.
JSON | Python |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Технически это преобразование не является идеальной обратной таблицей сериализации. В основном это означает, что если вы кодируете объект сейчас, а потом декодируете его позже, вы не сможете получить точно такой же объект обратно. Я предполагаю, что это немного похоже на телепортацию: разбейте мои молекулы здесь и соедините их там. Я все еще тот же человек?
В действительности, это скорее похоже на то, как один друг переводит что-то на японский, а другой - обратно на английский. В любом случае, простейшим примером будет кодирование + tuple +
и получение + list +
после декодирования, например, так:
>>>
>>> blackjack_hand = (8, "Q")
>>> encoded_hand = json.dumps(blackjack_hand)
>>> decoded_hand = json.loads(encoded_hand)
>>> blackjack_hand == decoded_hand
False
>>> type(blackjack_hand)
<class 'tuple'>
>>> type(decoded_hand)
<class 'list'>
>>> blackjack_hand == tuple(decoded_hand)
True
Простой пример десериализации
На этот раз представьте, что на диске хранятся некоторые данные, которыми вы хотели бы манипулировать в памяти. Вы по-прежнему будете использовать менеджер контекста, но на этот раз вы откроете существующий + data_file.json +
в режиме чтения.
with open("data_file.json", "r") as read_file:
data = json.load(read_file)
Здесь все довольно просто, но имейте в виду, что результат этого метода может вернуть любой из разрешенных типов данных из таблицы преобразования. Это важно только в том случае, если вы загружаете данные, которых раньше не видели. В большинстве случаев корневым объектом будет + dict +
или + list +
.
Если вы извлекли данные JSON из другой программы или иным образом получили строку данных в формате JSON в Python, вы можете легко десериализовать это с помощью + load () +
, который естественным образом загружается из строки:
json_string = """
{
"researcher": {
"name": "Ford Prefect",
"species": "Betelgeusian",
"relatives": [
{
"name": "Zaphod Beeblebrox",
"species": "Betelgeusian"
}
]
}
}
"""
data = json.loads(json_string)
Вуаля! Вы приручили дикий JSON, и теперь он под вашим контролем. Но то, что вы делаете с этой силой, зависит от вас. Вы можете кормить его, воспитывать и даже учить трюкам. Дело не в том, что я тебе не доверяю … но держу это на поводке, хорошо?
Пример из реального мира (вроде)
В качестве вводного примера вы будете использовать JSONPlaceholder, отличный источник поддельных данных JSON для практических целей.
Сначала создайте файл сценария с именем + scratch.py +
или как хотите. Я не могу тебя остановить.
Вам нужно будет сделать запрос API к службе JSONPlaceholder, поэтому просто используйте пакет + запросы +
для выполнения тяжелой работы. Добавьте этот импорт вверху вашего файла:
import json
import requests
Теперь вы будете работать со списком TODO, потому что … вы знаете, это обряд или что-то в этом роде.
Продолжайте и сделайте запрос к API JSONPlaceholder для конечной точки +/todos +
. Если вы не знакомы с + reports +
, на самом деле есть удобный метод + json () +
, который сделает всю работу за вас, но вы можете попрактиковаться в использовании библиотеки + json +
для десериализации `+ text + `атрибут объекта ответа. Это должно выглядеть примерно так:
response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos = json.loads(response.text)
Вы не верите, что это работает? Хорошо, запустите файл в интерактивном режиме и проверьте его сами. Пока вы это делаете, проверьте тип + todos +
. Если вы чувствуете себя авантюрным, взгляните на первые 10 или около того пунктов в списке.
>>>
>>> todos == response.json()
True
>>> type(todos)
<class 'list'>
>>> todos[:10]
...
Видишь, я бы не соврал тебе, но я рад, что ты скептик.
_
Что такое интерактивный режим? Ах, я думал, вы никогда не спросите! Вы знаете, как вы всегда прыгаете вперед и назад между вашим редактором и терминалом? Что ж, мы, хитрые Pythoneers, используем интерактивный флаг + -i +
, когда запускаем скрипт. Это отличный маленький трюк для тестирования кода, потому что он запускает скрипт, а затем открывает интерактивную командную строку с доступом ко всем данным из скрипта!
_
Хорошо, время для некоторых действий. Вы можете увидеть структуру данных, посетив endpoint в браузере, но вот пример TODO:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Есть несколько пользователей, каждый с уникальным + userId +
, и у каждой задачи есть логическое свойство + complete +
. Можете ли вы определить, какие пользователи выполнили больше всего задач?
# Map of userId to number of complete TODOs for that user
todos_by_user = {}
# Increment complete TODOs count for each user.
for todo in todos:
if todo["completed"]:
try:
# Increment the existing user's count.
todos_by_user[todo["userId"]] += 1
except KeyError:
# This user has not been seen. Set their count to 1.
todos_by_user[todo["userId"]] = 1
# Create a sorted list of (userId, num_complete) pairs.
top_users = sorted(todos_by_user.items(),
key=lambda x: x[1], reverse=True)
# Get the maximum number of complete TODOs.
max_complete = top_users[0][1]
# Create a list of all users who have completed
# the maximum number of TODOs.
users = []
for user, num_complete in top_users:
if num_complete < max_complete:
break
users.append(str(user))
max_users = " and ".join(users)
Да, да, ваша реализация лучше, но суть в том, что теперь вы можете манипулировать данными JSON как обычным объектом Python!
Я не знаю о вас, но когда я снова запускаю скрипт в интерактивном режиме, я получаю следующие результаты:
>>>
>>> s = "s" if len(users) > 1 else ""
>>> print(f"user{s} {max_users} completed {max_complete} TODOs")
users 5 and 10 completed 12 TODOs
Это круто и все, но вы здесь, чтобы узнать о JSON. Для окончательного задания вы создадите файл JSON, содержащий полные TODO для каждого из пользователей, которые выполнили максимальное количество TODO.
Все, что вам нужно сделать, это отфильтровать + todos +
и записать полученный список в файл. Ради оригинальности вы можете вызвать выходной файл + Filter_Data_file.json +
. Есть несколько способов сделать это, но вот один из них:
# Define a function to filter out completed TODOs
# of users with max completed TODOS.
def keep(todo):
is_complete = todo["completed"]
has_max_count = str(todo["userId"]) in users
return is_complete and has_max_count
# Write filtered TODOs to file.
with open("filtered_data_file.json", "w") as data_file:
filtered_todos = list(filter(keep, todos))
json.dump(filtered_todos, data_file, indent=2)
Отлично, вы избавились от всех данных, которые вам не нужны, и сохранили полезные вещи в совершенно новый файл! Запустите сценарий еще раз и проверьте + Filter_data_file.json +
, чтобы убедиться, что все работает. Он будет находиться в том же каталоге, что и + scratch.py +
, когда вы его запустите.
Теперь, когда вы продвинулись так далеко, держу пари, что вы чувствуете себя довольно жарко, верно? Не дерзайте: смирение - это добродетель. Я склонен согласиться с вами, хотя. Пока что плавание прошло гладко, но вы можете захлопнуть люки на этом последнем этапе пути.
Кодирование и декодирование пользовательских объектов Python
Что происходит, когда мы пытаемся сериализовать класс + Elf +
из приложения Dungeons & Dragons, над которым вы работаете?
class Elf:
def __init__(self, level, ability_scores=None):
self.level = level
self.ability_scores = {
"str": 11, "dex": 12, "con": 10,
"int": 16, "wis": 14, "cha": 13
} if ability_scores is None else ability_scores
self.hp = 10 + self.ability_scores["con"]
Неудивительно, что Python жалуется, что + Elf +
не serializable (что вы бы знали, если бы когда-нибудь пытались сказать эльфу иначе):
>>>
>>> elf = Elf(level=4)
>>> json.dumps(elf)
TypeError: Object of type 'Elf' is not JSON serializable
Хотя модуль + json +
может обрабатывать большинство встроенных типов Python, он не понимает, как кодировать пользовательские типы данных по умолчанию. Это все равно что пытаться втиснуть квадратный колышек в круглое отверстие - вам нужна гудение и родительский контроль.
Упрощение структур данных
Теперь вопрос в том, как бороться с более сложными структурами данных. Что ж, вы можете попробовать закодировать и декодировать JSON вручную, но есть немного более умное решение, которое сэкономит вам немного работы. Вместо прямого перехода от пользовательского типа данных к JSON вы можете добавить промежуточный шаг.
Все, что вам нужно сделать, это представить ваши данные в терминах встроенных типов, которые + json +
уже понимает. По сути, вы переводите более сложный объект в более простое представление, которое модуль + json +
затем переводит в JSON. Это как переходное свойство в математике: если A = B и B = C, то A = C.
Чтобы разобраться в этом, вам понадобится сложный объект для игры. Вы можете использовать любой пользовательский класс, который вам нравится, но в Python есть встроенный тип + complex +
для представления комплексных чисел, и он не сериализуем по умолчанию. Итак, ради этих примеров, ваш сложный объект будет объектом + complex +
. Смущены еще?
>>>
>>> z = 3 + 8j
>>> type(z)
<class 'complex'>
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable
_ Откуда берутся комплексные числа? Видите ли, когда действительное число и воображаемое число очень любят друг друга, они складываются вместе, образуя число, которое (оправданно) называется _complex