Введение
В этом лабораторном занятии вы узнаете о функциях высшего порядка в Python. Функции высшего порядка могут принимать другие функции в качестве аргументов или возвращать функции в качестве результатов. Этот концепт является важным в функциональном программировании и позволяет вам писать более модульный и переиспользуемый код.
Вы поймете, что такое функции высшего порядка, создадите функцию, которая принимает другую функцию в качестве аргумента, переструктурируете существующие функции для использования функции высшего порядка и примените встроенную функцию map() в Python. Файл reader.py будет изменен в ходе лабораторного занятия.
Понимание дублирования кода
Начнем с рассмотрения текущего кода в файле reader.py. В программировании изучение существующего кода является важным шагом для понимания того, как все работает, и для выявления областей, подлежащих улучшению. Вы можете открыть файл reader.py в WebIDE. Сделать это можно двумя способами. Вы можете кликнуть на файл в проводнике файлов, или выполнить следующие команды в терминале. Эти команды сначала переходят в директорию проекта, а затем отображают содержимое файла reader.py.
cd ~/project
cat reader.py
При рассмотрении кода вы заметите, что там есть две функции. Функции в Python - это блоки кода, выполняющие определенную задачу. Вот эти две функции и их назначение:
csv_as_dicts(): Эта функция принимает данные в формате CSV и преобразует их в список словарей. Словарь в Python - это коллекция пар ключ - значение, которая полезна для хранения данных в структурированном виде.csv_as_instances(): Эта функция принимает данные в формате CSV и преобразует их в список экземпляров. Экземпляр - это объект, созданный из класса, который является шаблоном для создания объектов.
Теперь давайте более подробно рассмотрим эти две функции. Вы увидите, что они очень похожи. Обе функции выполняют следующие шаги:
- Сначала они инициализируют пустой список
records. Список в Python - это коллекция элементов, которые могут быть разных типов. Инициализация пустого списка означает создание списка без элементов, который будет использоваться для хранения обработанных данных. - Затем они используют
csv.reader()для разбора входных данных. Разбор означает анализ входных данных для извлечения значимой информации. В этом случаеcsv.reader()помогает нам считывать данные CSV построчно. - Они обрабатывают заголовки одинаково. Заголовки в файле CSV - это первая строка, которая обычно содержит имена столбцов.
- Затем они перебирают каждую строку в данных CSV. Цикл - это конструкция программирования, которая позволяет выполнять блок кода несколько раз.
- Для каждой строки они обрабатывают ее, чтобы создать запись. Эта запись может быть либо словарем, либо экземпляром, в зависимости от функции.
- Они добавляют запись в список
records. Добавление означает добавление элемента в конец списка. - Наконец, они возвращают список
records, который содержит все обработанные данные.
Это дублирование кода представляет проблему по нескольким причинам. Когда код дублируется:
- Его становится сложнее поддерживать. Если вам нужно внести изменения в код, вы должны сделать те же изменения в нескольких местах. Это требует больше времени и усилий.
- Любые изменения должны быть внесены в нескольких местах. Это увеличивает вероятность того, что вы забудете внести изменения в одном из мест, что приведет к неконсистентному поведению.
- Также увеличивается вероятность появления ошибок. Ошибки - это ошибки в коде, которые могут привести к неожиданному поведению.
Единственное реальное различие между этими двумя функциями - это то, как они преобразуют строку в запись. Это классическая ситуация, когда функция высшего порядка может быть очень полезной. Функция высшего порядка - это функция, которая может принимать другую функцию в качестве аргумента или возвращать функцию в качестве результата.
Давайте рассмотрим пример использования этих функций, чтобы лучше понять, как они работают. Следующий код показывает, как использовать csv_as_dicts() и csv_as_instances():
## Example of using csv_as_dicts
with open('portfolio.csv') as f:
portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0]) ## {'name': 'AA', 'shares': 100, 'price': 32.2}
## Example of using csv_as_instances
class Stock:
@classmethod
def from_row(cls, row):
return cls(row[0], int(row[1]), float(row[2]))
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
with open('portfolio.csv') as f:
portfolio = csv_as_instances(f, Stock)
print(portfolio[0].name, portfolio[0].shares, portfolio[0].price) ## AA 100 32.2
На следующем шаге мы создадим функцию высшего порядка, чтобы устранить это дублирование кода. Это сделает код более поддерживаемым и менее подверженным ошибкам.
Создание функции высшего порядка
В Python функция высшего порядка - это функция, которая может принимать другую функцию в качестве аргумента. Это позволяет добиться большей гибкости и повторного использования кода. Теперь давайте создадим функцию высшего порядка с именем convert_csv(). Эта функция будет обрабатывать общие операции по обработке данных в формате CSV, при этом позволяя вам настраивать, как каждая строка CSV преобразуется в запись.
Откройте файл reader.py в WebIDE. Мы собираемся добавить функцию, которая будет принимать итерируемый объект с данными в формате CSV, функцию преобразования и, по желанию, заголовки столбцов. Функция преобразования будет использоваться для преобразования каждой строки CSV в запись.
Вот код функции convert_csv(). Скопируйте и вставьте его в файл reader.py:
def convert_csv(lines, conversion_func, *, headers=None):
'''
Convert lines of CSV data using the provided conversion function
Args:
lines: An iterable containing CSV data
conversion_func: A function that takes headers and a row and returns a record
headers: Column headers (optional). If None, the first row is used as headers
Returns:
A list of records as processed by conversion_func
'''
records = []
rows = csv.reader(lines)
if headers is None:
headers = next(rows)
for row in rows:
record = conversion_func(headers, row)
records.append(record)
return records
Давайте разберем, что делает эта функция. Сначала она инициализирует пустой список с именем records для хранения преобразованных записей. Затем она использует функцию csv.reader() для чтения строк данных в формате CSV. Если заголовки не предоставлены, она берет первую строку в качестве заголовков. Для каждой последующей строки она применяет conversion_func для преобразования строки в запись и добавляет ее в список records. Наконец, она возвращает список записей.
Теперь нам нужна простая функция преобразования для тестирования нашей функции convert_csv(). Эта функция будет принимать заголовки и строку и преобразовывать строку в словарь, используя заголовки в качестве ключей.
Вот код функции make_dict(). Добавьте эту функцию также в файл reader.py:
def make_dict(headers, row):
'''
Convert a row to a dictionary using the provided headers
'''
return dict(zip(headers, row))
Функция make_dict() использует функцию zip() для сопоставления каждого заголовка с соответствующим значением в строке, а затем создает словарь из этих пар.
Давайте протестируем эти функции. Откройте оболочку Python, выполнив следующие команды в терминале:
cd ~/project
python3 -i reader.py
Опция -i в команде python3 запускает интерпретатор Python в интерактивном режиме и импортирует файл reader.py, так что мы можем использовать только что определенные функции.
В оболочке Python выполните следующий код для тестирования наших функций:
## Open the CSV file
lines = open('portfolio.csv')
## Convert to a list of dictionaries using our new function
result = convert_csv(lines, make_dict)
## Print the result
print(result)
Этот код открывает файл portfolio.csv, использует функцию convert_csv() с функцией преобразования make_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'}]
Этот вывод показывает, что наша функция высшего порядка convert_csv() работает правильно. Мы успешно создали функцию, которая принимает другую функцию в качестве аргумента, что позволяет нам легко изменять способ преобразования данных CSV.
Чтобы выйти из оболочки Python, вы можете ввести exit() или нажать Ctrl+D.
Рефакторинг существующих функций
Теперь мы создали функцию высшего порядка с именем convert_csv(). Функции высшего порядка - это функции, которые могут принимать другие функции в качестве аргументов или возвращать функции в качестве результатов. Это мощная концепция в Python, которая помогает нам писать более модульный и переиспользуемый код. В этом разделе мы используем эту функцию высшего порядка для рефакторинга исходных функций csv_as_dicts() и csv_as_instances(). Рефакторинг - это процесс переструктурирования существующего кода без изменения его внешнего поведения, с целью улучшения его внутренней структуры, например, устранения дублирования кода.
Начнем с открытия файла reader.py в WebIDE. Мы обновим функции следующим образом:
- Сначала заменим функцию
csv_as_dicts(). Эта функция используется для преобразования строк данных в формате CSV в список словарей. Вот новый код:
def csv_as_dicts(lines, types, *, headers=None):
'''
Convert lines of CSV data into a list of dictionaries
'''
def dict_converter(headers, row):
return {name: func(val) for name, func, val in zip(headers, types, row)}
return convert_csv(lines, dict_converter, headers=headers)
В этом коде мы определяем внутреннюю функцию dict_converter, которая принимает headers и row в качестве аргументов. Она использует генератор словаря для создания словаря, где ключами являются имена заголовков, а значениями - результаты применения соответствующих функций преобразования типов к значениям в строке. Затем мы вызываем функцию convert_csv() с функцией dict_converter в качестве аргумента.
- Далее заменим функцию
csv_as_instances(). Эта функция используется для преобразования строк данных в формате CSV в список экземпляров заданного класса. Вот новый код:
def csv_as_instances(lines, cls, *, headers=None):
'''
Convert lines of CSV data into a list of instances
'''
def instance_converter(headers, row):
return cls.from_row(row)
return convert_csv(lines, instance_converter, headers=headers)
В этом коде мы определяем внутреннюю функцию instance_converter, которая принимает headers и row в качестве аргументов. Она вызывает метод класса from_row заданного класса cls для создания экземпляра из строки. Затем мы вызываем функцию convert_csv() с функцией instance_converter в качестве аргумента.
После рефакторинга этих функций нам нужно протестировать их, чтобы убедиться, что они по-прежнему работают как ожидается. Для этого мы выполним следующие команды в оболочке Python:
cd ~/project
python3 -i reader.py
Команда cd ~/project изменяет текущую рабочую директорию на директорию project. Команда python3 -i reader.py запускает файл reader.py в интерактивном режиме, что означает, что мы можем продолжать выполнять код Python после завершения выполнения файла.
Как только оболочка Python откроется, мы выполним следующий код для тестирования рефакторингованных функций:
## Define a simple Stock class for testing
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@classmethod
def from_row(cls, row):
return cls(row[0], int(row[1]), float(row[2]))
def __repr__(self):
return f'Stock({self.name}, {self.shares}, {self.price})'
## Test csv_as_dicts
with open('portfolio.csv') as f:
portfolio_dicts = csv_as_dicts(f, [str, int, float])
print("First dictionary:", portfolio_dicts[0])
## Test csv_as_instances
with open('portfolio.csv') as f:
portfolio_instances = csv_as_instances(f, Stock)
print("First instance:", portfolio_instances[0])
В этом коде мы сначала определяем простой класс Stock для тестирования. Метод __init__ инициализирует атрибуты экземпляра Stock. Метод класса from_row создает экземпляр Stock из строки данных в формате CSV. Метод __repr__ предоставляет строковое представление экземпляра Stock.
Затем мы тестируем функцию csv_as_dicts(), открывая файл portfolio.csv и передавая его в функцию вместе со списком функций преобразования типов. Мы выводим первый словарь в результирующем списке.
Наконец, мы тестируем функцию csv_as_instances(), открывая файл portfolio.csv и передавая его в функцию вместе с классом Stock. Мы выводим первый экземпляр в результирующем списке.
Если все работает правильно, вы должны увидеть вывод, похожий на следующий:
First dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
First instance: Stock(AA, 100, 32.2)
Этот вывод показывает, что наши рефакторингованные функции работают правильно. Мы успешно устранили дублирование кода, сохранив при этом ту же функциональность.
Чтобы выйти из оболочки Python, вы можете ввести exit() или нажать Ctrl+D.
Использование функции map()
В Python функция высшего порядка - это функция, которая может принимать другую функцию в качестве аргумента или возвращать функцию в качестве результата. Функция map() в Python является отличным примером функции высшего порядка. Это мощный инструмент, который позволяет применить заданную функцию к каждому элементу итерируемого объекта, такого как список или кортеж. После применения функции к каждому элементу, она возвращает итератор с результатами. Эта особенность делает map() идеальным для обработки последовательностей данных, например, строк в файле CSV.
Базовый синтаксис функции map() выглядит следующим образом:
map(function, iterable, ...)
Здесь function - это операция, которую вы хотите выполнить над каждым элементом в iterable. iterable - это последовательность элементов, например, список или кортеж.
Рассмотрим простой пример. Предположим, у вас есть список чисел, и вы хотите возвести каждое число в этом списке в квадрат. Вы можете использовать функцию map() для достижения этого. Вот как это можно сделать:
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x * x, numbers))
print(squared) ## Output: [1, 4, 9, 16, 25]
В этом примере мы сначала определяем список с именем numbers. Затем мы используем функцию map(). Лямбда-функция lambda x: x * x - это операция, которую мы хотим выполнить над каждым элементом в списке numbers. Функция map() применяет эту лямбда-функцию к каждому числу в списке. Поскольку map() возвращает итератор, мы преобразуем его в список с помощью функции list(). Наконец, мы выводим список squared, который содержит квадраты исходных чисел.
Теперь давайте посмотрим, как можно использовать функцию map() для модификации нашей функции convert_csv(). Ранее мы использовали цикл for для перебора строк в данных CSV. Теперь мы заменим этот цикл for функцией map().
def convert_csv(lines, conversion_func, *, headers=None):
'''
Convert lines of CSV data using the provided conversion function
'''
rows = csv.reader(lines)
if headers is None:
headers = next(rows)
## Use map to apply conversion_func to each row
records = list(map(lambda row: conversion_func(headers, row), rows))
return records
Обновленная версия функции convert_csv() делает то же самое, что и предыдущая версия, но использует функцию map() вместо цикла for. Лямбда-функция внутри map() берет каждую строку из данных CSV и применяет к ней conversion_func вместе с заголовками.
Давайте протестируем эту обновленную функцию, чтобы убедиться, что она работает правильно. Сначала откройте терминал и перейдите в директорию проекта. Затем запустите интерактивную оболочку Python с файлом reader.py.
cd ~/project
python3 -i reader.py
Как только вы окажетесь в оболочке Python, запустите следующий код для тестирования обновленной функции convert_csv():
## Test the updated convert_csv function
with open('portfolio.csv') as f:
result = convert_csv(f, make_dict)
print(result[0]) ## Should print the first dictionary
## Test that csv_as_dicts still works
with open('portfolio.csv') as f:
portfolio = csv_as_dicts(f, [str, int, float])
print(portfolio[0]) ## Should print the first dictionary with converted types
После выполнения этого кода вы должны увидеть вывод, похожий на следующий:
{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'AA', 'shares': 100, 'price': 32.2}
Этот вывод показывает, что обновленная функция convert_csv(), использующая функцию map(), работает правильно, и функции, которые на нее основаны, также продолжают работать как ожидается.
Использование функции map() имеет несколько преимуществ:
- Он может быть более компактным, чем цикл
for. Вместо того, чтобы писать несколько строк кода для циклаfor, вы можете достичь того же результата одной строкой с использованиемmap(). - Он четко сообщает о намерении преобразовать каждый элемент в последовательности. Когда вы видите
map(), вы сразу понимаете, что вы применяете функцию к каждому элементу итерируемого объекта. - Он может быть более экономным по памяти, так как возвращает итератор. Итератор генерирует значения по мере необходимости, что означает, что он не хранит все результаты в памяти сразу. В нашем примере мы преобразовали итератор, возвращаемый
map(), в список, но в некоторых случаях вы можете работать непосредственно с итератором, чтобы сэкономить память.
Чтобы выйти из оболочки Python, вы можете ввести exit() или нажать Ctrl+D.
Резюме
В этом практическом занятии (лабораторной работе) вы узнали о функциях высшего порядка в Python и о том, как они способствуют написанию более модульного и поддерживаемого кода. Сначала вы обнаружили дублирование кода в двух похожих функциях. Затем вы создали функцию высшего порядка convert_csv(), которая принимает функцию преобразования в качестве аргумента, и рефакторингнули исходные функции для ее использования. Наконец, вы обновили функцию высшего порядка, чтобы использовать встроенную функцию map() Python.
Эти методы являются мощными инструментами в арсенале Python-разработчика. Функции высшего порядка способствуют повторному использованию кода и разделению ответственности, а передача функций в качестве аргументов позволяет добиться более гибкого и настраиваемого поведения. Функции, такие как map(), предлагают компактные способы преобразования данных. Освоение этих концепций позволяет вам писать Python-код, который более лаконичен, поддерживаем и менее подвержен ошибкам.