Як використовувати генератори та yield у Python

25 хв. читання

Чи доводилось вам колись мати справу з набором даних, що перевантажує пам'ять вашого комп'ютера? Або, можливо, у вас є комплексна функція, що зберігає внутрішній стан при кожному виклику, однак створювати окремий клас для неї недоцільно. В подібних ситуаціях на допомогу приходять генератори та yield у Python.

Після прочитання статті, ви знатимете:

  • що таке генератори та як використовувати їх;
  • як створювати функції-генератори та вирази;
  • як працює yield у Python;
  • як використовувати декілька yield у виразі-генераторі;
  • як застосовувати просунуті методи генераторів;
  • як створювати конвеєри даних з декількома генераторами.

Для подальших прикладів ви можете використовувати набір даних з посібника, аби закріпити знання на практиці.

Використання генераторів

З введенням в PEP 255 функції-генератори є особливим видом функції, яка повертає лінивий ітератор. Тобто це об'єкт, подібний до списку, яким можна пройтись циклічно. Однак, на відміну від списків, ліниві ітератори не зберігають свій вміст у пам'яті. Дізнатися більше про ітератори в Python можна за посиланням.

Тепер, коли ви приблизно уявляєте, що роблять генератори, розглянемо їх у дії на двох прикладах. Спершу ми дізнаємось, як загалом працюють генератори, а вже потім розберемося в деталях.

Приклад 1: Зчитування великих файлів

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

csv_gen = csv_reader("some_csv.txt")
row_count = 0

for row in csv_gen:
    row_count += 1

print(f"Row count is {row_count}")

Ви можете припустити, що csv_gen — список. Щоб наповнити цей список, csv_reader() відкриває файл та завантажує його вміст у csv_gen. Потім відбувається ітерація списком та інкремент row_count для кожного рядка.

Чи буде працювати такий код, якщо файл більший за обсяг доступної пам'яті? Щоб відповісти, припустимо, наче csv_reader() просто відкриває файл та зчитує його в масив:

def csv_reader(file_name):
    file = open(file_name)
    result = file.read().split("\
")
    return result

Ця функція відкриває переданий файл та використовує file.read() разом зі split(), аби додати кожен рядок як окремий елемент списку. Якби ви використовували таку версію csv_reader() у блоці з підрахунком рядків, то отримали б такий результат:

Traceback (most recent call last):
  File "ex1_naive.py", line 22, in <module>
    main()
  File "ex1_naive.py", line 13, in main
    csv_gen = csv_reader("file.txt")
  File "ex1_naive.py", line 6, in csv_reader
    result = file.read().split("\
")
MemoryError

У цьому випадку open() повертає об'єкт генератора, який можна легко ітерувати рядок за рядком. Однак file.read().split() завантажує все одразу в пам'ять, викликаючи MemoryError.

То як бути з такими великими файлами даних? Поглянемо на визначення csv_reader():

def csv_reader(file_name):
    for row in open(file_name, "r"):
        yield row

Тут відкривається файл, відбувається ітерація ним, а для кожного рядка викликається yield. На виході отримаємо такий результат без помилок пам'яті:

Row count is 64186394

Чому так сталося? По суті, ми перетворили csv_reader() на функцію-генератор. Вона відкриває файл, циклічно обходить кожен рядок та викликає yield замість return.

Ви також можете оголосити вираз-генератор (відомий ще як генераторне включення), який має дуже подібний синтаксис до спискового включення. Тож ви можете використовувати генератор, не викликаючи функцію:

csv_gen = (row for row in open(file_name))

Це більш лаконічний спосіб створення списку csv_gen. Скоро ми дізнаємось більше про вираз yield у Python. Поки що просто запам'ятайте ключову відмінність:

  • використання yield призведе до створення об'єкта генератора;
  • використання return поверне лише рядок файлу.

Приклад 2: Генерація нескінченної послідовності

Розглянемо приклад з генерацією нескінченної послідовності. Щоб отримати скінченну послідовність в Python, можна використати range() та застосувати отриману послідовність як список:

>>> a = range(5)
>>> list(a)
[0, 1, 2, 3, 4]

Однак генерація нескінченної послідовності потребує використання генератора, оскільки пам'ять вашого комп'ютера обмежена:

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

В наведеному фрагменті коду ми спершу ініціалізуємо змінну num, потім починаємо нескінченний цикл. Далі ми одразу викликаємо yield для num, щоб захопити початковий стан змінної. Так ми імітуємо дію range(). Наступним кроком інкрементуємо num.

Якщо спробуємо використати створену функцію в циклі for, побачимо, що вона дійсно нескінченна:

