Course
В последние годы спрос на навыки программирования на Python заметно вырос. Чтобы помочь вам развивать навыки, мы собрали 30 классных трюков Python, которые вы можете использовать, чтобы сделать свой код лучше. Пытайтесь осваивать по одному в день в течение 30 дней и загляните в нашу публикацию с лучшими практиками Python, чтобы убедиться, что ваш код соответствует высшему классу.
Если ваши навыки Python пока не на должном уровне, прокачайте их с нашим треком навыков по Python.
Трюки с последовательностями и структурами данных
#1 Срезы
a = "Hello World!"
print(a[::-1])
"""
!dlroW olleH
"""
Срезы в Python опираются на индексацию и позволяют получать подмножество элементов последовательности. Индекс — это позиция элемента в последовательности. Если тип последовательности изменяемый, срезы можно использовать для извлечения и изменения данных.
Примечание: Срезы можно применять и к неизменяемым последовательностям, но попытка изменить срез вызовет TypeError.
Формат среза: sequence[start:stop:step]. Если параметры start, stop и step не заданы, используются значения по умолчанию. По умолчанию:
- «start» равен 0
- «stop» равен длине последовательности
- «step» равен 1, если он не указан.
При записи sequence[start:stop] возвращаются элементы от начального индекса до stop - 1 (индекс stop не включается).
Можно использовать и отрицательные индексы, например, для разворота последовательности. В списке из 4 элементов индекс 0 также равен -4, а последний индекс равен -1. В примере выше это знание применено к параметру step, благодаря чему строка напечатана задом наперёд — от конца последовательности к индексу 0.
#2 Обмен значений на месте / одновременное присваивание
a = 10
b = 5
print(f"First: {a, b}")
"""
First: (10, 5)
"""
a, b = b, a + 2
print(f"Second: {a, b}")
"""
Second: (5, 12)
"""
Если вы сначала решили, что значение b будет 7, а не 12, вы попались в ловушку обмена значений на месте.
В Python можно распаковывать итерируемые объекты в переменные одним присваиванием с помощью автоматической распаковки. Например:
a, b, c = [1, 2, 3]
print(a)
print(b)
print(c)
"""
1
2
3
"""
Можно также собрать несколько значений в одну переменную с помощью * – этот трюк называется упаковкой. Ниже пример упаковки.
a, *b = 1, 2, 3
print(a, b)
"""
1 [2, 3]
"""
Комбинация упаковки и распаковки даёт технику, известную как одновременное присваивание. С её помощью можно присвоить серию значений серии переменных.
#3 Списки против кортежей
import sys
a = [1, 2, 3, 4, 5]
b = (1, 2, 3, 4, 5)
print(f"List size: {sys.getsizeof(a)} bytes")
print(f"Tuple size: {sys.getsizeof(b)} bytes")
"""
List size: 52 bytes
Tuple size: 40 bytes
"""
Большинство программистов на Python знакомы со списками. А вот с кортежами знакомы не все. Оба типа итерируемые, поддерживают индексацию и позволяют хранить неоднородные типы данных. Но есть ситуации, когда кортеж предпочтительнее списка.
Во-первых, списки изменяемы, то есть мы можем их менять как захотим:
a = [1,2,3,4,5]
a[2] = 8
print(a)
"""
[1,2,8,4,5]
"""
Кортежи, напротив, неизменяемы, и попытка их изменить приведёт к TypeError.
По этой причине кортежи более экономны по памяти: Python может выделить ровно столько памяти, сколько требуется. В случае списков нужно закладывать дополнительную память на возможное расширение — это динамическое распределение памяти.
Итог: если данные не должны меняться, по соображениям памяти стоит предпочесть кортеж списку. Кортежи также быстрее списков.
Узнайте больше о структурах данных в Python в этом руководстве.
#4 Генераторы
a = [x * 2 for x in range(10)]
b = (x * 2 for x in range(10))
print(a)
print(b)
"""
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
<generator object <genexpr> at 0x7f61f8808b50>
"""
Генераторы списков — питоничный способ создавать список из другой итерируемой последовательности, он намного быстрее обычного цикла for. Но что будет, если вы случайно смените скобки с [] на ()? Вы получите объект-генератор.
В Python круглые скобки с логикой генератора списка создают так называемый генератор. Генераторы — особый вид итерируемых объектов. В отличие от списков, они не хранят элементы; они хранят инструкции по их поочерёдной генерации и текущее состояние итерации.
Каждый элемент создаётся только по запросу — это называется ленивыми вычислениями. Главное преимущество такого подхода — меньшее потребление памяти, потому что вся последовательность не строится сразу.
#5 Алиасинг
a = [1, 2, 3, 4 ,5]
b = a
# Change the 4th index in b
b[4] = 7
print(id(a))
print(id(b))
print(a) # Remember we did not explicitly make changes to a.
"""
15136008
15136008
[1, 2, 3, 4, 7]
"""
Python — объектно-ориентированный язык: всё является объектом. Присваивание объекта идентификатору создаёт ссылку на объект.
Когда мы присваиваем один идентификатор другому, получаем два идентификатора, ссылающихся на один и тот же объект. Это называется алиасингом. Изменения через один алиас затронут другой. Иногда это поведение желаемо, но часто застает врасплох.
Один из способов избежать этого — не использовать алиасинг для изменяемых объектов. Другой — создавать клон исходного объекта вместо ссылки.
Самый простой способ клонирования — воспользоваться срезом:
b = a[:]
Это создаст новую ссылку на объект списка в идентификаторе b.
Есть и другие варианты: например, вызвать list(a) при присваивании данным другого идентификатора или использовать метод copy().
#6 Оператор not
a = []
print(not a)
"""
True
"""
Самый простой способ проверить, пустая ли структура данных, — использовать оператор not. Встроенный логический оператор not возвращает True, если выражение ложно, иначе возвращает False – он инвертирует истинность булевых выражений и объектов.
Его можно встретить и в условии if:
if not a:
# do something...
Когда a равно True, оператор not вернёт False, и наоборот.
Сначала это может сбивать с толку — попробуйте сами.
Трюки со строками и выводом
#7 F-строки
first_name = "John"
age = 19
print(f"Hi, I'm {first_name} and I'm {age} years old!")
"""
Hi, I'm John and I'm 19 years old!
"""
Иногда нам нужно отформатировать строку; в Python 3.6 появился классный инструмент — f-строки, который сильно упрощает процесс. Чтобы по-настоящему оценить нововведение, полезно знать, как форматировали строки раньше.
Вот как это делалось раньше:
first_name = "John"
age = 19
print("Hi, I'm {} and I'm {} years old!".format(first_name, age))
"""
Hi, I'm John and I'm 19 years old!
"""
По сути, новый способ быстрее, читабельнее, короче и менее подвержен ошибкам.
Ещё одно применение f-строк — печать имени идентификатора вместе со значением. Это появилось в Python 3.8.
x = 10
y = 20
print(f"{x = }, {y = }")
"""
x = 10, y = 20
"""
Подробнее в руководстве Форматирование f-строк в Python.
#8 Параметр end у функции print()
languages = ["english", "french", "spanish", "german", "twi"]
print(' '.join(languages))
"""
english french spanish german twi
"""
Часто мы используем print без указания необязательных параметров, поэтому многие Python-разработчики не знают, что выводом можно управлять.
Один из параметров — end. Он определяет, что выводить в конце вызова print.
По умолчанию end равен "\n", что означает переход на новую строку. В коде выше мы заменили его на пробел, поэтому все элементы списка были напечатаны в одной строке.
#9 Добавление в кортеж
a = (1, 2, [1, 2, 3])
a[2].append(4)
print(a)
"""
(1, 2, [1, 2, 3, 4])
"""
Мы уже знаем, что кортежи неизменяемы – см. трюк Python #3 Списки против кортежей. Попытка изменить кортеж вызовет TypeError. Но если рассматривать кортеж как последовательность имён, связанных с объектами, где сами связи неизменяемы, картина меняется.
Первые два элемента нашего кортежа — целые числа, они неизменяемы. Последний элемент — список, изменяемый объект Python.
Если считать список просто ещё одним именем в последовательности, привязанным к объекту, связь менять нельзя, но сам объект списка можно изменить изнутри кортежа.
Рекомендовали бы мы делать так на практике? Вряд ли, но знать об этом полезно!
#10 Объединение словарей
a = {"a": 1, "b": 2}
b = {"c": 3, "d": 4}
a_and_b = a | b
print(a_and_b)
"""
{"a": 1, "b": 2, "c": 3, "d": 4}
"""
В Python 3.9+ можно объединять словари с помощью | (побитовое ИЛИ). О самом трюке больше и не скажешь — просто гораздо более читабельно!
Трюки стиля кода и синтаксиса
#11 Тернарный оператор / условные выражения
condition = True
name = "John" if condition else "Doe"
print(name)
"""
John
"""
В коде выше показан тернарный оператор (его ещё называют условным выражением). Мы используем его, чтобы вычислить значение в зависимости от того, является ли условие True или False.
То же можно было написать так:
condition = True
if condition:
name = "John"
else:
name = "Doe"
print(name)
"""
John
"""
Оба варианта дают один результат, но тернарное выражение позволяет писать короче и яснее. Это то, что Python-разработчики называют более «питоничным» стилем.
#12 Удаление дубликатов из списков
a = [1, 1, 2, 3, 4, 5, 5, 5, 6, 7, 2, 2]
print(list(set(a)))
"""
[1, 2, 3, 4, 5, 6, 7]
"""
Простейший способ убрать дубликаты из списка — преобразовать его в множество (а при необходимости обратно в список).
По изменяемости множества и списки похожи: мы можем добавлять и удалять элементы. Но различий тоже много.
Списки упорядочены, имеют нумерацию с нуля и изменяемы. Множества неупорядочены и не индексируются. Элементы множества должны быть неизменяемых типов, хотя само множество изменяемо — попытка получить элемент по индексу или изменить его вызовет ошибку.
Ещё одно отличие: множества не содержат дубликатов. Именно это и помогло нам убрать повторяющиеся элементы.
#13 Одинарное подчёркивание
>>> print(_)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
>>> 1 + 2
3
>>> print(_)
3
Подчёркивание (_) — корректный идентификатор в Python, его можно использовать для ссылки на объект. Но у него есть и другое назначение: хранить результат последнего вычисления.
В документации сказано: «интерактивный интерпретатор делает результат последней оценки доступным в переменной _ (она хранится в модуле builtins рядом со встроенными функциями вроде print)».
Поскольку до первого вызова мы не присваивали подчёркиванию объект, получили ошибку. После вычисления 1 + 2 интерпретатор сохранил результат в идентификаторе _ .
#14 Подчёркивание для игнорирования значений
for _ in range(100):
print("The index doesn't matter")
"""
The index doesn't matter
The index doesn't matter
...
"""
В трюке #13 мы узнали, что интерпретатор помещает результат последней операции в идентификатор _. Но это не единственный сценарий.
Подчёркивание также используют для обозначения объектов, которые нам не важны и не будут использоваться дальше. Это важно, потому что применение обычного идентификатора вместо _ может вызвать ошибку F841 при линтинге: «локальная переменная присвоена, но не используется», что считается плохой практикой.
#15 Заключительное подчёркивание
list_ = [0, 1, 2, 3, 4]
global_ = "Hi there"
Продолжая тему подчёркиваний: ещё одна его роль — избегать конфликтов с ключевыми словами Python.
PEP 8 рекомендует добавлять завершающее подчёркивание (_) «по соглашению, чтобы избежать конфликтов с ключевыми словами Python». Также сказано, что «обычно лучше добавить одно подчёркивание в конце, чем использовать аббревиатуру или искажение написания. Так, list_ лучше, чем lst.»
#16 Начальные подчёркивания
class Example:
def __init__(self):
self._internal = 2
self.external = 20
Опытные Python-разработчики часто ставят подчёркивание перед именем идентификатора или метода — и не зря.
Подчёркивание перед именем даёт понять: эта переменная или метод предназначены для внутреннего использования. По сути, это предупреждение для других программистов, зафиксированное в PEP 8, но не принудительно применяемое Python. То есть начальные подчёркивания — слабый индикатор.
В отличие от Java, в Python нет жёсткого разграничения private/public. Иными словами, смысл есть лишь потому, что сообщество договорилось так его трактовать. Их наличие не влияет на поведение программы.
#17 Подчёркивание как визуальный разделитель
Последний совет про подчёркивания. Мы рассмотрели три варианта применения, а подробнее читайте в руководстве о роли подчёркивания (_) в Python.
number = 1_500_000
print(number)
"""
15000000
"""
Ещё один способ использовать подчёркивание — как визуальный разделитель групп цифр в целых, числаx с плавающей точкой и комплексных литералах – это появилось в Python 3.6.
Идея — повысить читаемость длинных литералов или тех, чьё значение явно делится на логические части – подробнее в PEP 515.
Трюки стиля кода и синтаксиса
#18 __name__ == «__main__»
if __name__ == "__main__":
print("Read on to understand what is going on when you do this.")
"""
print("Read on to understand what is going on when you do this.")
"""
С большой вероятностью вы видели этот синтаксис: Python использует специальное имя "__main__" и присваивает его идентификатору __name__, если выполняемый файл является основным программным модулем.
Если мы импортируем модуль с изображённым кодом в другой модуль (файл Python) и запустим тот файл, выражение станет ложным. При импорте идентификатор __name__ устанавливается в имя модуля (файла).
#19 Метод setdefault
import pprint
text = "It's the first of April. It's still cold in the UK. But I'm going to the museum so it should be a wonderful day"
counts = {}
for word in text.split():
counts.setdefault(word, 0)
counts[word] += 1
pprint.pprint(counts)
"""
{'April.': 1,
'But': 1,
"I'm": 1,
"It's": 2,
'UK.': 1,
'a': 1,
'be': 1,
'cold': 1,
'day': 1,
'first': 1,
'going': 1,
'in': 1,
'it': 1,
'museum': 1,
'of': 1,
'should': 1,
'so': 1,
'still': 1,
'the': 3,
'to': 1,
'wonderful': 1}
"""
Иногда нужно задать начальное значение для разных ключей словаря. Например, когда вы считаете частоты слов в корпусе. Обычный подход таков:
- Проверить, существует ли ключ в словаре
- Если да, увеличить значение на 1
- Если нет, добавить ключ со значением 1
В коде это выглядит так:
counts = {}
for word in text.split():
if word in counts:
counts[word] += 1
else:
counts[word] = 1
Более лаконично это делается методом setdefault() у словаря.
Первый аргумент — ключ, который мы проверяем. Второй — значение, которое будет установлено, если ключа ещё нет; если ключ уже существует, метод вернёт текущее значение, не меняя его.
Трюки структуры программы
#20 Соответствие регэкспу
import re
number = re.compile(r"(0)?(\+44)?\d{10}")
num_1 = number.search("My number is +447999999999")
num_2 = number.search("My number is 07999999999")
print(num_1.group())
print(num_2.group())
"""
'+447999999999'
'07999999999'
"""
Регулярные выражения позволяют задавать шаблон текста для поиска. Большинство знает поиск CTRL + F (Windows), но что, если вы не знаете точное вхождение? Ищите по шаблонам.
Например, британские номера следуют схеме: ноль в начале и десять цифр или +44 вместо нуля и десять цифр – второй вариант означает международный формат.
Регулярные выражения экономят массу времени. В противном случае код правил для наших случаев занял бы 10+ строк.
Разобраться с регэкспами полезно даже тем, кто не пишет код: большинство современных редакторов и процессоров поддерживают их в поиске и замене.
#21 Альтернация (pipe) в регэкспах
import re
heros = re.compile(r"Super(man|woman|human)")
h1 = heros.search("This will find Superman")
h2 = heros.search("This will find Superwoman")
h3 = heros.search("This will find Superhuman")
print(h1.group())
print(h2.group())
print(h3.group())
"""
Superman
Superwoman
Superhuman
"""
В регулярных выражениях есть специальный символ pipe (|), который позволяет сопоставлять одно из нескольких выражений. Он удобен при работе с похожими шаблонами.
Например, «Superman», «Superwoman» и «Superhuman» имеют общий префикс. Можно использовать pipe, чтобы сохранить повторяющуюся часть и варьировать окончание. Снова экономия времени.
Осторожно: если все варианты встречаются в одном тексте, будет возвращено первое совпадение, например, в строке «An example text containing Superwoman, Superman, Superhuman» вернётся Superwoman.
#22 Параметр sep у функции print()
day = "04"
month = "10"
year = "2022"
print(day, month, year)
print(day, month, year, sep = "")
print(day, month, year, sep = ".")
"""
04 10 2022
04/10/2022
04.10.2022
"""
Удивительно много разработчиков не знают всех возможностей функции print(). Если «Hello World» был вашей первой программой, то print() наверняка была одной из первых изученных встроенных функций. Мы используем её для вывода форматированных сообщений, но она умеет больше.
В примере показаны разные способы форматированного вывода. Параметр sep — необязательный аргумент, определяющий, как разделять объекты при передаче нескольких штук.
По умолчанию используется пробел, но мы изменили это поведение: в одном случае sep равен "", в другом — ".".
#23 Лямбда-функции
def square(num:int) -> int:
return num ** 2
print(f"Function call: {square(4)}")
"""
Function call: 16
"""
square_lambda = lambda x: x**2
print(f"Lambda function: {square_lambda(4)}")
"""
Lambda functional: 16
"""
Лямбда-функции поднимают вас на более продвинутый уровень – изучайте Intermediate Python в этом курсе. На первый взгляд они сложны, но на деле всё просто.
В примере мы использовали один аргумент, но можно передавать и несколько:
square = lambda a, b: a ** b
print(f"Lambda function: {square(4, 2)}")
"""
16
"""
Ключевое слово lambda позволяет создавать маленькие, ограниченные, анонимные функции в одну строку. Они ведут себя как обычные функции, объявленные через def, но без имени.
#24 Метод swapcase
string = "SoMe RaNDoM sTriNg"
print(string.swapcase())
"""
sOmE rAndOm StRInG
"""
Метод swapcase() применяется к строке и меняет регистр букв на противоположный одной строкой кода. Сценариев немного, но знать полезно.
#25 Метод isalnum
password = "ABCabc123"
print(password.isalnum())
"""
True
"""
Допустим, вы пишете программу, где пароль должен состоять из букв и цифр. Это можно проверить одной строкой, вызвав isalnum() у строки.
Метод проверяет, что все символы — буквы (A-Za-z) и цифры (0-9). Пробел или символ (!#%$&? и т. п.) вернёт False.
#26 Обработка исключений
def get_ration(x:int, y:int) -> int:
try:
ratio = x/y
except ZeroDivisionError:
y = y + 1
ratio = x/y
return ratio
print(get_ration(x=400, y=0))
"""
400.0
"""
Программы на Python завершают работу при возникновении ошибки.
Иногда этого не хочется, например, при взаимодействии с конечным пользователем. Было бы нехорошо, если бы код преждевременно завершился.
Существует несколько подходов к обработке исключений. Многие питонисты придерживаются принципа «проще попросить прощения, чем разрешения»: они предпочитают ловить возникшие ошибки, окружая код контекстом, способным обработать исключение, вместо того чтобы заранее страховаться от всех случаев.
Но это работает только тогда, когда есть механизм для исправления проблемы после её возникновения.
#27 Поиск различий в списках
list_1 = [1, 3, 5, 7, 8]
list_2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
solution_1 = list(set(list_2) - set(list_1))
solution_2 = list(set(list_1) ^ set(list_2))
solution_3 = list(set(list_1).symmetric_difference(set(list_2)))
print(f"Solution 1: {solution_1}")
print(f"Solution 2: {solution_2}")
print(f"Solution 3: {solution_3}")
"""
Solution 1: [9, 2, 4, 6]
Solution 2: [2, 4, 6, 9]
Solution 3: [2, 4, 6, 9]
"""
Три разных способа сравнить различия между двумя списками в Python.
Примечание: Если вы не уверены, что list_1 является подмножеством list_2, то решение 1 отличается от двух других.
#28 Args и kwargs
def some_function(*args, **kwargs):
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
some_function(1, 2, 3, a=4, b=5, c=6)
"""
Args: (1, 2, 3)
Kwargs: {'a': 4, 'b': 5, 'c': 6}
"""
Мы используем *args и **kwargs в параметрах функции, когда не знаем заранее, сколько аргументов ожидать.
*args позволяет передавать переменное число позиционных аргументов (без имён). **kwargs позволяет передавать произвольное число именованных аргументов.
На самом деле «магия» не в словах args и kwargs, а в звёздочках (*). После звёздочек можно использовать любое имя, но args и kwargs стали общепринятыми среди Python-разработчиков.
#29 Эллипсис
print(...)
"""
Ellipsis
"""
def some_function():
...
# Alternative solution
def another_function():
pass
Ellipsis — это объект Python, который можно получить, написав три точки (...) или вызвав сам объект (Ellipsis).
Наиболее заметное применение — доступ и срезы многомерных массивов в NumPy, например:
import numpy as np
arr = np.array([[2,3], [1,2], [9,8]])
print(arr[...,0])
"""
[2 1 9]
"""
print(arr[...])
"""
[[2 3]
[1 2]
[9 8]]
"""
Ещё одно применение Ellipsis — как заполнитель в нереализованной функции.
То есть можно использовать Ellipsis, ... или pass — все варианты валидны.
#30 Генераторы списков
even_numbers = [x for x in range(10) if x % 2 == 0 and x != 0]
print(even_numbers)
"""
[2, 4, 6, 8]
"""
Наш финальный трюк — генераторы списков, элегантный способ создать список из другой последовательности. Они позволяют выполнять сложную логику и фильтрацию, как в примере выше.
Есть и другие способы добиться того же, например, лямбда-функция:
even_numbers = list(filter(lambda x: x % 2 ==0 and x != 0, range(10)))
print(even_numbers)
"""
[0, 2, 4, 6, 8]
"""
Но многие питонисты скажут, что это менее читаемо, чем генератор списка.
FAQs
Какой трюк Python самый полезный для начинающих?
F-строки, пожалуй, самые полезные с ходу. Они делают форматирование строк быстрее, понятнее и менее ошибкоопасным по сравнению со старыми подходами вроде .format().
Как быстрее всего поменять местами два значения в Python?
Используйте одновременное присваивание: a, b = b, a. Временная переменная не нужна — Python полностью вычисляет правую часть перед присваиванием.
Как объединить два словаря в одну строку?
В Python 3.9+ используйте оператор |: merged = dict_a | dict_b. В старых версиях используйте {**dict_a, **dict_b}.
В чём разница между *args и **kwargs?
*args собирает дополнительные позиционные аргументы в кортеж. **kwargs собирает дополнительные именованные аргументы в словарь. Магия в операторах * и **, а не в самих словах — назвать их можно как угодно.
Как проверить, что список пуст, в Python?
Используйте оператор not: if not my_list:. Это более питонично, чем проверка len(my_list) == 0.
В чём разница между генератором списка и генератором?
Генератор списка (квадратные скобки) строит весь список в памяти сразу. Генератор (круглые скобки) выдаёт значения по одному по требованию, что значительно экономит память на больших последовательностях.
Когда стоит использовать тернарный оператор?
Используйте для простых однострочных условий, где оба результата короткие и понятные: name = "John" if condition else "Doe". Для более сложной логики обычный блок if/else читабельнее.
Как проще всего убрать дубликаты из списка?
Обёрните в set() и сконвертируйте обратно: list(set(my_list)). Учтите, что множества неупорядочены, исходный порядок не сохранится.