Источник: Nuances of Programming
В данной статье мы рассмотрим основные концепции Python, которые необходимо знать и понимать каждому профессиональному программисту. Они образуют основу продвинутого программирования на Python, нацеленного на разумное решение задач.
Генераторы
Создание итератора и итерируемого объекта в Python требует немалой работы: необходимо создать класс (объектно-ориентированное программирование) при помощи методов __iter__() и __next__(); надлежит сохранить и обновить внутренние состояния и вывести исключение StopIteration при отсутствии возвращаемого значения.
Для сокращения этого затянутого процесса генераторы Python могут создавать итераторы, выполняя все вышеописанные действия автоматически. Генератор — это функция, возвращающая объект (итератор), который также может быть итерирован (последовательным перебором значений по одному).
Функция генератора определяется как функция, но при этом использует ключевое слово yield вместо традиционного return. Если тело функции содержит yield, то она является функцией генератора.
Первые четыре строки кода определяют генератор, который будет итерационно возвращать 1, 2 и 3. Две последние строки демонстрируют итерацию, выводя на экран одно значение за другим:
1
2
3
Генератор по-прежнему будет требовать функцию для выделения объекта, но нам его нужно приравнять к переменной, в данном случае x. Атрибуты, описанные выше как .next(), автоматически создаются Python и могут быть вызваны.
Например, следующий код создает генератор последовательности чисел Фибоначчи, в которой каждый член является суммой двух предыдущих (0, 1, 1, 2, 3, 5, 8, 13, …).
Для отслеживания последовательности используются два числа, переменные a и b, которые соответственно инициализируются со значениями 0 и 1. Пока a находится в пределах допустимого значения (limit), т. е. числа, указанного в качестве параметра функции, программа выдает (“возвращает”) значение a. Затем a и b одновременно обновляются, в процессе чего a принимает значение b, а b — значение суммы a + b. В результате этих действий последовательность смещается вправо на одну позицию, сохраняя соответствующую информацию для получения следующего значения последовательности (суммы двух предыдущих чисел).
Теперь генератор можно использовать в цикле for:
for i in fibonacci(8):
print(i)
Таким образом, на выходе будут поочередно перебираться все элементы последовательности Фибоначчи со значением меньше 8.
0
1
1
2
3
5
Объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП), одна из особенностей Python, обеспечивает понятное и структурированное хранение методов и переменных. ООП на Python состоит из объектов class, содержащих информацию об объекте.
Предположим, нам захотелось создать виртуальную собаку в Python. Индивидуальные атрибуты класса хранятся в функции __init__. Нам также необходимо включить параметр self наряду с другими атрибутами, определяющими характеристики объекта при его создании, такие как species и age.
Переменные объекта и функции можно вызвать внутри объекта class, используя ., предшествующий ей элемент для ссылки на объект и элемент после точки, ссылающийся на вызываемый объект. self.species = species присваивает внутренней переменной то значение, которое принимает входной параметр species.
Функции можно создавать также внутри класса:
Эти две внутренние функции, bark и reveal_information, являются методами, выполняемыми и присоединяемыми классом. Затем нам нужно установить переменную pepper в классе dog и конкретизировать параметры инициализации, а именно species и age.
pepper = dog(species='German Shepard', age=3)
После этого можно вызвать атрибуты pepper.
pepper.reveal_information()
На выходе получим результат:
I am a German Shepard
I am 3 years old
ООП на Python подходит для многих целей. И хотя, возможно, вам потребуется больше времени на написание, зато этот метод программирования обеспечивает хорошую читаемость кода.
Замыкания
Замыкания помогают избежать использования глобальных значений и обеспечивают способ сокрытия данных, предлагая тем самым объектно-ориентированное решение задачи. Если в классе необходимо выполнить несколько методов, замыкания могут предложить альтернативное и более оригинальное решение. Когда количество атрибутов и методов увеличивается, класс становится более подходящим для их применения. Определить замыкания в Python, когда вложенная функция ссылается на переменную в окружающей ее области видимости, можно по следующим признакам:
- существует вложенная функция (функция внутри функции);
- вложенная функция ссылается на значение, отклоненное во внешней функции;
- внешняя функция возвращает вложенную функцию.
Посмотрим, как работает замыкание на примере функции make_multiplier, которая принимает параметр nи возвращает функцию с этим параметром.
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
Создание функции, которая умножает что-либо на 3, было бы выполнено следующим образом:
times3 = make_multiplier(3)
Так как make_multiplier возвращает функцию, то times3 также является функцией.
print(times3(9))
И возвращает
27
… потому что при умножении 3 на 9 получается 27. Пожалуй, замыкания являются лучшим способом выполнить эту задачу в Python.
Встроенное перечисление
Отметим без ложной скромности, что встроенное перечисление Python просто превосходно. Возможно, одна из самых частых задач, с которой сталкивается разработчик — это итерация элементов в списке с сохранением отслеживания индекса. Во многих других языках без функции перечисления программисту нужно было бы выполнить это действие вручную, как в этом примере на Python:
Однако функция enumerate() в Python автоматически отслеживает счетчик каждой итерации, возвращая распаковываемый кортеж:
for index, item in enumerate(a_list):
do_something_with(item)
do_something_with(index)
Например, следующий код
for index, item in enumerate(['a','b','c']):
print(index,item)
выведет:
0 a
1 b
2 c
Декораторы
Декораторы принимают функцию, расширяют ее функциональность и затем возвращают. Это особенно полезно в ситуациях, в которых надо произвести небольшие вариации родительской функции. И лучше изменить ее при помощи декоратора, чем переписывать снова для каждой из ее вариаций.
Допустим, у нас есть функция ordinary(), чье единственное назначение состоит в выводе строки “I am an ordinary function.”
def ordinary():
print("I am an ordinary function.")
И предположим, нам захотелось добавить еще одно сообщение “I was decorated!”. Алгоритм действий следующий: создаем функцию decorate(), которая принимает имеющуюся функцию, набираем дополнительное сообщение и вызываем исходную функцию, чей объект введен в decorate() в качестве параметра. Процедуру ввода дополнительной строки и вызов исходной функции можно сохранить во внутренней функции, объект которой возвращается decorate().
Чтобы обернуть исходную функцию ordinary(), мы вызовем для нее decorate(). Переменная decorated, которую мы сохраняем на выходе decorate(), является функцией (inner_function в функции decorate()).
decorated = decorate(ordinary)
decorated()
Вызов decorated() выдает:
I was decorated!
I am an ordinary function.
Декораторы используют знак @ для автоматического обертывания функции:
@decorate
def ordinary():
print("I am an ordinary function.")
Использование @ перед определением функции приводит к автоматическому ее декорированию, которое осуществляется так же, как и ранее описанный метод.
Несколько декораторов могут быть связаны в одну цепочку друг поверх друга путем добавления строк @decorate перед функцией.
Благодарю за внимание!
Читайте также:
Читайте нас в телеграмме и vk
Перевод статьи Andre Ye: Essential Python Concepts Any Serious Programmer Needs to Know, Explained