В данной статье мы рассмотрим основные концепции Python, которые необходимо знать и понимать каждому профессиональному программисту. Они образуют основу продвинутого программирования на Python, нацеленного на разумное решение задач.

Генераторы 

Создание итератора и итерируемого объекта в Python требует немалой работы: необходимо создать класс (объектно-ориентированное программирование) при помощи методов __iter__() и __next__(); надлежит сохранить и обновить внутренние состояния и вывести исключение StopIteration при отсутствии возвращаемого значения. 

Итерации в Python

Для сокращения этого затянутого процесса генераторы Python могут создавать итераторы, выполняя все вышеописанные действия автоматически. Генератор — это функция, возвращающая объект (итератор), который также может быть итерирован (последовательным перебором значений по одному).

Функция генератора определяется как функция, но при этом использует ключевое слово yield вместо традиционного return. Если тело функции содержит yield, то она является функцией генератора. 

def simple_generator():
     yield 1
     yield 2
     yield 3for value in simple_generator():
     print(value)

Первые четыре строки кода определяют генератор, который будет итерационно возвращать 1, 2 и 3. Две последние строки демонстрируют итерацию, выводя на экран одно значение за другим: 

1
2
3

Генератор по-прежнему будет требовать функцию для выделения объекта, но нам его нужно приравнять к переменной, в данном случае x. Атрибуты, описанные выше как .next(), автоматически создаются Python и могут быть вызваны.

x = simple_generator()
print(x.next())
print(x.next())
print(x.next())

Например, следующий код создает генератор последовательности чисел Фибоначчи, в которой каждый член является суммой двух предыдущих (0, 1, 1, 2, 3, 5, 8, 13, …). 

def fibonacci(limit):
     a, b = 0, 1
     
     while a < limit:
          yield a
          a, b = b, a+b

Для отслеживания последовательности используются два числа, переменные 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 dog:
     def __init__(self, species, age):
          self.species = species
          self.age = age

Переменные объекта и функции можно вызвать внутри объекта class, используя ., предшествующий ей элемент для ссылки на объект и элемент после точки, ссылающийся на вызываемый объект. self.species = species присваивает внутренней переменной то значение, которое принимает входной параметр species.

Функции можно создавать также внутри класса: 

class dog:
     def __init__(self, species, age):
          self.species = species
          self.age = agedef bark(self, call='Woof!'):
          print(call)def reveal_information(self):
          print('I am a', self.species)
          print('I am', age, 'years old')

Эти две внутренние функции, 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: 

counter = 0
for item in a_list:
     do_something_with(item)
     do_something_else_with(counter)
     counter += 1

Однако функция 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()

def decorate(function):
    def inner_function():
        print("I was decorated!")
        function()
    return inner_function

Чтобы обернуть исходную функцию 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 перед функцией. 

Благодарю за внимание!

Читайте также:


Перевод статьи Andre Ye: Essential Python Concepts Any Serious Programmer Needs to Know, Explained

Предыдущая статьяСвязь между микро-фронтендами
Следующая статья7 причин выгорания программистов