Итерируйте как профессионал

PythonPythonBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

Вы изучите несколько мощных итерационных техник Python, включая базовую итерацию с помощью цикла for, распаковку последовательностей, использование встроенных функций, таких как enumerate() и zip(), а также применение выражений - генераторов для более эффективного использования памяти.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/DataStructuresGroup(["Data Structures"]) python/ControlFlowGroup -.-> python/for_loops("For Loops") python/DataStructuresGroup -.-> python/lists("Lists") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/AdvancedTopicsGroup -.-> python/generators("Generators") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/for_loops -.-> lab-132442{{"Итерируйте как профессионал"}} python/lists -.-> lab-132442{{"Итерируйте как профессионал"}} python/function_definition -.-> lab-132442{{"Итерируйте как профессионал"}} python/build_in_functions -.-> lab-132442{{"Итерируйте как профессионал"}} python/generators -.-> lab-132442{{"Итерируйте как профессионал"}} python/data_collections -.-> lab-132442{{"Итерируйте как профессионал"}} end

Базовая итерация и распаковка последовательностей

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

Загрузка данных из CSV-файла

Начнем с загрузки некоторых данных из CSV-файла. CSV (Comma-Separated Values, значения, разделенные запятыми) - это распространенный формат файлов, используемый для хранения табличных данных. Чтобы начать, нам нужно открыть терминал в WebIDE и запустить интерпретатор Python. Это позволит нам интерактивно запускать код на Python.

cd ~/project
python3

Теперь, когда мы находимся в интерпретаторе Python, мы можем выполнить следующий код на Python для чтения данных из файла portfolio.csv. Сначала мы импортируем модуль csv, который предоставляет функциональность для работы с CSV-файлами. Затем мы открываем файл и создаем объект csv.reader для чтения данных. Мы используем функцию next для получения заголовков столбцов и преобразуем оставшиеся данные в список. Наконец, мы используем функцию pprint из модуля pprint для вывода строк в более читаемом формате.

import csv

f = open('portfolio.csv')
f_csv = csv.reader(f)
headers = next(f_csv)    ## Get the column headers
rows = list(f_csv)       ## Convert the remaining data to a list
from pprint import pprint
pprint(rows)             ## Pretty print the rows

Вы должны увидеть вывод, похожий на следующий:

[['AA', '100', '32.20'],
 ['IBM', '50', '91.10'],
 ['CAT', '150', '83.44'],
 ['MSFT', '200', '51.23'],
 ['GE', '95', '40.37'],
 ['MSFT', '50', '65.10'],
 ['IBM', '100', '70.44']]

Базовая итерация с использованием циклов for

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

for row in rows:
    print(row)

Этот код будет проходить по каждой строке в списке rows и выводить ее. Вы увидите каждую строку данных из нашего CSV-файла, выведенную по одной.

['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']

Распаковка последовательностей в циклах

Python позволяет распаковывать последовательности непосредственно в цикле for. Это очень полезно, когда вы знаете структуру каждого элемента в последовательности. В нашем случае каждая строка в списке rows содержит три элемента: имя, количество акций и цену. Мы можем распаковать эти элементы непосредственно в цикле for.

for name, shares, price in rows:
    print(name, shares, price)

Этот код распакует каждую строку в переменные name, shares и price, а затем выведет их. Вы увидите данные, выведенные в более читаемом формате.

AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44

Если вам не нужны некоторые значения, вы можете использовать _ в качестве заполнителя, чтобы указать, что вы не заинтересованы в этих значениях. Например, если вы хотите вывести только имя и цену, вы можете использовать следующий код:

for name, _, price in rows:
    print(name, price)

Этот код будет игнорировать второй элемент в каждой строке и выводить только имя и цену.

AA 32.20
IBM 91.10
CAT 83.44
MSFT 51.23
GE 40.37
MSFT 65.10
IBM 70.44

Расширенная распаковка с использованием оператора *

Для более продвинутой распаковки вы можете использовать оператор * в качестве подстановочного знака. Это позволяет собирать несколько элементов в список. Давайте сгруппируем наши данные по имени, используя эту технику.

from collections import defaultdict

byname = defaultdict(list)
for name, *data in rows:
    byname[name].append(data)

## Print the data for IBM
print(byname['IBM'])

## Iterate through IBM's data
for shares, price in byname['IBM']:
    print(shares, price)

В этом коде мы сначала импортируем класс defaultdict из модуля collections. defaultdict - это словарь, который автоматически создает новое значение (в данном случае, пустой список), если ключ не существует. Затем мы используем оператор * для сбора всех элементов, кроме первого, в список с именем data. Мы сохраняем этот список в словаре byname, сгруппированном по имени. Наконец, мы выводим данные для IBM и проходим по ним, чтобы вывести количество акций и цену.

Вывод:

[['50', '91.10'], ['100', '70.44']]
50 91.10
100 70.44

В этом примере *data собирает все элементы, кроме первого, в список, который мы затем сохраняем в словаре, сгруппированном по имени. Это мощная техника для обработки данных с последовательностями переменной длины.

