Python. Функції-генератори. Інструкція yield. Методи next(), iter(), send()

Функції-генератори. Інструкція yield. Методи next(), iter(), send()


Зміст


Пошук на інших ресурсах:

1. Конструкції, що відтворюють результати за вимогою. Особливості. Поняття відкладеної операції

Мова програмування Python має засоби, які дозволяють отримувати результати за вимогою. До цих засобів відносяться:

  • функції-генератори;
  • вирази-генератори.

Функції-генератори дозволяють генерувати послідовність значень за вимогою. Ці функції відрізняються від звичайних функцій тим, що вони, повернувши значення, можуть продовжити свою роботу з того місця, де вони були зупинені. Як відомо, звичайні функції, повернувши значення припиняють свою роботу.

 

2. Поняття протоколу ітерацій. Методи __next__() та iter(). Підтримка протоколу ітерацій функціями-генераторами

З допомогою функцій-генераторів або інших механізмів (наприклад, циклів for) можна здійснювати обхід послідовностей чи значень генераторів. Для забезпечення цього використовується так званий ітераційний протокол (протокол ітерацій). Якщо протокол не підтримується, то виконання циклу for є невдалим і ця інструкція виконує операцію індексування послідовності.

Згідно з цим протоколом, всі ітеровані об’єкти визначають метод __next__(). Цей метод призначений для повернення наступного елементу в ітерації. Якщо ітерації завершуються, то метод генерує виключення StopIteration. Для кожного ітерованого об’єкта (рядок, список, тощо) можна отримати доступ до ітератора цього об’єкта з допомогою виклику вбудованої функцїі iter().

Наприклад, для заданого рядка s, можна викликати функцію iter(), щоб отримати ітератор наступним чином

# Функції-генератори
# Функція iter()

# 1. Задано рядок - це є ітерований об'єкт
s = "abcde axsadlk sdw"

# 2. Отримати ітератор об'єкту s
iterObj = iter(s)

# 3. Використати ітератор для обходу рядка s
count_a = 0
for c in iterObj:
    if c=='a':
        count_a = count_a+1

print("count of 'a' = ", count_a) # count of 'a' = 3

Так само як цикл for, функції-генератори підтримують протокол ітерацій. Якщо викликається функція-генератор, то вона повертає об’єкт, що є генератором. Цей об’єкт-генератор містить метод __next__(), який створюється автоматично. Наявність методу __next__() дозволяє продовжити виконання ітерацій на наступних кроках.

 

3. Відмінності між звичайними функціями та функціями-генераторами. Заморожування стану в функціях-генераторах

Для звичайних функцій та функцій-генераторів можна виділити такі спільні та відмінні риси:

  1. Обидва види функцій створюються з допомогою інструкції def.
  2. Звичайні функції повертають значення оператором return. Функції-генератори повертають значення оператором yield.
  3. Звичайні функції не підтримують протокол ітерацій. Функції-генератори підтримують цей протокол.
  4. При поверненні значення оператором return, звичайні функції припиняють свою роботу. При поверненні значення операторм yield, функції-генератори автоматично призупиняють та відновлюють своє виконання, зберігаючи інформацію що необхідна для генерування значень.
  5. Функції-генератори постачають значення. Звичайні функції повертають значення.

Найбільш важливою особливістю функцій-генераторів є те, що вони вміють призупиняти свою роботу та автоматично зберігати інформацію про свій стан. Цей стан визначає всю локальну область видимості функції з усіма локальними змінними. При відновленні роботи функції-генератора, раніше збережений стан стає доступний для отримання наступного згенерованого значення.
Отже, процес призупинки виконання функції та збереження інформації про стан локальних змінних чи параметрів, називається заморожуванням стану.

З точки зору використання пам’яті, функції-генератори дають можливість уникати необхідності виконувати всю роботу одразу. Ця властивість підсилюється, коли список результатів має значний об’єм або коли обчислення кожного нового значення займає тривалий час. У цьому випадку використання функцій-генераторів дає більш ефективний розподіл часу, що необхідний для створення усієї послідовності значень.

 

4. Функції-генератори. Особливості реалізації. Інструкція yield. Загальна форма

Для того, щоб функція могла генерувати послідовність значень та заморожувати свій стан, потрібно щоб ця функція підтримувала протокол ітерацій. У цьому випадку функція повинна повертати результат інструкцією yield замість інструкції return.
Формування значень, що повертаються з функції, може здійснюватись одним з двох відомих операторів циклу: for або while.

Якщо значення в тілі функції-генератора формуються з допомогою циклу for, то приблизна загальна форма такої функції наступна:

def FuncName(list_of_parameters):
    ...
    for value in iterObj:
        ...
        yield value

тут

  • FuncName – ім’я функції;
  • list_of_parameters – перелік параметрів, які отримує функція;
  • iterObj – ітерований об’єкт. Цей об’єкт може створюватись стандартними засобами, наприклад, методом range();
  • value – елемент з множини значень, що сформовані в ітерованому об’єкті iterObj.

Якщо функція-генератор використовує цикл while для генерування значень, то загальна форма такої функції наступна

def FuncName(list_of_parameters):
    ...
    while condition:
        ...
        yield return_value

тут

  • FuncName – ім’я функції;
  • list_of_parameters – перелік параметрів, які отримує функція;
  • condition – умова виконання циклу while;
  • return_value – значення, яке повертається з функції.

 

5. Приклади функцій-генераторів
5.1. Функція, що підносить число до степеня 3. Використання циклу for

У прикладі наводиться функція-генератор, яка повертає число в степені 3. Повернення результату відбувається з допомогою конструкції yield. Така функція підтримує протокол ітерацій.

# Функції-генератори

# Функція, яка підносить число до степеня 3.
# Результат повертається конструкцією yield
def Power3(value):
    for i in range(value):
        yield i*i*i

