Funciones de orden superior en Python

PythonPythonBeginner
Practicar Ahora

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

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás sobre las funciones de orden superior en Python. Las funciones de orden superior pueden aceptar otras funciones como argumentos o devolver funciones como resultados. Este concepto es crucial en la programación funcional y te permite escribir código más modular y reutilizable.

Comprenderás qué son las funciones de orden superior, crearás una que tome una función como argumento, refactorizarás funciones existentes para utilizar una función de orden superior y utilizarás la función incorporada map() de Python. El archivo reader.py se modificará durante el laboratorio.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/lambda_functions("Lambda Functions") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/AdvancedTopicsGroup -.-> python/iterators("Iterators") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") subgraph Lab Skills python/function_definition -.-> lab-132505{{"Funciones de orden superior en Python"}} python/arguments_return -.-> lab-132505{{"Funciones de orden superior en Python"}} python/lambda_functions -.-> lab-132505{{"Funciones de orden superior en Python"}} python/build_in_functions -.-> lab-132505{{"Funciones de orden superior en Python"}} python/classes_objects -.-> lab-132505{{"Funciones de orden superior en Python"}} python/iterators -.-> lab-132505{{"Funciones de orden superior en Python"}} python/data_collections -.-> lab-132505{{"Funciones de orden superior en Python"}} end

Comprendiendo la duplicación de código

Comencemos por observar el código actual en el archivo reader.py. En programación, examinar el código existente es un paso importante para entender cómo funcionan las cosas e identificar áreas de mejora. Puedes abrir el archivo reader.py en el WebIDE. Hay dos formas de hacer esto. Puedes hacer clic en el archivo en el explorador de archivos, o puedes ejecutar los siguientes comandos en la terminal. Estos comandos primero navegan al directorio del proyecto y luego muestran el contenido del archivo reader.py.

cd ~/project
cat reader.py

Cuando mires el código, notarás que hay dos funciones. Las funciones en Python son bloques de código que realizan una tarea específica. Aquí están las dos funciones y lo que hacen:

  1. csv_as_dicts(): Esta función toma datos CSV y los convierte en una lista de diccionarios. Un diccionario en Python es una colección de pares clave - valor, que es útil para almacenar datos de manera estructurada.
  2. csv_as_instances(): Esta función toma datos CSV y los convierte en una lista de instancias. Una instancia es un objeto creado a partir de una clase, que es un modelo para crear objetos.

Ahora, echemos un vistazo más detallado a estas dos funciones. Verás que son bastante similares. Ambas funciones siguen estos pasos:

  • Primero, inicializan una lista records vacía. Una lista en Python es una colección de elementos que pueden ser de diferentes tipos. Inicializar una lista vacía significa crear una lista sin elementos, que se utilizará para almacenar los datos procesados.
  • Luego, utilizan csv.reader() para analizar la entrada. Analizar significa analizar los datos de entrada para extraer información significativa. En este caso, csv.reader() nos ayuda a leer los datos CSV fila por fila.
  • Manejan los encabezados de la misma manera. Los encabezados en un archivo CSV son la primera fila que generalmente contiene los nombres de las columnas.
  • Después de eso, recorren cada fila en los datos CSV. Un bucle es una construcción de programación que te permite ejecutar un bloque de código varias veces.
  • Para cada fila, la procesan para crear un registro. Este registro puede ser un diccionario o una instancia, dependiendo de la función.
  • Añaden el registro a la lista records. Añadir significa agregar un elemento al final de la lista.
  • Finalmente, devuelven la lista records, que contiene todos los datos procesados.

Esta duplicación de código es un problema por varias razones. Cuando el código se duplica:

  • Se vuelve más difícil de mantener. Si necesitas hacer un cambio en el código, debes hacer el mismo cambio en múltiples lugares. Esto lleva más tiempo y esfuerzo.
  • Cualquier cambio debe implementarse en múltiples lugares. Esto aumenta la posibilidad de que olvides hacer el cambio en uno de los lugares, lo que puede causar un comportamiento inconsistente.
  • También aumenta la posibilidad de introducir errores. Los errores son fallos en el código que pueden hacer que se comporte de manera inesperada.

La única diferencia real entre estas dos funciones es cómo convierten una fila en un registro. Esta es una situación clásica en la que una función de orden superior puede ser muy útil. Una función de orden superior es una función que puede tomar otra función como argumento o devolver una función como resultado.

Veamos algunos ejemplos de uso de estas funciones para entender mejor cómo funcionan. El siguiente código muestra cómo usar csv_as_dicts() y 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

En el siguiente paso, crearemos una función de orden superior para eliminar esta duplicación de código. Esto hará que el código sea más mantenible y menos propenso a errores.

✨ Revisar Solución y Practicar

Creando una función de orden superior

En Python, una función de orden superior es una función que puede tomar otra función como argumento. Esto permite una mayor flexibilidad y reutilización de código. Ahora, vamos a crear una función de orden superior llamada convert_csv(). Esta función manejará las operaciones comunes de procesamiento de datos CSV, mientras te permite personalizar cómo se convierte cada fila del CSV en un registro.

