March 6, 2020

7 фишек Python максимально улучшающие твой код

Хочешь писать более лаконичный и читаемый код а также умещать как можно больше смысла в одно выражение? Считаешь, что лучше один раз прочитать об уловках 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.


Статья подготовлена образовательной организацией Python Academy