В питоне очень много полезного и интересного синтаксического сахара. Настолько много, что у неподготовленных пользователей может случиться сахарный диабет. Здесь вы увидите несколько уникального для питона синтаксического сахара, его примеры правильного и неправильного применения.
Разделители разрядов в числах
Длинные захардкоженные числа очень плохо воспринимаются на глаз.
На письме мы привыкли ставить между разрядами разделители (в России, например, принято писать пробелы, а в Америке - запятые). В коде тоже можно это делать.
Отличать длинные целые числа помогают знаки _
, вставленные между разрядами.
Так, 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_prime = False
for i in range(2, isqrt(n)):
if n % i == 0:
is_prime = True
break
if is_prime:
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 default
Ellipsis - не замена 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
и самое главное - здравый смысл. Не надо пихать сахар там, где он хотя-бы визуально мешает и тем более там, где он вредит.
Автор: AKTOO