for i in Power3(10):
    print(i)

У циклі for відбувається виклик функції Power3(). При кожній ітерації в циклі for функція повертає наступне значення. Це означає, що функція автоматично призупиняє свою роботу і відновлює своє виконання на наступній ітерації циклу.

Результат виконання програми

0
1
8
27
64
125
216
343
512
729

Якщо замість інструкції yield у функції використати інструкцію return то це означатиме що функція буде звичайною і не буде підтримувати протокол ітерацій. У цьому випадку при запуску програми буде виникати помилка типу TypeError

TypeError: 'int' object is not iterable

 

5.2. Функція, що формує послідовність значень з допомогою циклу while

Нижче наводиться проста функція, яка формує послідовність значень

0, 0.2, 0.4, ..., value

Значення змінюються з кроком 0.2.

# Функція-генератор, яка формує числа 0, 0.2, 0.4, ..., value.
# Функція використовує цикл while для формування значень.
def GetFloatValues(value):
    t = 0
    while t<value:
        t = t + 0.2
        yield t

# Використання функції-генератора у зовнішньому коді
for i in GetFloatValues(2):
    print(i)

Результат виконання програми

0.2
0.4
0.6000000000000001
0.8
1.0
1.2
1.4
1.5999999999999999
1.7999999999999998
1.9999999999999998
2.1999999999999997

 

5.3. Функція, що генерує випадкові числа в заданому діапазоні

У прикладі демонструється використання функції-генератора GetRandomValues(), яка формує випадкові числа з допомогою методу random.randint() з модуля random. Функція отримує параметри:

  • n – кількість випадкових чисел, які потрібно згенерувати;
  • a, b – відповідно верхня та нижня межі діапазону чисел, що генеруються.
# Функції-генератори

# Підключити модуль random
import random

# 1. Оголосити Функцію-генератор, яка формує n випадкових чисел
# з допомогою циклу while в заданому діапазоні [a; b]
def GetRandomValues(a, b, n):
    for t in range(n):
        yield random.randint(a, b)

# 2. Використання функції-генератора у зовнішньому коді

# 2.1. Ввід вхідних даних
n = int(input("n = "))
a = int(input("a = "))
b = int(input("b = "))

# 2.2. Сформувати список з n випадкових чисел.
L = []
for i in GetRandomValues(a, b, n):
    L = L + [i]

# 2.3. Вивести список
print("L = ", L)

Тестовий приклад

n = 8
a = 20
b = 40
L = [34, 22, 25, 33, 22, 27, 29, 34]

 

6. Розширений протокол функцій-генераторів. Передача значення у функцію-генератор. Метод send(). Приклад

Крім методу next() у протокол функцій-генераторів додано метод send(). Цей метод забезпечує так званий розширений протокол і використовується для взаємодії між функцією-генератором та викликаючою програмою. Підтримка розширеного протоколу введена починаючи з версії Python 2.5.

З допомогою методу send() можна передати значення виразу yield. У цьому випадку вираз yield розміщується у правій частині оператора присвоєння. Загальний вигляд виразу yield може бути таким

value = yield return_value

тут

  • return_value – значення, яке повертається оператором yield;
  • value – нове значення, яке передається інструкції yield. Фактично, інструкція yield є виразом, який повертає елемент, що передається методу send().

У викликаючому коді, перед тим як передати значення функції генератору, потрібно виконати наступні три кроки:

1. Задати ім’я, яке буде зв’язане з функцією-генератором, наприклад

FN = FuncName(list_of_parameters)

тут

  • FuncName – ім’я функції-генератора;
  • list_of_parameters – список параметрів, що передаються у функцію-генератор;
  • FN – ім’я, яке зв’язане з функцією-генератором.

2. Запустити генератор викликом методу next()

next(FN)

3. Передати значення в генератор шляхом виклику методу send()

FN.send(value)

тут

  • value – значення, що передається в функцію-генератор. Це значення у функції-генераторі може оброблятись деяким чином.

 

7. Приклад, що демонструє використання методу send() для управління послідовністю отримуваних значень з функції-генератора

Умова задачі. Реалізувати функцію-генератор, яка постачає n випадкових чисел. Значення кожного числа знаходиться в межах від 1 до 10 включно. Якщо число розміщується на позиції, яка кратна 3, то функція-генератор повинна повертати 0. Вважати, що позиції чисел нумеруються з 1.

Розв’язок.

# Функції-генератори. Метод send().

import random

# Функція-генератор, яка використовує розширений протокол.
# Ця функція постачає випадкові числа в діапазоні [1, 10].
# Кількість чисел рівна n.
def FnGen(n):
    for t in range(n):
        # Отримати випадкове значення
        value = yield random.randint(1, 10)

        # Якщо у цю функцію передано -1, то повернути 0
        if value==-1:
            yield 20

# 1. Задати кількість випадкових чисел
n = int(input("n = "))

# 2. Запустити генератор
V = FnGen(n+1)
next(V) # Отримати якесь число - це є запуск генератора

# 3. Отримати n випадкових чисел за винятком чисел,
# які лежать на позиціях, що кратні числу 3: (3, 6, 9, ...).
# У ці позиції вписати число 0.

# 4. Ініціалізувати результуючий список чисел
L = []

# 5. Цикл формування списку
i = 1
while i<=n:
    if i%3==0:
        item = V.send(-1) # якщо позиція кратна 3, то передати -1
    else:
        item = next(V)
    L = L + [item]
    i=i+1

# 6. Вивести результат
print(L)

Тестовий приклад

n = 20
[10, 2, 20, 8, 6, 20, 4, 9, 20, 5, 6, 20, 4, 9, 20, 4, 1, 20, 8, 7]

 


Споріднені теми