>>> for i in infinite_sequence():
...     print(i, end=" ")
...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39 40 41 42
[...]
6157818 6157819 6157820 6157821 6157822 6157823 6157824 6157825 6157826 6157827
6157828 6157829 6157830 6157831 6157832 6157833 6157834 6157835 6157836 6157837
6157838 6157839 6157840 6157841 6157842
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

Програма продовжить виконання, поки ви не зупините її вручну.

Замість використання for ви також можете викликати next() одразу для об'єкта генератора. Це особливо корисно для тестування генератора в консолі:

>>> gen = infinite_sequence()
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
3

Вище ми оголосили генератор gen, який вручну ітеруємо, постійно викликаючи next(). У такий спосіб легко перевіряти правильність роботи генераторів.

Зверніть увагу: коли ви використовуєте next(), Python викликає next() у функції, яку ви передаєте як параметр. Існують деякі особливості в такому підході, та ми не розглядаємо їх в цьому матеріалі. Поекспериментуйте з параметрами, які ви передаєте в next(), та спостерігайте за результатом.

Приклад 3: Виявлення паліндромів

Ви можете використовувати нескінченні послідовності багатьма способами. Один із практичних варіантів — виявлення паліндромів. Детектор паліндромів помічає всі послідовності літер або цифр, які утворюють паліндроми (слова або числа, які читаються однаково в обох напрямках). Спершу розглянемо детектор числових паліндромів:

def is_palindrome(num):
    # Skip single-digit inputs
    if num // 10 == 0:
        return False
    temp = num
    reversed_num = 0

    while temp != 0:
        reversed_num = (reversed_num * 10) + (temp % 10)
        temp = temp // 10

    if num == reversed_num:
        return num
    else:
        return False

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

>>> for i in infinite_sequence():
...     pal = is_palindrome(i)
...     if pal:
...         print(pal)
...
11
22
33
[...]
99799
99899
99999
100001
101101
102201
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 5, in is_palindrome

Зверніть увагу: на практиці вам навряд чи доведеться писати власний генератор нескінченних послідовностей. Модуль itertools пропонує ефективний генератор нескінченної послідовності itertools.count().

Ми розглянули прості приклади використання генераторів нескінченних послідовностей. Заглибимось тепер в особливості роботи самих генераторів.

Розуміння генераторів

Ви вже знаєте, як створити генератори двома способами: використовуючи функцію-генератор та вираз-генератор. Імовірно, ви вже інтуїтивно розумієте і принцип роботи генераторів. Ця частина допоможе впорядкувати та поглибити отримані знання.

Функції-генератори нагадують звичайні функції, але з однією визначальною характеристикою. Генератори використовують ключове слово yield у Python замість return. Пригадайте раніше створену функцію-генератор:

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

Все це схоже на звичайне оголошення функції, окрім виразу з yield. За допомогою yield ми вказуємо, коли значення повертається до ініціатора виклику, але (на відміну від return) не завершує виконання функції.

Тож стан функції запам'ятовується: при виклику next() для об'єкта-генератора (як явно, так і неявно всередині циклу for) попереднє значення num інкрементується та знов повертається. Оскільки функції-генератори подібні до інших функцій, ви можете припустити, що вирази-генератори дуже схожі на інші включення в Python.

Створення генераторів з виразами-генераторами

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

>>> nums_squared_lc = [num**2 for num in range(5)]
>>> nums_squared_gc = (num**2 for num in range(5))

Конструкції nums_squared_lc і nums_squared_gc практично однакові, але мають одну ключову відмінність. Можете її визначити?

Поглянемо, що відбувається, коли ви виводите вміст кожного з об'єктів.

>>> nums_squared_lc
[0, 1, 4, 9, 16]
>>> nums_squared_gc
<generator object <genexpr> at 0x107fbbc78>

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

Оцінка продуктивності генераторів

Ми вже стверджували, що генератори — чудовий спосіб оптимізувати пам'ять. Тож перевіримо, наскільки використання генераторів ефективне на практиці:

>>> import sys
>>> nums_squared_lc = [i * 2 for i in range(10000)]
>>> sys.getsizeof(nums_squared_lc)
87624
>>> nums_squared_gc = (i ** 2 for i in range(10000))
>>> print(sys.getsizeof(nums_squared_gc))
120

В цьому прикладі список, який ми отримуємо зі спискового включення, важить 87,624 байта, а об'єкт генератора — лише 120. Тобто список займає в пам'яті в 700 разів більше місця, ніж об'єкт-генератор.

