Introdução
Neste laboratório, você aprenderá sobre funções de ordem superior em Python. Funções de ordem superior podem aceitar outras funções como argumentos ou retornar funções como resultados. Este conceito é crucial na programação funcional e permite que você escreva um código mais modular e reutilizável.
Você entenderá o que são funções de ordem superior, criará uma que aceita uma função como argumento, refatorará funções existentes para usar uma função de ordem superior e utilizará a função map() embutida do Python. O arquivo reader.py será modificado durante o laboratório.
Compreendendo a Duplicação de Código
Vamos começar analisando o código atual no arquivo reader.py. Em programação, examinar o código existente é um passo importante para entender como as coisas funcionam e identificar áreas para melhoria. Você pode abrir o arquivo reader.py no WebIDE. Existem duas maneiras de fazer isso. Você pode clicar no arquivo no explorador de arquivos ou pode executar os seguintes comandos no terminal. Esses comandos primeiro navegam até o diretório do projeto e, em seguida, exibem o conteúdo do arquivo reader.py.
cd ~/project
cat reader.py
Ao olhar para o código, você notará que existem duas funções. Funções em Python são blocos de código que executam uma tarefa específica. Aqui estão as duas funções e o que elas fazem:
csv_as_dicts(): Esta função recebe dados CSV e os converte em uma lista de dicionários. Um dicionário em Python é uma coleção de pares chave-valor, o que é útil para armazenar dados de forma estruturada.csv_as_instances(): Esta função recebe dados CSV e os converte em uma lista de instâncias. Uma instância é um objeto criado a partir de uma classe, que é um modelo para criar objetos.
Agora, vamos dar uma olhada mais de perto nessas duas funções. Você verá que elas são bastante semelhantes. Ambas as funções seguem estas etapas:
- Primeiro, elas inicializam uma lista
recordsvazia. Uma lista em Python é uma coleção de itens que podem ser de diferentes tipos. Inicializar uma lista vazia significa criar uma lista sem nenhum item, que será usada para armazenar os dados processados. - Em seguida, elas usam
csv.reader()para analisar a entrada. Analisar (parsing) significa analisar os dados de entrada para extrair informações significativas. Neste caso,csv.reader()nos ajuda a ler os dados CSV linha por linha. - Elas lidam com os cabeçalhos da mesma forma. Os cabeçalhos em um arquivo CSV são a primeira linha que geralmente contém os nomes das colunas.
- Depois disso, elas percorrem cada linha nos dados CSV. Um loop é uma construção de programação que permite que você execute um bloco de código várias vezes.
- Para cada linha, elas a processam para criar um registro. Este registro pode ser um dicionário ou uma instância, dependendo da função.
- Elas anexam o registro à lista
records. Anexar (appending) significa adicionar um item ao final da lista. - Finalmente, elas retornam a lista
records, que contém todos os dados processados.
Essa duplicação de código é um problema por vários motivos. Quando o código é duplicado:
- Torna-se mais difícil de manter. Se você precisar fazer uma alteração no código, você tem que fazer a mesma alteração em vários lugares. Isso leva mais tempo e esforço.
- Quaisquer alterações devem ser implementadas em vários lugares. Isso aumenta a chance de você esquecer de fazer a alteração em um dos lugares, levando a um comportamento inconsistente.
- Também aumenta a chance de introduzir bugs. Bugs são erros no código que podem fazer com que ele se comporte de forma inesperada.
A única diferença real entre essas duas funções é como elas convertem uma linha em um registro. Esta é uma situação clássica onde uma função de ordem superior pode ser muito útil. Uma função de ordem superior é uma função que pode receber outra função como argumento ou retornar uma função como resultado.
Vamos analisar alguns exemplos de uso dessas funções para entender melhor como elas funcionam. O código a seguir mostra como usar csv_as_dicts() e 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
Na próxima etapa, criaremos uma função de ordem superior para eliminar essa duplicação de código. Isso tornará o código mais fácil de manter e menos propenso a erros.
Criando uma Função de Ordem Superior
Em Python, uma função de ordem superior é uma função que pode receber outra função como argumento. Isso permite maior flexibilidade e reutilização de código. Agora, vamos criar uma função de ordem superior chamada convert_csv(). Esta função lidará com as operações comuns de processamento de dados CSV, permitindo que você personalize como cada linha do CSV é convertida em um registro.
Abra o arquivo reader.py no WebIDE. Vamos adicionar uma função que receberá um iterável de dados CSV, uma função de conversão e, opcionalmente, cabeçalhos de coluna. A função de conversão será usada para transformar cada linha do CSV em um registro.
Aqui está o código para a função convert_csv(). Copie e cole-o em seu arquivo 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
Vamos detalhar o que essa função faz. Primeiro, ela inicializa uma lista vazia chamada records para armazenar os registros convertidos. Em seguida, ela usa a função csv.reader() para ler as linhas de dados CSV. Se nenhum cabeçalho for fornecido, ela pega a primeira linha como cabeçalhos. Para cada linha subsequente, ela aplica a conversion_func para converter a linha em um registro e adiciona-o à lista records. Finalmente, ela retorna a lista de registros.
Agora, precisamos de uma função de conversão simples para testar nossa função convert_csv(). Esta função receberá os cabeçalhos e uma linha e converterá a linha em um dicionário usando os cabeçalhos como chaves.
Aqui está o código para a função make_dict(). Adicione esta função ao seu arquivo reader.py também:
def make_dict(headers, row):
'''
Convert a row to a dictionary using the provided headers
'''
return dict(zip(headers, row))
A função make_dict() usa a função zip() para emparelhar cada cabeçalho com seu valor correspondente na linha e, em seguida, cria um dicionário a partir desses pares.
Vamos testar essas funções. Abra um shell Python executando os seguintes comandos no terminal:
cd ~/project
python3 -i reader.py
A opção -i no comando python3 inicia o interpretador Python em modo interativo e importa o arquivo reader.py, para que possamos usar as funções que acabamos de definir.
No shell Python, execute o seguinte código para testar nossas funções:
## 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)
Este código abre o arquivo portfolio.csv, usa a função convert_csv() com a função de conversão make_dict() para converter os dados CSV em uma lista de dicionários e, em seguida, imprime o resultado.
Você deve ver uma saída semelhante à seguinte:
[{'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'}]
Esta saída mostra que nossa função de ordem superior convert_csv() funciona corretamente. Criamos com sucesso uma função que recebe outra função como argumento, o que nos dá a capacidade de alterar facilmente como os dados CSV são convertidos.
Para sair do shell Python, você pode digitar exit() ou pressionar Ctrl+D.
Refatorando Funções Existentes
Agora, criamos uma função de ordem superior chamada convert_csv(). Funções de ordem superior são funções que podem receber outras funções como argumentos ou retornar funções como resultados. Elas são um conceito poderoso em Python que pode nos ajudar a escrever um código mais modular e reutilizável. Nesta seção, usaremos essa função de ordem superior para refatorar as funções originais csv_as_dicts() e csv_as_instances(). Refatoração (Refactoring) é o processo de reestruturar o código existente sem alterar seu comportamento externo, visando melhorar sua estrutura interna, como eliminar a duplicação de código.
Vamos começar abrindo o arquivo reader.py no WebIDE. Atualizaremos as funções da seguinte forma:
- Primeiro, substituiremos a função
csv_as_dicts(). Esta função é usada para converter linhas de dados CSV em uma lista de dicionários. Aqui está o novo código:
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)
Neste código, definimos uma função interna dict_converter que recebe headers e row como argumentos. Ele usa uma compreensão de dicionário para criar um dicionário onde as chaves são os nomes dos cabeçalhos e os valores são o resultado da aplicação da função de conversão de tipo correspondente aos valores na linha. Em seguida, chamamos a função convert_csv() com a função dict_converter como argumento.
- Em seguida, substituiremos a função
csv_as_instances(). Esta função é usada para converter linhas de dados CSV em uma lista de instâncias de uma determinada classe. Aqui está o novo código:
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)
Neste código, definimos uma função interna instance_converter que recebe headers e row como argumentos. Ele chama o método de classe from_row da classe cls fornecida para criar uma instância a partir da linha. Em seguida, chamamos a função convert_csv() com a função instance_converter como argumento.
Após refatorar essas funções, precisamos testá-las para garantir que ainda funcionem conforme o esperado. Para fazer isso, executaremos os seguintes comandos em um shell Python:
cd ~/project
python3 -i reader.py
O comando cd ~/project altera o diretório de trabalho atual para o diretório project. O comando python3 -i reader.py executa o arquivo reader.py em modo interativo, o que significa que podemos continuar a executar o código Python após a conclusão da execução do arquivo.
Depois que o shell Python estiver aberto, executaremos o seguinte código para testar as funções refatoradas:
## 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])
Neste código, primeiro definimos uma classe Stock simples para teste. O método __init__ inicializa os atributos de uma instância Stock. O método de classe from_row cria uma instância Stock a partir de uma linha de dados CSV. O método __repr__ fornece uma representação de string da instância Stock.
Em seguida, testamos a função csv_as_dicts() abrindo o arquivo portfolio.csv e passando-o para a função junto com uma lista de funções de conversão de tipo. Imprimimos o primeiro dicionário na lista resultante.
Finalmente, testamos a função csv_as_instances() abrindo o arquivo portfolio.csv e passando-o para a função junto com a classe Stock. Imprimimos a primeira instância na lista resultante.
Se tudo estiver funcionando corretamente, você deverá ver uma saída semelhante à seguinte:
First dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
First instance: Stock(AA, 100, 32.2)
Esta saída indica que nossas funções refatoradas estão funcionando corretamente. Eliminamos com sucesso a duplicação de código, mantendo a mesma funcionalidade.
Para sair do shell Python, você pode digitar exit() ou pressionar Ctrl+D.
Usando a Função map()
Em Python, uma função de ordem superior é uma função que pode receber outra função como argumento ou retornar uma função como resultado. A função map() do Python é um ótimo exemplo de uma função de ordem superior. É uma ferramenta poderosa que permite aplicar uma determinada função a cada item em um iterável, como uma lista ou uma tupla. Após aplicar a função a cada item, ela retorna um iterador dos resultados. Esse recurso torna map() perfeito para processar sequências de dados, como linhas em um arquivo CSV.
A sintaxe básica da função map() é a seguinte:
map(function, iterable, ...)
Aqui, a function é a operação que você deseja realizar em cada item no iterable. O iterable é uma sequência de itens, como uma lista ou uma tupla.
Vamos analisar um exemplo simples. Suponha que você tenha uma lista de números e queira elevar ao quadrado cada número dessa lista. Você pode usar a função map() para conseguir isso. Veja como você pode fazer:
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x * x, numbers))
print(squared) ## Output: [1, 4, 9, 16, 25]
Neste exemplo, primeiro definimos uma lista chamada numbers. Em seguida, usamos a função map(). A função lambda lambda x: x * x é a operação que queremos realizar em cada item na lista numbers. A função map() aplica essa função lambda a cada número na lista. Como map() retorna um iterador, convertemos para uma lista usando a função list(). Finalmente, imprimimos a lista squared, que contém os valores ao quadrado dos números originais.
Agora, vamos dar uma olhada em como podemos usar a função map() para modificar nossa função convert_csv(). Anteriormente, usamos um loop for para iterar sobre as linhas nos dados CSV. Agora, substituiremos esse loop for pela função 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
Esta versão atualizada da função convert_csv() faz exatamente a mesma coisa que a versão anterior, mas usa a função map() em vez de um loop for. A função lambda dentro de map() pega cada linha dos dados CSV e aplica a conversion_func a ela, junto com os cabeçalhos.
Vamos testar esta função atualizada para garantir que ela funcione corretamente. Primeiro, abra seu terminal e navegue até o diretório do projeto. Em seguida, inicie o shell interativo do Python com o arquivo reader.py.
cd ~/project
python3 -i reader.py
Depois de entrar no shell Python, execute o seguinte código para testar a função convert_csv() atualizada:
## 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
Após executar este código, você deverá ver uma saída semelhante à seguinte:
{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'AA', 'shares': 100, 'price': 32.2}
Esta saída mostra que a função convert_csv() atualizada usando a função map() funciona corretamente, e as funções que dependem dela também continuam funcionando como esperado.
Usar a função map() tem várias vantagens:
- Pode ser mais conciso do que um loop
for. Em vez de escrever várias linhas de código para um loopfor, você pode obter o mesmo resultado com uma única linha usandomap(). - Comunica claramente sua intenção de transformar cada item em uma sequência. Quando você vê
map(), você sabe imediatamente que está aplicando uma função a cada item em um iterável. - Pode ser mais eficiente em termos de memória porque retorna um iterador. Um iterador gera valores sob demanda, o que significa que não armazena todos os resultados na memória de uma vez. Em nosso exemplo, convertemos o iterador retornado por
map()em uma lista, mas em alguns casos, você pode trabalhar diretamente com o iterador para economizar memória.
Para sair do shell Python, você pode digitar exit() ou pressionar Ctrl+D.
Resumo
Neste laboratório, você aprendeu sobre funções de ordem superior em Python e como elas contribuem para escrever um código mais modular e sustentável. Primeiro, você identificou a duplicação de código em duas funções semelhantes. Em seguida, você criou uma função de ordem superior convert_csv() que aceita uma função de conversão como argumento e refatorou as funções originais para usá-la. Finalmente, você atualizou a função de ordem superior para utilizar a função map() embutida do Python.
Essas técnicas são ativos poderosos no kit de ferramentas de um programador Python. Funções de ordem superior promovem a reutilização de código e a separação de preocupações, enquanto passar funções como argumentos permite um comportamento mais flexível e personalizável. Funções como map() oferecem maneiras concisas de transformar dados. Dominar esses conceitos permite que você escreva código Python que seja mais conciso, sustentável e menos propenso a erros.