В питоне очень много полезного и интересного синтаксического сахара. Настолько много, что у неподготовленных пользователей может случиться сахарный диабет. Здесь вы увидите несколько уникального для питона синтаксического сахара, его примеры правильного и неправильного применения.
Разделители разрядов в числах
Длинные захардкоженные числа очень плохо воспринимаются на глаз.
На письме мы привыкли ставить между разрядами разделители (в России, например, принято писать пробелы, а в Америке - запятые). В коде тоже можно это делать.
Отличать длинные целые числа помогают знаки _, вставленные между разрядами.
Так, 10_000_000 и 8_800_555_35_35 являются обыкновенными целыми числами.
List comprehension
Прикольный способ записывать списки, словари, множества и генераторы в одну строчку
x = [выражение for i in итератор]Этот код полностью эквивалентен следующему
def generator():
for i in итератор:
yield выражение
x = list(generator())Так же можно добавить условие в это выражение. Тогда генератор будет возвращать только те значения, которые удовлетворяют условию
x = [выражение for i in итератор if условие]Например, здесь все нечетные числа до 10 возводятся в квадрат
x = [i ** 2 for i in range(10) if i % 2 == 1]Хотя, конкретно эту задачу можно решить вдвое быстрее, изменив шаг range
x = [i ** 2 for i in range(1, 10, 2)]С таким же успехом создаются словари и множества. Надо просто поменять скобки
a_dict = {i: ord(i) for i in "abcdefghijklmnopqrstuvwxyz"}
a_set = {isqrt(i) for i in range(100)}Если поставить круглые скобки, то создастся обычный генератор. Кортежи и замороженные множества (неизмемяемые объекты) так создавать нельзя.
Вниманию оптимизаторов
Волшебная _, указаная в качестве переменной итерирования не экономит память! Выражения [0 for _ in l] и [0 for i in l] абсолютно одинаково ждут эту память, хотя разница почти никакая.
JIT пугать выражением _ не н��до, хотя бы потому, что в cpython JIT нет, а pypy воспримет _ как обычную переменную.
Хотите что-то оптимизировать? Оптимизируйте время, написав [0] * len(l).
Чем выше версия питона, тем лучше интерпретатор обрабатывает итераторы и тем меньше разница между обычным циклом и инициализацией через генераторы.
Распаковка итераторов
Допустим, у нас есть кортеж x = (1, 2, 3, 42, 999, 7). Я хочу распихать его значения по переменным, а именно: первое в a, второе в b, последнее в c, а все остальное в other.
Вместо громоздкого кода
a = x[0]
b = x[1]
c = x[-1]
other = x[2:-1]Можно написать просто
a, b, *other, c = xБолее того, можно распаковывать вложенные кортежи абсолютно так же
y = (1, 2, 3, (10, 20, 30, 40, 50), (33, 77), 19, 29)
a, b, c, (d, e, *f), (g, h), *i = yВместо кортежей могли бы быть любые итерируемые объекты
Такая распаковка работает везде: в циклах, списковых выражениях и т.д.
persons = [("Alice", 2), ("Bob", 9), ("Charlie", 11)]
for name, rank in persons:
print(f"{name} -- {rank}")Else в циклах
Обычно `else` используется для условного оператора, но в питоне для него есть дополнительная функциональность. Указав `else` после цикла, можно задать блок кода, который выполнится только если цикл завершился без `break`.
Например,
for i in range(2, isqrt(n)):
if n % i == 0: break
else:
print(f"{n} - простое число")Эта классная конструкция делает код чище и избавляет программиста от объявления лишних флагов и танцев с бубнами. Сравните с
is_compose = False
for i in range(2, isqrt(n)):
if n % i == 0:
is_compose = True
break
if is_compose:
print(f"{n} - составное число")
else:
print(f"{n} - простое число")Объект Ellipsis
В питоне есть встроенная константа Ellipsis, имеющая псевдоним (литерал) ...
Это просто специальное значение, отличное от None, True/False и других констант.
Используется многоточие для упрощения жизни и для замены особых литералов.
Аннотации типов
Пусть нам надо указать тип переменной x - кортеж с целыми числами
x: tuple[int] = (1,)Это объявление не то же самое, что list[int], ведь list[int] указывает тип для всех элементов списка, а tuple[int] - только тип первого элемента (и их количество - 1).
Для объявления кортежа с двумя элементами придется писать типы
x: tuple[int, int] = (1, 2)А если длина кортежа неизвестна и может быть любой? Многоточие в помощь!
x: tuple[int, ...] = (1, 2, 3, 42, 999, 7)Альтернатива None
Бывают ситуации, когда нужно запихать в функцию какое-то особое значение. Это не может быть None, ведь он используется как обычное значение. Здесь пригодится ...
Например, функция, возвращающая первое значение итератора
def first(iterable, default=...):
"""Возвращает первый элемент iterable"""
for item in iterable:
return item
if default is ...:
raise ValueError('first() вызвано с пустым iterable,'
'а значение по умолчанию не установленно')
return defaultEllipsis - не замена pass
Теоритически можно написать ... вместо pass, но это будет семантически неверно. Код
def function():
...полностью равносилен
def function():
42Нет никакого смысла помещать значащую константу в тело функции, цикла, условия и т.д., чтобы показать отсутствие действия. Логичнее и правильнее использовать pass.
pass означает, что кода нет. ... же подразумевает, что код есть, но я его просто не пишу. Поэтому единственная ситуация, где уместно использование ... в таком контексте - файлы .pyd. Ведь это объявления (прото)типов функций, классов и т.д., где код действительно есть, но его не видно (он ведь в другом файле).
Замена индекса
В обычном питоне такого функционала нет, но его добавляют сторонние библиотеки (например, numpy).
Идея основывается на том, что внутри срезов (объектов slice, используемых в индексации a[i:j]) могут стоять любые хешируемые объекты, в том числе и кортежи.
Пусть a - сильно многомерный массив (пусть будет 7-мерный). Вместо громоздкого a[0, :, :, :, :, :, 0] можно написать просто a[0, ..., 0].
Моржовый оператор
Специальная синтаксическая конструкция :=, которая позволяет присвоить значение переменной и сразу вернуть его. Используется, чтобы избежать громоздких выражений.
Например, проверка на соответствие регулярному выражению
if m := re.match(r"(.*)@([a-z\.]+)", email):
print(f"Почтовый ящик {m[1]} на сервисе {m[2]}")Представим, что мы делаем свою «командную строку». Вместо дублирования кода
command = input("$ ")
while command != 'exit':
...
command = input("$ ")можно написать просто
while (command := input("$ ")) != 'exit':
... И еще оно классное применение моржа -- фиксация свидетелей any и контрпримеров в all. Функция any итерирует до первого истинного значения, а all - до первого ложного.
Перезаписывая какую-то переменную, мы сможем зафиксировать первое значение, для которого any стало истинным и all стало ложным.
Вот есть список
x = [1, 2, 3, 4, 10, 12, 7, 8]И я проверяю, есть ли хотя бы одно число, большее 10
if any((a := i) > 10 for i in x):
print(f'Есть хотя бы одно число, большее 10. Это {a}!')И, соответственно, все ли числа меньше 10
if all((a := i) < 10 for i in x):
print(f'Все числа меньше 10')
else:
print(f'Не все числа меньше 10. Например, {a}')Не забывайте про DRY, import this и самое главное - здравый смысл. Не надо пихать сахар там, где он хотя-бы визуально мешает и тем более там, где он вредит.