Однак варто пам'ятати про одну особливість. Якщо список менший за доступну пам'ять, спискове включення виконуватиметься швидше за еквівалентний вираз-генератор.

Щоб перевірити все на практиці, просумуємо результати двох включень і виведемо тривалість виконання в консоль за допомогою cProfile.run():

>>> import cProfile
>>> cProfile.run('sum([i * 2 for i in range(10000)])')
         5 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <string>:1(<listcomp>)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.001    0.001 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


>>> cProfile.run('sum((i * 2 for i in range(10000)))')
         10005 function calls in 0.003 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10001    0.002    0.000    0.002    0.000 <string>:1(<genexpr>)
        1    0.000    0.000    0.003    0.003 <string>:1(<module>)
        1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
        1    0.001    0.001    0.003    0.003 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Тут ми переконуємось, що варіант зі списковим включенням втричі швидший за генератор. Якщо у вашому випадку швидкість важлива, а пам'ять ні, тоді спискове включення — найкращий вибір.

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

Пам'ятайте, що спискове включення повертає повний список, а вираз-генератор – об'єкт генератора. При цьому неважливо, в який спосіб ви створите генератор: виразом чи функцією. Кінцевий результат не відрізнятиметься. Єдине, при використанні виразу ви зможете визначити простий генератор одним рядком. Так припускається, що виклик yield відбувається наприкінці кожної ітерації.

Оператор yield є саме тим стрижнем, на якому тримається весь функціонал генераторів, тому розберемось в особливостях його роботи.

Розуміння yield у Python

Основна робота yield — контроль виконання функції-генератора, як це робить return. Однак, ми вже помітили, що yield має деякі особливості.

Коли ви викликаєте функцію-генератор чи вираз-генератор, то повертаєте спеціальний ітератор, який також називається генератором. Ви можете передати його змінній, щоб використовувати далі в коді. Якщо ви викликаєте спеціальні методи генератора (на зразок next()), код всередині функції виконується до yield.

Коли Python натрапляє на вираз yield, призупиняється виконання функції та повертається значення, для якого викликано yield (return повністю зупиняє виконання функції). Коли функція призупинена, її стан зберігається. Тобто зберігається прив'язка всіх локальних для генератора змінних, внутрішній стек та обробка винятків.

Так ми можемо поновити виконання функції при кожному виклику метода генератора. Перевіримо на практиці:

>>> def multi_yield():
...     yield_str = "This will print the first string"
...     yield yield_str
...     yield_str = "This will print the second string"
...     yield yield_str
...
>>> multi_obj = multi_yield()
>>> print(next(multi_obj))
Роздрукуємо перший рядок
>>> print(next(multi_obj))
Роздрукуємо другий рядок
>>> print(next(multi_obj))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Зверніть особливу увагу на останній виклик next(). Ми бачимо, що виконання закінчилось трасуванням стеку. Усе тому, що генератори, як і всі ітератори, можуть вичерпуватись. Якщо ваш генератор скінчений, ви можете проітерувати його лише раз. Щойно вичерпаються всі значення, ітерація зупиниться. Якщо далі ви викличете next(), то отримаєте виняток StopIteration.

Зверніть увагу: виняток StopIteration повідомляє про завершення ітератора. Той самий цикл for працює у поєднанні зі StopIteration. Ви навіть можете реалізувати власний цикл for, використовуючи while:

>>> letters = ["a", "b", "c", "y"]
>>> it = iter(letters)
>>> while True:
...     try:
...         letter = next(it)
...     except StopIteration:
...         break
...     print(letter)
...
a
b
c
y

*Більше про StopIteration можна дізнатись з документації Python про винятки.

Тож yield можна використовувати багатьма способами, аби контролювати роботу генератора.

Використання просунутих методів генератора

Ми вже оглянули достатньо поширених варіантів використання та конструкцій генераторів, однак залишилось ще декілька трюків, вартих уваги. Окрім yield, об'єкт генератора може виконувати такі методи:

  • send()
  • throw()
  • close()

Як використовувати send()

В цьому розділі ми на практиці використаємо всі три методи. Створимо програму, що виводитиме числові паліндроми, як і раніше, але з деякими змінами. Ми будемо обробляти винятки зі throw() та зупиняти генератор після певної кількості чисел з close(). Але спершу пригадаймо код для пошуку паліндромів:

def is_palindrome(num):
    # Skip single-digit inputs
    if num // 10 == 0:
        return False
    temp = num
    reversed_num = 0

    while temp != 0:
        reversed_num = (reversed_num * 10) + (temp % 10)
        temp = temp // 10

    if num == reversed_num:
        return True
    else:
        return False

