Идиомы, которые ИИ пишет постоянно
шесть конструкций, после которых Python читается свободно
Что это
У Python есть набор оборотов, которые ИИ использует почти в каждом файле. Они короче обычных циклов и считаются «питоновскими» — поэтому модель тянется к ним, даже когда можно проще.
Выучить их надо один раз. После этого сгенерированный Python читается почти без спотыканий.
Как выглядит в коде
from dataclasses import dataclass
from pathlib import Path
@dataclass
class City:
name: str
temp: float | None = None
cities = [City("Oslo", 4.0), City("Tromsø"), City("Bergen", 7.5)]
warm = [c.name for c in cities if c.temp is not None and c.temp > 5]
print("тёплые:", warm)
for i, c in enumerate(cities, start=1):
print(f"{i}. {c.name}: {c.temp if c.temp is not None else 'нет данных'}")
codes = ["OSL", "TOS", "BGO"]
print(dict(zip(codes, (c.name for c in cities))))
p = Path("/tmp/idioms.py")
print(p.name, "|", p.suffix, "|", p.exists())Вывод:
тёплые: ['Bergen']
1. Oslo: 4.0
2. Tromsø: нет данных
3. Bergen: 7.5
{'OSL': 'Oslo', 'TOS': 'Tromsø', 'BGO': 'Bergen'}
idioms.py | .py | True1. Списковое включение
warm = [c.name for c in cities if c.temp is not None and c.temp > 5]Читается слева направо как предложение: собрать c.name для каждого c из
cities, где температура выше пяти.
Форм три, и различаются они только скобками:
| Запись | Что получается |
|---|---|
[x for x in ...] | список |
{k: v for ...} | словарь |
(x for x in ...) | генератор — считается по мере обхода |
Третья форма экономит память на больших данных, но её нельзя пройти дважды: после первого обхода генератор пуст.
2. @dataclass
@dataclass
class City:
name: str
temp: float | None = NoneИзбавляет от __init__ и даёт читаемый вывод. Когда ИИ предлагает класс
только для хранения данных — это должен быть @dataclass (статья 2.7).
3. enumerate
for i, c in enumerate(cities, start=1):Индекс и элемент сразу. Если видишь for i in range(len(x)) — это тот же
цикл, написанный неудобно.
4. zip
dict(zip(codes, names))Идёт по двум спискам параллельно. Важная деталь: zip молча останавливается
на том, который короче. Если списки разной длины, часть данных пропадёт без
единой ошибки.
5. pathlib
p = Path("/tmp/idioms.py")
p.name, p.suffix, p.exists()
p.parent / "other.py"Современная замена os.path. Косая черта вместо os.path.join — не
опечатка, а перегруженный оператор.
6. f-строки
f"{c.name}: {c.temp if c.temp is not None else 'нет данных'}"Внутри фигурных скобок — обычное выражение. Полезные дополнения:
| Запись | Что делает |
|---|---|
f"{x!r}" | показывает кавычки и невидимое (статья 4.5) |
f"{x:.2f}" | два знака после запятой |
f"{x=}" | печатает x=значение — удобно при отладке |
Как сказать ИИ
Перепиши это списковое включение обычным циклом.
Поведение не меняй.Этот класс только хранит данные. Сделай из него dataclass.Обратный приём — ограничить питоновскую изощрённость заранее:
Пиши просто: обычные циклы вместо вложенных включений,
без walrus-оператора и однострочников. Код должны читать
не только ты и я.На что смотреть
if x: там, где ноль — нормальное значение. if c.temp: отбросит и
None, и 0.0: мороз в ноль градусов станет «нет данных». Пустая строка,
0 и [] тоже ложны. Если проверяешь «есть ли значение» — пиши
is not None. ИИ путает эти две проверки постоянно.
Вложенные включения. [y for x in data for y in x if y] — формально
питоновски, фактически нечитаемо. Два уровня — предел.
zip на списках разной длины. Молча обрежет по короткому.
Генератор вместо списка там, где нужен повторный обход. Второй проход даст пустоту, без ошибки.
Walrus-оператор :=. if (n := len(data)) > 10: — экономит строку,
усложняет чтение. ИИ использует его чаще, чем нужно.
dict вместо dataclass. data["temp"] ломается опечаткой в ключе,
city.temp — нет. Если структура известна заранее, ей место в классе.
Шпаргалка
Включения:
[...]список,{k: v ...}словарь,(...)генератор.Генератор проходится один раз.
@dataclass— класс только для данных.enumerateвместоrange(len(...)),zipдля двух списков.zipмолча режет по короткому списку.f"{x!r}"иf"{x=}"— для отладки;pathlibвместоos.path.