Abre el archivo reader.py en el WebIDE. Vamos a agregar una función que tomará un iterable de datos CSV, una función de conversión y, opcionalmente, encabezados de columna. La función de conversión se utilizará para transformar cada fila del CSV en un registro.

Aquí está el código de la función convert_csv(). Cópialo y pégalo en tu archivo 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

Analicemos lo que hace esta función. Primero, inicializa una lista vacía llamada records para almacenar los registros convertidos. Luego, utiliza la función csv.reader() para leer las líneas de datos CSV. Si no se proporcionan encabezados, toma la primera fila como encabezados. Para cada fila subsiguiente, aplica la conversion_func para convertir la fila en un registro y lo agrega a la lista records. Finalmente, devuelve la lista de registros.

Ahora, necesitamos una función de conversión simple para probar nuestra función convert_csv(). Esta función tomará los encabezados y una fila y convertirá la fila en un diccionario utilizando los encabezados como claves.

Aquí está el código de la función make_dict(). Agrega esta función también a tu archivo reader.py:

def make_dict(headers, row):
    '''
    Convert a row to a dictionary using the provided headers
    '''
    return dict(zip(headers, row))

La función make_dict() utiliza la función zip() para emparejar cada encabezado con su valor correspondiente en la fila y luego crea un diccionario a partir de estos pares.

Vamos a probar estas funciones. Abre una shell de Python ejecutando los siguientes comandos en la terminal:

cd ~/project
python3 -i reader.py

La opción -i en el comando python3 inicia el intérprete de Python en modo interactivo e importa el archivo reader.py, para que podamos usar las funciones que acabamos de definir.

En la shell de Python, ejecuta el siguiente código para probar nuestras funciones:

## 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 el archivo portfolio.csv, utiliza la función convert_csv() con la función de conversión make_dict() para convertir los datos CSV en una lista de diccionarios y luego imprime el resultado.

Deberías ver una salida similar a la siguiente:

[{'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 salida muestra que nuestra función de orden superior convert_csv() funciona correctamente. Hemos creado con éxito una función que toma otra función como argumento, lo que nos da la capacidad de cambiar fácilmente cómo se convierten los datos CSV.

Para salir de la shell de Python, puedes escribir exit() o presionar Ctrl+D.

✨ Revisar Solución y Practicar

Refactorización de funciones existentes

Ahora, hemos creado una función de orden superior llamada convert_csv(). Las funciones de orden superior son funciones que pueden tomar otras funciones como argumentos o devolver funciones como resultados. Son un concepto poderoso en Python que puede ayudarnos a escribir código más modular y reutilizable. En esta sección, usaremos esta función de orden superior para refactorizar las funciones originales csv_as_dicts() y csv_as_instances(). La refactorización es el proceso de reorganizar el código existente sin cambiar su comportamiento externo, con el objetivo de mejorar su estructura interna, como eliminar la duplicación de código.

Comencemos abriendo el archivo reader.py en el WebIDE. Actualizaremos las funciones de la siguiente manera:

  1. Primero, reemplazaremos la función csv_as_dicts(). Esta función se utiliza para convertir líneas de datos CSV en una lista de diccionarios. Aquí está el nuevo 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)

En este código, definimos una función interna dict_converter que toma headers y row como argumentos. Utiliza una comprensión de diccionario para crear un diccionario donde las claves son los nombres de los encabezados y los valores son el resultado de aplicar la función de conversión de tipo correspondiente a los valores de la fila. Luego, llamamos a la función convert_csv() con la función dict_converter como argumento.

  1. A continuación, reemplazaremos la función csv_as_instances(). Esta función se utiliza para convertir líneas de datos CSV en una lista de instancias de una clase dada. Aquí está el nuevo 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)

En este código, definimos una función interna instance_converter que toma headers y row como argumentos. Llama al método de clase from_row de la clase cls dada para crear una instancia a partir de la fila. Luego, llamamos a la función convert_csv() con la función instance_converter como argumento.

Después de refactorizar estas funciones, debemos probarlas para asegurarnos de que sigan funcionando como se espera. Para hacer esto, ejecutaremos los siguientes comandos en una shell de Python:

cd ~/project
python3 -i reader.py

El comando cd ~/project cambia el directorio de trabajo actual al directorio project. El comando python3 -i reader.py ejecuta el archivo reader.py en modo interactivo, lo que significa que podemos continuar ejecutando código Python después de que el archivo haya terminado de ejecutarse.

Una vez abierta la shell de Python, ejecutaremos el siguiente código para probar las funciones refactorizadas:

## 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])

En este código, primero definimos una clase Stock simple para pruebas. El método __init__ inicializa los atributos de una instancia de Stock. El método de clase from_row crea una instancia de Stock a partir de una fila de datos CSV. El método __repr__ proporciona una representación en cadena de la instancia de Stock.