На відміну від коду, який був спочатку, тут суворо повертається True або False. Вам також необхідно буде змінити генератор нескінченної послідовності так:

def infinite_palindromes():
    num = 0
    while True:
        if is_palindrome(num):
            i = (yield num)
            if i is not None:
                num = i
        num += 1

Ми внесли безліч змін. Перша з них у 5-му рядку, де i = (yield num). Раніше ми засвоїли, що yield є оператором, та це не єдина його особливість.

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

Ви також можете виконати перевірку if i is not None, якщо next() викликається для об'єкта генератора (або відбувається ітерація в циклі for). Якщо i приймає певне значення, ви оновлюєте ним num. Однак ви в будь-якому випадку інкрементуєте num та починаєте цикл спочатку.

Тепер розглянемо код основної функції:

pal_gen = infinite_palindromes()
for i in pal_gen:
    digits = len(str(i))
    pal_gen.send(10 ** (digits))

Тут ми створюємо об'єкт генератора та ітеруємось ним. Програма повертає значення, лише коли знаходить паліндром. Ми використовуємо len(), щоб визначити кількість цифр в паліндромі. Потім ми передаємо результат 10 ** digits назад в генератор. Він призначає отримане значення i. Оскільки i має значення, оновлюється num, інкрементується та знов проводиться перевірка на паліндром.

Як тільки ваш код знаходить та повертає інший паліндром, ви продовжите ітерацію циклом for. Це те саме, що й ітерація з next(). Генератор також підхоплює значення з рядка 5 i = (yield num). Однак тепер i є None, оскільки ви явно не повернули значення.

Тільки що ми створили корутину, або функцію-генератор, в яку можна передавати дані. Вона може бути корисною для створення конвеєрів даних.

Детальніше ознайомитись з темою корутин та паралелізму можна за посиланням.

Тепер, коли ми дізнались про .send(), перейдемо до .throw().

Як використовувати .throw()

.throw() дозволяє викидати винятки з генератором. В прикладі нижче ми викликаємо виняток ValueError на рядку 6, щойно довжина digits складатиме 5.

pal_gen = infinite_palindromes()
for i in pal_gen:
    print(i)
    digits = len(str(i))
    if digits == 5:
        pal_gen.throw(ValueError("We don't like large palindromes"))
    pal_gen.send(10 ** (digits))

Єдина відмінність від попереднього коду в тому, що ми перевіряємо довжину числа. Аби перевірити, чи все працює правильно, поглянемо на результат коду:

11
111
1111
10101
Traceback (most recent call last):
  File "advanced_gen.py", line 47, in <module>
    main()
  File "advanced_gen.py", line 41, in main
    pal_gen.throw(ValueError("We don't like large palindromes"))
  File "advanced_gen.py", line 26, in infinite_palindromes
    i = (yield num)
ValueError: We don't like large palindromes

.throw() корисний для випадків, коли необхідно відловити винятки. В цьому прикладі ми звернулись до .throw(), щоб вчасно завершити ітерацію генератором. Але більш елегантним рішенням буде використання .close().

Як використовувати .close()

Як видно з назви, метод .close() дозволяє зупинити генератор. Це може бути дуже корисним для контролю генератора нескінченної послідовності. Замінимо .throw() в коді вище на .close(), щоб зупинити ітерацію:

pal_gen = infinite_palindromes()
for i in pal_gen:
    print(i)
    digits = len(str(i))
    if digits == 5:
        pal_gen.close()
    pal_gen.send(10 ** (digits))

Перевага .close() у тому, що він викликає виняток StopIteration, який сповіщає про завершення ітерації:

11
111
1111
10101
Traceback (most recent call last):
  File "advanced_gen.py", line 46, in <module>
    main()
  File "advanced_gen.py", line 42, in main
    pal_gen.send(10 ** (digits))
StopIteration

Тепер, коли ми детально поговорили про спеціальні методи генераторів, розглянемо їх більш практичне застосування — створення конвеєрів даних.

Створення конвеєрів даних з генераторами

Конвеєри даних дозволяють об'єднувати код для обробки великих наборів або потоків даних, не виходячи за межі доступної пам'яті.

Припустимо, існує великий CSV-файл:

permalink,company,numEmps,category,city,state,fundedDate,raisedAmt,raisedCurrency,round
digg,Digg,60,web,San Francisco,CA,1-Dec-06,8500000,USD,b
digg,Digg,60,web,San Francisco,CA,1-Oct-05,2800000,USD,a
facebook,Facebook,450,web,Palo Alto,CA,1-Sep-04,500000,USD,angel
facebook,Facebook,450,web,Palo Alto,CA,1-May-05,12700000,USD,a
photobucket,Photobucket,60,web,Palo Alto,CA,1-Mar-05,3000000,USD,a