Использование функций enumerate() и zip()

В этом шаге мы рассмотрим две чрезвычайно полезные встроенные функции в Python, которые являются важными для итерации: enumerate() и zip(). Эти функции могут значительно упростить ваш код при работе с последовательностями.

Подсчет с использованием enumerate()

При итерации по последовательности вам часто может понадобиться отслеживать индекс или позицию каждого элемента. Именно здесь функция enumerate() пригодится. Функция enumerate() принимает последовательность в качестве входных данных и возвращает пары (индекс, значение) для каждого элемента в этой последовательности.

Если вы следовали инструкциям в интерпретаторе Python из предыдущего шага, вы можете продолжить использовать его. Если нет, запустите новую сессию. Вот как вы можете подготовить данные, если вы начинаете с чистого листа:

## If you're starting a new session, reload the data first:
## import csv
## f = open('portfolio.csv')
## f_csv = csv.reader(f)
## headers = next(f_csv)
## rows = list(f_csv)

## Use enumerate to get row numbers
for rowno, row in enumerate(rows):
    print(rowno, row)

При запуске вышеуказанного кода функция enumerate(rows) сгенерирует пары индекса (начиная с 0) и соответствующей строки из последовательности rows. Затем цикл for распакует эти пары в переменные rowno и row, и мы выведем их на экран.

Вывод:

0 ['AA', '100', '32.20']
1 ['IBM', '50', '91.10']
2 ['CAT', '150', '83.44']
3 ['MSFT', '200', '51.23']
4 ['GE', '95', '40.37']
5 ['MSFT', '50', '65.10']
6 ['IBM', '100', '70.44']

Мы можем сделать код еще более читаемым, объединив enumerate() с распаковкой. Распаковка позволяет нам напрямую присваивать элементы последовательности отдельным переменным.

for rowno, (name, shares, price) in enumerate(rows):
    print(rowno, name, shares, price)

В этом коде мы используем дополнительную пару скобок вокруг (name, shares, price) для правильной распаковки данных строки. Функция enumerate(rows) по-прежнему возвращает индекс и строку, но теперь мы распаковываем строку в переменные name, shares и price.

Вывод:

0 AA 100 32.20
1 IBM 50 91.10
2 CAT 150 83.44
3 MSFT 200 51.23
4 GE 95 40.37
5 MSFT 50 65.10
6 IBM 100 70.44

Сопоставление данных с использованием zip()

Функция zip() - еще один мощный инструмент в Python. Она используется для объединения соответствующих элементов из нескольких последовательностей. Когда вы передаете несколько последовательностей в zip(), она создает итератор, который генерирует кортежи, где каждый кортеж содержит элементы из каждой входной последовательности на одной и той же позиции.

Давайте посмотрим, как мы можем использовать zip() с данными headers и row, с которыми мы работали.

## Recall the headers variable from earlier
print(headers)  ## Should show ['name', 'shares', 'price']

## Get the first row
row = rows[0]
print(row)      ## Should show ['AA', '100', '32.20']

## Use zip to pair column names with values
for col, val in zip(headers, row):
    print(col, val)

В этом коде zip(headers, row) берет последовательность headers и последовательность row и сопоставляет их соответствующие элементы. Затем цикл for распакует эти пары в col (для названия столбца из headers) и val (для значения из row), и мы выведем их на экран.

Вывод:

['name', 'shares', 'price']
['AA', '100', '32.20']
name AA
shares 100
price 32.20

Один из самых распространенных применений zip() - создание словарей из пар ключ - значение. В Python словарь - это коллекция пар ключ - значение.

## Create a dictionary from headers and row values
record = dict(zip(headers, row))
print(record)

Здесь zip(headers, row) создает пары названий столбцов и значений, а функция dict() берет эти пары и преобразует их в словарь.

Вывод:

{'name': 'AA', 'shares': '100', 'price': '32.20'}

Мы можем расширить эту идею и преобразовать все строки в нашей последовательности rows в словари.

## Convert all rows to dictionaries
for row in rows:
    record = dict(zip(headers, row))
    print(record)

В этом цикле для каждой строки в rows мы используем zip(headers, row) для создания пар ключ - значение, а затем dict() для преобразования этих пар в словарь. Эта техника очень распространена в приложениях для обработки данных, особенно при работе с CSV-файлами, где первая строка содержит заголовки.

Вывод:

{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'IBM', 'shares': '50', 'price': '91.10'}
{'name': 'CAT', 'shares': '150', 'price': '83.44'}
{'name': 'MSFT', 'shares': '200', 'price': '51.23'}
{'name': 'GE', 'shares': '95', 'price': '40.37'}
{'name': 'MSFT', 'shares': '50', 'price': '65.10'}
{'name': 'IBM', 'shares': '100', 'price': '70.44'}

Генераторные выражения и эффективность использования памяти

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

Понимание генераторных выражений

