Хочешь писать более лаконичный и читаемый код а также умещать как можно больше смысла в одно выражение? Считаешь, что лучше один раз прочитать об уловках Python, чем провести остаток своих дней за чтением ненужной документации?
Тогда ты обратился по адресу. Мы начнем с маленьких уловок, которые ты уже мог где-то видеть, если работал с Python. Но я обещаю, что ближе к концу статьи будет больше безумных вещей.
1. Правдивость различных объектов
В отличие от некоторых языков программирования, в Python объект считается False
, только если он пуст. Это значит, что не нужно проверять длину строки, кортежа или словаря – достаточно проверить его как логическое выражение.
Кроме того, разумеется, 0 – тоже
False
, а остальные числа –True
.
Например, следующие выражения эквивалентны:
string = 'Test' # True пример # string = '' # False пример if len(string) > 0: print('Объект string не пуст') if len(string): # Здесь 0 преобразовывается к False print('Объект string не пуст') if string != '': print('Объект string не пуст') if string: # Здесь пустая строка преобразовывается к False print('Объект string не пуст')
В данном случае string
– это строка, но здесь мог оказаться другой тип (с соответствующими изменениями условий блока if
).
Наиболее элегантным и соответствующим «pythonic» стилю вариантом будет именно
if string: ...
2. Проверка на вхождение подстроки
Это маленькая, довольно очевидная подсказка, но даже я узнал о ней не сразу. Должно быть очевидно, что можно проверить, содержится ли нужный элемент в кортеже, списке, словаре, с помощью конструкции if item in list: ...
. Но это может сработать и для строк!
Можно конечно написать и так:
string = 'Hi there' # True пример # string = 'Hello' # False пример if string.find('Hi') != -1: print('Success!')
Но гораздо понятнее и красивее будет так:
string = 'Hi there' # True example # string = 'Hello' # False example if 'Hi' in string: print('Success!')
3. Лямбда-функции
Иногда нужно передать функцию в качестве аргумента или сделать короткую, но сложную операцию несколько раз. Можно определить функцию обычным способом, а можно использовать лямбда-функцию – маленькую функцию, возвращающую результат одного выражения.
Следующие два определения полностью идентичны:
def add(a,b): return a + b add = lambda a, b: a + b
Преимущество лямбда-функции заключается в том, что она является выражением и может быть использована внутри другого выражения. Например, можно удобно использовать их в функции map
, которая вызывает функцию-выражение для каждого элемента списка и возвращает список результатов.
squares = map(lambda x: x * x, [1, 2, 3, 4, 5]) # squares = [1, 4, 9, 16, 25]
Без лямбда-функций нам конечно же пришлось бы определить эту функцию отдельно. По сути мы сэкономили одну строчку кода и одно имя переменной.
4. Списковые включения
Возможно где-то до этого ты уже мог слышать понятие «list comprehensions». Это такой способ уместить цикл for
, блок if
и присваивание в одну строку.
Начнем с простейшего примера. Допустим, нам снова надо возвести в квадрат все элементы списка.
Если ты совсем недавно начал писать на Python, то скорее всего напишешь так:
numbers = [1, 2, 3, 4, 5] squares = [] for number in numbers: squares.append(number * number) # squares = [1, 4, 9, 16, 25]
Можно воспользоваться предыдущим примером с функцией map
:
numbers = [1, 2, 3, 4, 5] squares = map(lambda x: x * x, numbers) # squares = [1, 4, 9, 16, 25]
Да, определенно этот код короче предыдущего, но всё еще некрасив. С первого взгляда сложно сказать, что делает функция map
(она принимает в качестве аргументов функцию и список и применяет функцию к каждому элементу списка). К тому же мы вынуждены определять функцию, это выглядит довольно беспорядочно.
Но, оказывается, можно писать проще и понятнее:
numbers = [1, 2, 3, 4, 5] squares = [number * number for number in numbers] # squares = [1, 4, 9, 16, 25]
Вторая строчка здесь читается практически как исполняемый псевдокод. Чем это хорошо? Даже без знаний Python человек без проблем определит что делает этот код.
5. Фильтрация списка
А что, если нас интересует фильтрация списка? Например, требуется удалить все четные элементы.
Новичок в программировании на Python напишет так:
numbers = [1, 2, 3, 4, 5] odds = [] for number in numbers: if number % 2: odds.append(number) # odds = [1, 3, 5]
Очень просто, не так ли? Но код занимает 5 строк, содержит два уровня отступов и при этом делает совершенно тривиальную вещь.
Можно уменьшить размер кода с помощью функции filter
:
numbers = [1, 2, 3, 4, 5] odds = filter(lambda x: x % 2, numbers) # odds = [1, 3, 5]
Аналогично функции map
, о которой мы говорили выше, filter
сокращает код, но выглядит довольно уродливо.
Как работает функция filter
? Как и map
, filter
получает функцию и список. Если функция от элемента возвращает True
, элемент включается в результирующий список.
Ну и а качестве вишенки на торте, мы можем сделать это через списковые включения:
numbers = [1, 2, 3, 4, 5] odds = [number for number in numbers if number % 2] # odds = [1,3,5]
6. Одновременное использование map и filter
Теперь мы можем использовать всю силу списковых включений (их еще кстати называют генераторами списков). Теперь попробуем использовать отображение и фильтрацию списка одновременно. Другими словами, я хочу увидеть нечетные квадраты элементов списка.
Неопытный программист на Python напишет так:
numbers = [1, 2, 3, 4, 5] odd_squares = [] for number in numbers: if number % 4: odd_squares.append(number * number) # odd_squares = [1, 9, 25]
Увы, код начал растягиваться вправо. Может, получится упростить его?
Попробуем использовать map
и filter
:
numbers = [1, 2, 3, 4, 5] odd_squares = map(lambda x: x * x, filter(lambda x: x % 2, numbers)) # odd_squares = [1, 9, 25]
Раньше map
и filter
было трудно читать, а теперь вообще невозможно. Очевидно, это не лучшая идея.
Попробуем спасти ситуацию с помощью генератора списков:
numbers = [1, 2, 3, 4, 5] odd_squares = [number * number for number in numbers if number % 2] # odd_squares = [1, 9, 25]
Да, получилось немного длиннее, чем предыдущие примеры со списковым включением, но, мне кажется, вполне читабельно. Определенно лучше, чем цикл for
.
Кстати, генератор списков сначала фильтрует, а затем уже отображает. Если нужно наоборот, получится сложнее. Придется использовать либо вложенные генерации, либо
map
иfilter
, либо обычный циклfor
, в зависимости от того, что проще.
7. Моржовый оператор
Новый способ присваивания выражения (:=
), или оператор «морж», был самой обсуждаемой функцией, представленной в последней версии Python. Новое дополнение к языку было предложено в PEP 572.
Сразу посмотрим, как это выглядит в коде:
(x:= 5) print(x) # Output: 5
Присваивания с помощью моржа требуется делать в круглых скобках, и перед знаком равно пишется двоеточие. Внутри скобок либо создается новая переменная, либо происходит присвоение значения уже существующий переменной.
Пойдем дальше и рассмотрим пример, в котором раскрывается весь потенциал моржового оператора:
# пусть a – контейнер или последовательность if (n:= len(a)) > 10: print(f'Слишком много элементов, а именно {n}')
То есть мы только что прямо в проверке условия объявили новую переменную n и далее использовали ее вместо того, чтобы вычислять значение заново. Для сравнения посмотрим, как мы это могли сделать раньше:
n = len(a) if n > 10: print(f'Слишком много элементов, а именно {n}') # или if len(a) > 10: print(f'Слишком много элементов, а именно {len(a)}')
В первом случае появляется лишняя строка кода, в которой объявляется ненужная переменная. Во втором случае дважды вызвана одна и та же функция, а это потеря в производительности.
Ну и еще пара примеров на закуску:
- Повторно используем значение в списке:
foo = [y:= f(x), y * 2, y * 3] # вместо foo = [f(x), f(x) * 2, f(x) * 3]
- Повторное использование значения из условия в генераторных выражениях:
[y for x in data if (y:=f(x))] # вместо result = [] for x in data: result = f(x) if result: results.append(result)
- Чтение данных из файла внутри циклов while:
while (block := f.read(256)) != '': process(block) # вместо while True: stuff() if fail_condition: break
Надеюсь тебе понравилась эта статья. Если будет много просмотров, мы подготовим и выпустим вторую часть топ фишек в Python.