Завантажити набір даних можна за посиланням.

Аби продемонструвати, як створювати конвеєри з генераторами, обчислимо середнє та загальне значення всіх серій А в наборі.

Стратегія буде така:

  1. Зчитуємо кожен рядок файлу.
  2. Розбиваємо кожен рядок на список значень.
  3. Вилучаємо назви колонок.
  4. Використовуємо назви колонок та списки для створення словника.
  5. Фільтруємо дані, які нас не цікавлять.
  6. Підраховуємо загальне та середнє значення для потрібних даних.

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

file_name = "techcrunch.csv"
lines = (line for line in open(file_name))

Потім використаємо інший вираз-генератор, разом з попереднім, аби створити з рядків списки.

list_line = (s.rstrip().split(",") for s in lines)

Тут ми створили генератор list_line, який ітерується іншим генератором – lines. Такий підхід поширений при створенні конвеєрів генератора. Далі витягуємо назви стовпців з techcrunch.csv. Оскільки назви стовпців, як правило, займають перший рядок в CSV-файлі, ви можете отримати їх, викликавши next().

cols = next(list_line)

Вище ми змістили ітератор генератором list_line один раз. Якщо зібрати весь код докупи, вийде ось що:

file_name = "techcrunch.csv"
lines = (line for line in open(file_name))
list_line = (s.rstrip().split(",") for s in lines)
cols = next(list_line)

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

Зверніть увагу: остерігайтеся рядків з пробілів. У коді ми використали .rstrip(), аби прибрати усі зайві символи переведення рядка, на які можна натрапити в CSV-файлах.

Для полегшення фільтрації та операції над даними, створюємо словники, ключами яких будуть назви колонок з CSV.

company_dicts = (dict(zip(cols, data)) for data in list_line)

Цей вираз-генератор ітерується списком, утвореним list_line. Далі використовуємо zip() та dict(), щоб створити словник. Тепер використаємо четвертий генератор, щоб відфільтрувати потрібні дані.

funding = (
    int(company_dict["raisedAmt"])
    for company_dict in company_dicts
    if company_dict["round"] == "a"
)

Варто пам'ятати, що ви не ітеруєте все одразу в одному виразі-генераторі. Ітерація не відбувається, доки ви явно не використаєте цикл for або функцію, яка працює з ітерованими об'єктами, на зразок sum(). Саме виклик sum() зумовлює ітерацію генераторами:

total_series_a = sum(funding)

Збираємо все докупи і отримуємо такий код:

file_name = "techcrunch.csv"
lines = (line for line in open(file_name))
list_line = (s.rstrip()split(",") for s in lines)
cols = next(list_line)
company_dicts = (dict(zip(cols, data)) for data in list_line)
funding = (
    int(company_dict["raisedAmt"])
    for company_dict in company_dicts
    if company_dict["round"] == "A"
)
total_series_a = sum(funding)
print(f"Total series A fundraising: ${total_series_a}")

Ми зібрали всі генератори, щоб вони функціонували, як один великий конвеєр даних. Розберемо крок за кроком:

  • Рядок 2: зчитуємо кожен рядок файлу.
  • Рядок 3: розбиваємо кожен рядок на значення та розміщуємо їх в списку.
  • Рядок 4: використовуємо next(), щоб зберегти назву колонки в списку.
  • Рядок 5: створюємо словники та об'єднуємо їх викликом zip(). Ключі є колонками (cols) з рядка 4. Значення є рядками у формі списку, створеного в рядку 3.
  • Рядок 6: отримуємо потрібні дані.
  • Рядок 7: починаємо ітерацію, викликавши sum(), щоб отримати загальну кількість даних з CSV.

Після запуску коду для файлу techcrunch.csv ви отримаєте загальну суму $4,376,015,000 фінансування в серії А.

Зверніть увагу: такий метод обробки CSV-файлів важливий для розуміння генераторів та yield у Python. Однак, коли ви працюєте з CSV-файлами в Python, вам слід використовувати CSV-модуль зі стандартної бібліотеки Python. Там значно оптимізуються методи обробки CSV-файлів.

Щоб зануритись ще глибше, спробуйте знайти середню кількість, зароблену кожною компанією в серії А. Одразу декілька підказок:

  • генератори вичерпуються після того, як повністю проітеровані;
  • вам досі потрібна функція sum().
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація