Программирование с ИИ
ENRU
2.4

Переменные, типы, структуры данных

четыре контейнера, из которых собрано почти всё

Что это

Почти любые данные в программе лежат в одной из четырёх структур: словарь, список, кортеж или множество. Зная, чем они отличаются, читаешь код гораздо быстрее — по одному виду скобок понятно, что происходит.

Отдельно разберём аннотации типов (text: str): они в коде от ИИ встречаются постоянно и читаются проще, чем кажутся.

Как выглядит в коде

user = {"id": 7, "name": "Ola", "cities": ["Tønsberg", "Oslo"]}

print(user["name"])
print(user.get("email"))
print(user.get("email", "не указан"))

cities = user["cities"]
cities.append("Bergen")
print(user["cities"])          # изменился и оригинал

point = (59.27, 10.41)
lat, lon = point
print(lat, lon)

seen = {"Oslo", "Bergen", "Oslo"}
print(sorted(seen), len(seen))

Вывод:

измерено
Ola
None
не указан
['Tønsberg', 'Oslo', 'Bergen']
59.27 10.41
['Bergen', 'Oslo'] 2

Четыре структуры

ВидСкобкиДля чегоПризнак в коде
Словарь{"ключ": значение}данные с именами полейобращение x["name"]
Список[1, 2, 3]упорядоченный наборобращение по номеру x[0], .append()
Кортеж(1, 2)набор, который не меняютраспаковка a, b = x
Множество{1, 2, 3}уникальные значенияпроверка if x in seen

Фигурные скобки означают и словарь, и множество — различить легко: если внутри есть двоеточия, это словарь.

Две строки, которые надо заметить

print(user.get("email"))           # None
print(user.get("email", "не указан"))

user["email"] при отсутствии ключа падает с KeyError. user.get("email") возвращает None молча. Разница принципиальная и постоянно встречается в коде от ИИ: .get() — частый способ «починить» ошибку, спрятав её (статья 2.14).

cities = user["cities"]
cities.append("Bergen")
print(user["cities"])   # ['Tønsberg', 'Oslo', 'Bergen']

cities — не копия, а то же самое. Списки и словари передаются по ссылке: изменил один — изменились оба. Это источник целого класса ошибок, которые выглядят как магия.

Аннотации типов

def add_reminder(user_id: int, text: str) -> int:

Читается слева направо: user_id — целое число, text — строка, функция возвращает целое число. Это подсказка для читателя, а не проверка: Python аннотации не навязывает, и передать строку вместо числа ничто не помешает.

Расшифровка того, что встречается чаще всего:

ЗаписьЗначит
str, int, float, boolстрока, целое, дробное, да/нет
list[str]список строк
dict[str, int]словарь: ключи-строки, значения-числа
str | Noneстрока или ничего
-> Noneфункция ничего не возвращает

Запись str | None особенно полезна при чтении: она прямо предупреждает, что результат надо проверять.

Как сказать ИИ

Запрос
Какие структуры данных используются в этом файле
и что в каждой лежит? Коротко, по строке на переменную.

Полезный вопрос, когда данные приходят извне:

Запрос
Покажи пример данных, которые придут в эту функцию.
Один удачный случай и один с недостающими полями.

Когда нужно понять, не изменится ли что-то неожиданно:

Запрос
Эта функция меняет переданный ей список или создаёт новый?

На что смотреть

.get() там, где нужна ошибка. Если поле обязано быть, его отсутствие — это ошибка, а не повод подставить None. Молчаливый None всплывёт позже и дальше от причины.

Изменение того, что передали. Функция получила список и добавила в него элемент — снаружи список тоже изменился. Если это не задумано, будут трудные баги.

Словарь вместо понятной структуры. data["result"]["items"][0]["price"] — признак того, что данные никто не разобрал. Каждое звено здесь может упасть, и трейсбек покажет только последнее.

Аннотации не соответствуют коду. -> str, а функция иногда возвращает None. Аннотации ничего не проверяют, поэтому расходиться с кодом могут незаметно.

Шпаргалка

  • Четыре структуры: словарь · список · кортеж · множество.

  • Фигурные скобки с двоеточиями — словарь, без — множество.

  • x["k"] падает при отсутствии ключа, x.get("k") возвращает None.

  • Списки и словари передаются по ссылке: изменил один — изменились оба.

  • str | None в аннотации — результат надо проверять.

  • Аннотации ничего не проверяют, они для читателя.