Luego, probamos la función csv_as_dicts() abriendo el archivo portfolio.csv y pasándolo a la función junto con una lista de funciones de conversión de tipo. Imprimimos el primer diccionario de la lista resultante.

Finalmente, probamos la función csv_as_instances() abriendo el archivo portfolio.csv y pasándolo a la función junto con la clase Stock. Imprimimos la primera instancia de la lista resultante.

Si todo está funcionando correctamente, deberías ver una salida similar a la siguiente:

First dictionary: {'name': 'AA', 'shares': 100, 'price': 32.2}
First instance: Stock(AA, 100, 32.2)

Esta salida indica que nuestras funciones refactorizadas están funcionando correctamente. Hemos eliminado con éxito la duplicación de código mientras mantenemos la misma funcionalidad.

Para salir de la shell de Python, puedes escribir exit() o presionar Ctrl+D.

✨ Revisar Solución y Practicar

Usando la función map()

En Python, una función de orden superior es una función que puede tomar otra función como argumento o devolver una función como resultado. La función map() de Python es un excelente ejemplo de función de orden superior. Es una herramienta poderosa que te permite aplicar una función dada a cada elemento de un iterable, como una lista o una tupla. Después de aplicar la función a cada elemento, devuelve un iterador con los resultados. Esta característica hace que map() sea perfecta para procesar secuencias de datos, como las filas de un archivo CSV.

La sintaxis básica de la función map() es la siguiente:

map(function, iterable, ...)

Aquí, la function es la operación que quieres realizar en cada elemento del iterable. El iterable es una secuencia de elementos, como una lista o una tupla.

Veamos un ejemplo sencillo. Supongamos que tienes una lista de números y quieres elevar al cuadrado cada número de esa lista. Puedes usar la función map() para lograr esto. Así es como se hace:

numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x * x, numbers))
print(squared)  ## Output: [1, 4, 9, 16, 25]

En este ejemplo, primero definimos una lista llamada numbers. Luego, usamos la función map(). La función lambda lambda x: x * x es la operación que queremos realizar en cada elemento de la lista numbers. La función map() aplica esta función lambda a cada número de la lista. Dado que map() devuelve un iterador, lo convertimos a una lista usando la función list(). Finalmente, imprimimos la lista squared, que contiene los valores al cuadrado de los números originales.

Ahora, veamos cómo podemos usar la función map() para modificar nuestra función convert_csv(). Anteriormente, usamos un bucle for para iterar sobre las filas de los datos CSV. Ahora, reemplazaremos ese bucle for con la función 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 versión actualizada de la función convert_csv() hace exactamente lo mismo que la versión anterior, pero utiliza la función map() en lugar de un bucle for. La función lambda dentro de map() toma cada fila de los datos CSV y le aplica la conversion_func, junto con los encabezados.

Probemos esta función actualizada para asegurarnos de que funcione correctamente. Primero, abre tu terminal y navega hasta el directorio del proyecto. Luego, inicia la shell interactiva de Python con el archivo reader.py.

cd ~/project
python3 -i reader.py

Una vez que estés en la shell de Python, ejecuta el siguiente código para probar la función convert_csv() actualizada:

## 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

Después de ejecutar este código, deberías ver una salida similar a la siguiente:

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

Esta salida muestra que la función convert_csv() actualizada que utiliza la función map() funciona correctamente, y las funciones que dependen de ella también siguen funcionando como se espera.

Usar la función map() tiene varias ventajas:

  1. Puede ser más concisa que un bucle for. En lugar de escribir varias líneas de código para un bucle for, puedes lograr el mismo resultado con una sola línea usando map().
  2. Comunica claramente tu intención de transformar cada elemento de una secuencia. Cuando ves map(), inmediatamente sabes que estás aplicando una función a cada elemento de un iterable.
  3. Puede ser más eficiente en términos de memoria porque devuelve un iterador. Un iterador genera valores sobre la marcha, lo que significa que no almacena todos los resultados en memoria a la vez. En nuestro ejemplo, convertimos el iterador devuelto por map() a una lista, pero en algunos casos, puedes trabajar directamente con el iterador para ahorrar memoria.

Para salir de la shell de Python, puedes escribir exit() o presionar Ctrl+D.

✨ Revisar Solución y Practicar

Resumen

En este laboratorio, has aprendido sobre las funciones de orden superior en Python y cómo contribuyen a escribir código más modular y mantenible. Primero, identificaste la duplicación de código en dos funciones similares. Luego, creaste una función de orden superior convert_csv() que acepta una función de conversión como argumento y refactorizaste las funciones originales para usarla. Finalmente, actualizaste la función de orden superior para utilizar la función incorporada map() de Python.

Estas técnicas son herramientas poderosas en el arsenal de un programador de Python. Las funciones de orden superior promueven la reutilización del código y la separación de responsabilidades, mientras que pasar funciones como argumentos permite un comportamiento más flexible y personalizable. Funciones como map() ofrecen formas concisas de transformar datos. Dominar estos conceptos te permite escribir código Python más conciso, mantenible y menos propenso a errores.