Python можно без сомнений называть языком, испытавшим в последнее десятилетие значительный рост, языком, который доказал свою мощь. Я создал множество Python-приложений — от интерактивных карт до блокчейнов. Python обладает огромным количеством возможностей. Начинающим очень сложно сходу всё это ухватить.
Даже если вы — программист, который переходит на Python с другого языка, вроде C, или если вы — учёный, который раньше работал в MATLAB, Python-программирование с использованием высокоуровневых абстракций — это, определённо, совершенно особый опыт. Я хочу рассказать о пяти очень важных, на мой взгляд, возможностях Python, о которых я раньше, к сожалению, не знал.
1. Списковые включения — написание компактного кода
Многие назвали бы такие механизмы Python, как lambda
, map
и filter
, теми самыми секретами, которые следует изучить каждому начинающему программисту. И хотя я уверен, что всё это стоит знать, я, в большинстве ситуаций, в которых эти инструменты можно применить, столкнулся с недостаточной гибкостью этих инструментов. В результате они оказываются далеко не такими полезными, как может показаться.
Lambda
— это метод создания маленьких однострочных функций для однократного использования. Если некая функция вызывается много раз — это ухудшает производительность. Но при этом функция map
применяет функцию ко всем элементам списка, а filter
выбирает из некоего множества элементов подмножество, соответствующее условию, заданному пользователем.
add_func = lambda z: z ** 2
is_odd = lambda z: z%2 == 1
multiply = lambda x,y: x*y
aList = list(range(10))
print(aList)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Списковое включение (list comprehension) — это компактная и удобная конструкция, применяемая для создания списков из других списков с использованием гибких выражений и условий. Эту конструкцию описывают, используя квадратные скобки. В них включают выражения или функции, применяемые к каждому элементу списка только в том случае, если этот элемент удовлетворяет определённому условию. Списковое включение может представлять собой многоуровневую конструкцию, содержащую вложенные блоки. Это позволяет обрабатывать вложенные списки, давая гораздо больше гибкости, чем использование map
и filter
.
Вот схема синтаксиса спискового включения:
[ expression(x) for x in aList if optional_condition(x) ]
Вот пример:
print(list(map(add_func, aList)))
print([x ** 2 for x in aList])
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(list(filter(is_odd, aList)))
print([x for x in aList if x%2 == 1])
# [1, 3, 5, 7, 9]
# [1, 3, 5, 7, 9]
2. Работа со списками — циклические списки
Python позволяет, при работе со списками, использовать отрицательные индексы. При этом справедливо такое сравнение:
aList[-1] == aList[len(aList)-1]
Это даёт возможность, например, получить второй элемент с конца списка, воспользовавшись конструкцией aList[-2]
. Возможны и другие подобные конструкции.
Кроме того, можно получить срез (slice) списка:
aList[start:end:step]
Здесь в получаемый срез включается начальный элемент списка, а конечный элемент в него не включается. Предположим, список выглядит так:
aList = [1, 2, 3, 4, 5, 6, 7, 8, 9]
Срез aList[2:5]
будет выглядеть как [2, 3, 4]
. Кроме того, можно «перевернуть» список, воспользовавшись командой aList[::-1]
. В нашем случае это даст [9, 8, 7, 6, 5, 4, 3, 2, 1]
. Я нахожу это решение задачи «переворачивания» списка весьма наглядным и удачным.
Ещё список можно распаковать, поместив хранящиеся в нём значения в отдельные переменные. Или — «разложив» его значения по отдельным переменным и по новым спискам, используя звёздочку:
a, b, c, d = aList[0:4]
print(f'a = {a}, b = {b}, c = {c}, d = {d}')
# a = 0, b = 1, c = 2, d = 3
a, *b, c, d = aList
print(f'a = {a}, b = {b}, c = {c}, d = {d}')
# a = 0, b = [1, 2, 3, 4, 5, 6, 7], c = 8, d = 9
3. Функции zip и enumerate — создание мощных циклов for
Функция zip
создаёт итератор, который комбинирует элементы нескольких списков. Это позволяет осуществлять параллельный обход списков в циклах for
и выполнять параллельную сортировку. Вернуть скомбинированные элементы в обычное состояние можно, воспользовавшись оператором *
.
Вот пример работы с функцией zip
:
numList = [0, 1, 2]
engList = ['zero', 'one', 'two']
espList = ['cero', 'uno', 'dos']
print(list(zip(numList, engList, espList)))
# [(0, 'zero', 'cero'), (1, 'one', 'uno'), (2, 'two', 'dos')]
for num, eng, esp in zip(numList, engList, espList):
print(f'{num} is {eng} in English and {esp} in Spanish.')
# 0 is zero in English and cero in Spanish.
# 1 is one in English and uno in Spanish.
# 2 is two in English and dos in Spanish.
А вот как вернуть списки в обычное состояние:
Eng = list(zip(engList, espList, numList))
Eng.sort() # сортировка по engList
a, b, c = zip(*Eng)
print(a)
print(b)
print(c)
# ('one', 'two', 'zero')
# ('uno', 'dos', 'cero')
# (1, 2, 0)
Функция enumerate
, на первый взгляд, может выглядеть очень сложной, но если с ней разобраться, оказывается, что она способна пригодиться во многих случаях. Это — автоматический счётчик, который часто используется в циклах for
. При этом в таких циклах не нужно создавать и инкрементировать переменную-счётчик, используя конструкции вроде counter = 0
и counter += 1
.
Функции zip
и enumerate
— это два мощнейших инструмента, применяемых при конструировании циклов for
:
upperCase = ['A', 'B', 'C', 'D', 'E', 'F']
lowerCase = ['a', 'b', 'c', 'd', 'e', 'f']
for i, (upper, lower) in enumerate(zip(upperCase, lowerCase), 1):
print(f'{i}: {upper} and {lower}.')
# 1: A and a.
# 2: B and b.
# 3: C and c.
# 4: D and d.
# 5: E and e.
# 6: F and f.
4. Генераторы — эффективное использование памяти
Генераторы применяются в тех случаях, когда нужно выполнить некие вычисления, приводящие к появлению большого набора результатов, но при этом требуется избежать единовременного выделения памяти под хранение всех таких результатов. Другими словами, генераторы выдают некие значения, что называется, «на лету». При этом они не хранят в памяти ранее найденные ими значения. В результате память используется экономно.
Генераторы часто применяются для чтения больших файлов или для создания бесконечных последовательностей с помощью ключевого слова yield
. Я часто нахожу целесообразным их использование в моих научных проектах.
def gen(n): # генератор бесконечной последовательности целых чисел, члены которой >= n
while True:
yield n
n += 1
G = gen(3) # начинается с 3
print(next(G)) # 3
print(next(G)) # 4
print(next(G)) # 5
print(next(G)) # 6
5. Виртуальное окружение — изоляция кода
Если бы вы могли выбрать лишь одну идею из этой статьи, которую смогли бы запомнить, то я посоветовал бы вам ту, о которой мы поговорим в этом разделе. Речь идёт о виртуальных окружениях (virtual environment).
Python-приложения часто используют множество различных пакетов. Они созданы разными разработчиками, они отличаются сложными наборами зависимостей. В некоем приложении может быть использована какая-то конкретная версия некоей библиотеки. При этом другая версия для такого приложения не подойдёт. А что если несколько приложений используют разные версии одной и той же библиотеки? Это непростой вопрос, который приводит нас к мысли о том, что не существует некоей универсальной среды, пакеты, установленные в которой, способны подойти для абсолютно всех приложений. Ответом на этот вопрос можно назвать использование виртуальных окружений.
Для создания изолированных друг от друга виртуальных окружений можно поступить так:
conda create -n venv pip python=3.7 # выбор версии python
source activate venv
...
source deactivate
Итоги
В этом материале мы разобрали 5 полезных возможностей Python. А именно, речь шла о списковых включениях, о циклических списках, о функциях zip
и enumerate
, о генераторах и о виртуальных окружениях. Надеемся, эти возможности вам пригодятся.
Уважаемые читатели! Если бы вы могли рассказать начинающему программисту всего об одной возможности Python — что бы вы выбрали?
Автор: ru_vds