Генераторное выражение похоже на списочное включение, но есть важное различие. Когда вы используете списочное включение, Python сразу создает список со всеми результатами. Это может занять много памяти, особенно если вы работаете с большим набором данных. С другой стороны, генераторное выражение порождает результаты по одному по мере необходимости. Это означает, что оно не нужно хранить все результаты в памяти сразу, что может сэкономить значительное количество памяти.

Давайте рассмотрим простой пример, чтобы понять, как это работает:

## Start a new Python session if needed
## python3

## List comprehension (creates a list in memory)
nums = [1, 2, 3, 4, 5]
squares_list = [x*x for x in nums]
print(squares_list)

## Generator expression (creates a generator object)
squares_gen = (x*x for x in nums)
print(squares_gen)  ## This doesn't print the values, just the generator object

## Iterate through the generator to get values
for n in squares_gen:
    print(n)

При запуске этого кода вы увидите следующий вывод:

[1, 4, 9, 16, 25]
<generator object <genexpr> at 0x7f...>
1
4
9
16
25

Важно отметить, что генераторы можно пройти только один раз. После того, как вы переберете все значения в генераторе, он исчерпается, и вы не сможете получить эти значения снова.

## Try to iterate again over the same generator
for n in squares_gen:
    print(n)  ## Nothing will be printed, as the generator is already exhausted

Вы также можете вручную получать значения из генератора по одному с использованием функции next().

## Create a fresh generator
squares_gen = (x*x for x in nums)

## Get values one by one
print(next(squares_gen))  ## 1
print(next(squares_gen))  ## 4
print(next(squares_gen))  ## 9

Когда в генераторе больше нет значений, вызов next() вызовет исключение StopIteration.

Генераторные функции с использованием yield

Для более сложной логики генераторов вы можете написать генераторные функции с использованием оператора yield. Генераторная функция - это особый тип функции, которая использует yield для возврата значений по одному, а не возврата одного результата сразу.

def squares(nums):
    for x in nums:
        yield x*x

## Use the generator function
for n in squares(nums):
    print(n)

При запуске этого кода вы увидите следующий вывод:

1
4
9
16
25

Снижение потребления памяти с помощью генераторных выражений

Теперь давайте посмотрим, как генераторные выражения могут сэкономить память при работе с большими наборами данных. Мы будем использовать файл данных о автобусах CTA, который довольно большой.

Сначала попробуем подход, который требует много памяти:

import tracemalloc
tracemalloc.start()

import readrides
rows = readrides.read_rides_as_dicts('ctabus.csv')
rt22 = [row for row in rows if row['route'] == '22']
max_row = max(rt22, key=lambda row: int(row['rides']))
print(max_row)

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

Теперь выйдите из Python и перезапустите его, чтобы сравнить с подходом на основе генераторов:

exit() python3
import tracemalloc
tracemalloc.start()

import csv
f = open('ctabus.csv')
f_csv = csv.reader(f)
headers = next(f_csv)

## Use generator expressions for all operations
rows = (dict(zip(headers, row)) for row in f_csv)
rt22 = (row for row in rows if row['route'] == '22')
max_row = max(rt22, key=lambda row: int(row['rides']))
print(max_row)

## Check memory usage
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

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

Генераторные выражения с функциями-редукторами

Генераторные выражения особенно полезны в сочетании с функциями, такими как sum(), min(), max(), any() и all(), которые обрабатывают целую последовательность и возвращают один результат.

Давайте рассмотрим несколько примеров:

from readport import read_portfolio
portfolio = read_portfolio('portfolio.csv')

## Calculate the total value of the portfolio
total_value = sum(s['shares']*s['price'] for s in portfolio)
print(f"Total portfolio value: {total_value}")

## Find the minimum number of shares in any holding
min_shares = min(s['shares'] for s in portfolio)
print(f"Minimum shares in any holding: {min_shares}")

## Check if any stock is IBM
has_ibm = any(s['name'] == 'IBM' for s in portfolio)
print(f"Portfolio contains IBM: {has_ibm}")

## Check if all stocks are IBM
all_ibm = all(s['name'] == 'IBM' for s in portfolio)
print(f"All stocks are IBM: {all_ibm}")

## Count IBM shares
ibm_shares = sum(s['shares'] for s in portfolio if s['name'] == 'IBM')
print(f"Total IBM shares: {ibm_shares}")

Генераторные выражения также полезны для операций со строками. Вот как можно объединить значения в кортеже:

s = ('GOOG', 100, 490.10)
## This would fail: ','.join(s)
## Use a generator expression to convert all items to strings
joined = ','.join(str(x) for x in s)
print(joined)  ## Output: 'GOOG,100,490.1'

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

Резюме

В этом практическом занятии вы изучили несколько мощных методов итерации в Python. Во - первых, вы овладели базовыми методами итерации и распаковки последовательностей, используя циклы for для перебора последовательностей и распаковки их в отдельные переменные. Во - вторых, вы рассмотрели встроенные функции, такие как enumerate() для отслеживания индексов во время итерации и zip() для сопоставления элементов из разных последовательностей.

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