Uso práctico de la herencia

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 cómo utilizar la herencia para escribir código extensible y crear una aplicación práctica que muestre datos en múltiples formatos. También entenderás cómo utilizar clases base abstractas y sus implementaciones concretas.

Además, implementarás un patrón de fábrica (factory pattern) para simplificar la selección de clases. El archivo que modificarás es tableformat.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("Polymorphism") subgraph Lab Skills python/function_definition -.-> lab-132495{{"Uso práctico de la herencia"}} python/classes_objects -.-> lab-132495{{"Uso práctico de la herencia"}} python/inheritance -.-> lab-132495{{"Uso práctico de la herencia"}} python/polymorphism -.-> lab-132495{{"Uso práctico de la herencia"}} end

Comprendiendo el Problema

En este laboratorio, vamos a aprender sobre la herencia en Python y cómo puede ayudarnos a crear código que sea tanto extensible como adaptable. La herencia es un concepto poderoso en la programación orientada a objetos (object - oriented programming) donde una clase puede heredar atributos y métodos de otra clase. Esto nos permite reutilizar código y construir funcionalidades más complejas sobre el código existente.

Comencemos por observar la función existente print_table(). Esta es la función que mejoraremos para que sea más flexible en términos de formatos de salida.

Primero, debes abrir el archivo tableformat.py en el editor del WebIDE. La ruta a este archivo es la siguiente:

/home/labex/project/tableformat.py

Una vez que abras el archivo, verás la implementación actual de la función print_table(). Esta función está diseñada para formatear e imprimir datos tabulares. Toma dos entradas principales: una lista de registros (que son objetos) y una lista de nombres de campos. Basándose en estas entradas, imprime una tabla bien formateada.

Ahora, probemos esta función para ver cómo funciona. Abre una terminal en el WebIDE y ejecuta los siguientes comandos de Python. Estos comandos importan los módulos necesarios, leen datos de un archivo CSV y luego utilizan la función print_table() para mostrar los datos.

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
tableformat.print_table(portfolio, ['name', 'shares', 'price'])

Después de ejecutar estos comandos, deberías ver la siguiente salida:

      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

La salida se ve bien, pero esta función tiene una limitación. Actualmente, solo admite un formato de salida, que es texto plano. En escenarios del mundo real, es posible que desees mostrar tus datos en diferentes formatos como CSV, HTML u otros.

En lugar de hacer cambios en la función print_table() cada vez que queramos admitir un nuevo formato de salida, podemos utilizar la herencia para crear una solución más flexible. Así es como lo haremos:

  1. Definiremos una clase base TableFormatter. Esta clase tendrá métodos que se utilizarán para formatear datos. La clase base proporciona una estructura y funcionalidad comunes sobre las cuales todas las subclases pueden construir.
  2. Crearemos varias subclases. Cada subclase estará diseñada para un formato de salida diferente. Por ejemplo, una subclase podría ser para la salida en formato CSV, otra para la salida en formato HTML, y así sucesivamente. Estas subclases heredarán los métodos de la clase base y también pueden agregar su propia funcionalidad específica.
  3. Modificaremos la función print_table() para que pueda trabajar con cualquier formateador. Esto significa que podemos pasar diferentes subclases de la clase TableFormatter a la función print_table(), y esta será capaz de utilizar los métodos de formato adecuados.

Este enfoque tiene una gran ventaja. Nos permite agregar nuevos formatos de salida sin cambiar la funcionalidad central de la función print_table(). Entonces, a medida que cambien tus requisitos y necesites admitir más formatos de salida, puedes hacerlo fácilmente creando nuevas subclases.

Creando una Clase Base y Modificando la Función de Impresión

En programación, la herencia es un concepto poderoso que nos permite crear una jerarquía de clases. Para comenzar a utilizar la herencia para mostrar datos en diferentes formatos, primero necesitamos crear una clase base. Una clase base sirve como un modelo para otras clases, definiendo un conjunto común de métodos que sus subclases pueden heredar y sobrescribir.

Ahora, creemos una clase base que definirá la interfaz para todos los formateadores de tablas. Abre el archivo tableformat.py en el WebIDE y agrega el siguiente código en la parte superior del archivo:

class TableFormatter:
    """
    Base class for all table formatters.
    This class defines the interface that all formatters must implement.
    """
    def headings(self, headers):
        """
        Generate the table headings.
        """
        raise NotImplementedError()

    def row(self, rowdata):
        """
        Generate a single row of table data.
        """
        raise NotImplementedError()

La clase TableFormatter es una clase base abstracta. Una clase base abstracta es una clase que define métodos pero no proporciona implementaciones para ellos. En lugar de eso, espera que sus subclases proporcionen estas implementaciones. Las excepciones NotImplementedError se utilizan para indicar que estos métodos deben ser sobrescritos por las subclases. Si una subclase no sobrescribe estos métodos y tratamos de utilizarlos, se generará un error.

A continuación, necesitamos modificar la función print_table() para utilizar la clase TableFormatter. La función print_table() se utiliza para imprimir una tabla de datos a partir de una lista de objetos. Al modificarla para que utilice la clase TableFormatter, podemos hacer que la función sea más flexible y capaz de trabajar con diferentes formatos de tabla.

Reemplaza la función print_table() existente con el siguiente código:

def print_table(records, fields, formatter):
    """
    Print a table of data from a list of objects using the specified formatter.

    Args:
        records: A list of objects
        fields: A list of field names
        formatter: A TableFormatter object
    """
    formatter.headings(fields)
    for r in records:
        rowdata = [getattr(r, fieldname) for fieldname in fields]
        formatter.row(rowdata)

El cambio clave aquí es que print_table() ahora toma un parámetro formatter, que debe ser una instancia de TableFormatter o una subclase. Esto significa que podemos pasar diferentes formateadores de tablas a la función print_table(), y utilizará el formateador adecuado para imprimir la tabla. La función delega la responsabilidad de formateo al objeto formateador llamando a sus métodos headings() y row().

Probemos nuestros cambios intentando utilizar la clase base TableFormatter:

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Cuando ejecutes este código, deberías ver un error:

Traceback (most recent call last):
...
NotImplementedError

Este error se produce porque estamos tratando de utilizar directamente la clase base abstracta, pero no proporciona implementaciones para sus métodos. Dado que los métodos headings() y row() en la clase TableFormatter generan NotImplementedError, Python no sabe qué hacer cuando se llaman a estos métodos. En el siguiente paso, crearemos una subclase concreta que sí proporcione estas implementaciones.

✨ Revisar Solución y Practicar

Implementando un Formateador Concreto

Ahora que hemos definido nuestra clase base abstracta y actualizado la función print_table(), es hora de crear una clase de formateador concreto. Una clase de formateador concreto es aquella que proporciona implementaciones reales para los métodos definidos en la clase base abstracta. En nuestro caso, crearemos una clase que pueda formatear datos en una tabla de texto plano.

Agreguemos la siguiente clase a tu archivo tableformat.py. Esta clase heredará de la clase base abstracta TableFormatter e implementará los métodos headings() y row().

class TextTableFormatter(TableFormatter):
    """
    Formatter that generates a plain - text table.
    """
    def headings(self, headers):
        """
        Generate plain - text table headings.
        """
        print(' '.join('%10s' % h for h in headers))
        print(('-'*10 + ' ')*len(headers))

    def row(self, rowdata):
        """
        Generate a plain - text table row.
        """
        print(' '.join('%10s' % d for d in rowdata))

La clase TextTableFormatter hereda de TableFormatter. Esto significa que obtiene todas las propiedades y métodos de la clase TableFormatter, pero también proporciona sus propias implementaciones para los métodos headings() y row(). Estos métodos son responsables de formatear los encabezados y las filas de la tabla respectivamente. El método headings() imprime los encabezados de una manera bien formateada, seguido de una línea de guiones para separar los encabezados de los datos. El método row() formatea cada fila de datos de manera similar.

Ahora, probemos nuestro nuevo formateador. Utilizaremos los módulos stock, reader y tableformat para leer datos de un archivo CSV e imprimirlos utilizando nuestro nuevo formateador.

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TextTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Cuando ejecutes este código, deberías ver la misma salida que antes. Esto se debe a que nuestro nuevo formateador está diseñado para producir la misma tabla de texto plano que la función original print_table().

      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

Esta salida confirma que nuestro TextTableFormatter está funcionando correctamente. La ventaja de utilizar este enfoque es que hemos hecho nuestro código más modular y extensible. Al separar la lógica de formateo en una jerarquía de clases separada, podemos agregar fácilmente nuevos formatos de salida. Todo lo que necesitamos hacer es crear nuevas subclases de TableFormatter sin modificar la función print_table(). De esta manera, podremos admitir diferentes formatos de salida como CSV o HTML en el futuro.

✨ Revisar Solución y Practicar

Creando Formateadores Adicionales

En programación, la herencia es un concepto poderoso que nos permite crear nuevas clases basadas en las existentes. Esto ayuda a reutilizar código y hacer nuestros programas más extensibles. En esta parte del experimento, usaremos la herencia para crear dos nuevos formateadores para diferentes formatos de salida: CSV y HTML. Estos formateadores heredarán de una clase base, lo que significa que compartirán cierto comportamiento común mientras tendrán sus propias maneras únicas de formatear datos.

Ahora, agreguemos las siguientes clases a tu archivo tableformat.py. Estas clases definirán cómo formatear datos en formatos CSV y HTML respectivamente.

class CSVTableFormatter(TableFormatter):
    """
    Formatter that generates CSV formatted data.
    """
    def headings(self, headers):
        """
        Generate CSV headers.
        """
        print(','.join(headers))

    def row(self, rowdata):
        """
        Generate a CSV data row.
        """
        print(','.join(str(d) for d in rowdata))

class HTMLTableFormatter(TableFormatter):
    """
    Formatter that generates HTML table code.
    """
    def headings(self, headers):
        """
        Generate HTML table headers.
        """
        print('<tr>', end=' ')
        for header in headers:
            print(f'<th>{header}</th>', end=' ')
        print('</tr>')

    def row(self, rowdata):
        """
        Generate an HTML table row.
        """
        print('<tr>', end=' ')
        for data in rowdata:
            print(f'<td>{data}</td>', end=' ')
        print('</tr>')

La clase CSVTableFormatter está diseñada para formatear datos en el formato CSV (Comma-Separated Values, Valores Separados por Comas). El método headings toma una lista de encabezados e imprime cada uno separado por comas. El método row toma una lista de datos para una sola fila y también los imprime separados por comas.

Por otro lado, la clase HTMLTableFormatter se utiliza para generar código de tabla HTML. El método headings crea los encabezados de la tabla utilizando etiquetas HTML <th>, y el método row crea una fila de la tabla utilizando etiquetas HTML <td>.

Probemos estos nuevos formateadores para ver cómo funcionan.

  1. Primero, probemos el formateador CSV:
import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.CSVTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

En este código, primero importamos los módulos necesarios. Luego leemos datos de un archivo CSV llamado portfolio.csv y creamos instancias de la clase Stock. A continuación, creamos una instancia de la clase CSVTableFormatter. Finalmente, usamos la función print_table para imprimir los datos del portafolio en formato CSV.

Deberías ver la siguiente salida en formato CSV:

name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44
  1. Ahora probemos el formateador HTML:
formatter = tableformat.HTMLTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Aquí, creamos una instancia de la clase HTMLTableFormatter y usamos nuevamente la función print_table para imprimir los datos del portafolio en formato HTML.

Deberías ver la siguiente salida en formato HTML:

<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>

Como puedes ver, cada formateador produce una salida en un formato diferente, pero todos comparten la misma interfaz definida por la clase base TableFormatter. Este es un gran ejemplo del poder de la herencia y el polimorfismo. Podemos escribir código que funcione con la clase base, y automáticamente funcionará con cualquier subclase.

La función print_table() no necesita saber nada sobre el formateador específico que se está utilizando. Simplemente llama a los métodos definidos en la clase base, y la implementación adecuada se selecciona según el tipo de formateador proporcionado. Esto hace que nuestro código sea más flexible y fácil de mantener.

✨ Revisar Solución y Practicar

Creando una Función Fábrica

Al utilizar la herencia, un desafío común es que los usuarios tienen que recordar los nombres de las clases de formateadores específicas. Esto puede ser bastante molesto, especialmente a medida que aumenta el número de clases. Para simplificar este proceso, podemos crear una función fábrica. Una función fábrica es un tipo especial de función que crea y devuelve objetos. En nuestro caso, devolverá el formateador adecuado basado en un nombre de formato simple.

Agreguemos la siguiente función a tu archivo tableformat.py. Esta función tomará un nombre de formato como argumento y devolverá el objeto formateador correspondiente.

def create_formatter(format_name):
    """
    Create a formatter of the specified type.

    Args:
        format_name: Name of the formatter ('text', 'csv', 'html')

    Returns:
        A TableFormatter object

    Raises:
        ValueError: If format_name is not recognized
    """
    if format_name == 'text':
        return TextTableFormatter()
    elif format_name == 'csv':
        return CSVTableFormatter()
    elif format_name == 'html':
        return HTMLTableFormatter()
    else:
        raise ValueError(f'Unknown format {format_name}')

La función create_formatter() es una función fábrica. Verifica el argumento format_name que proporcionas. Si es 'text', crea y devuelve un objeto TextTableFormatter. Si es 'csv', devuelve un objeto CSVTableFormatter, y si es 'html', devuelve un objeto HTMLTableFormatter. Si el nombre de formato no es reconocido, genera una excepción ValueError. De esta manera, los usuarios pueden seleccionar fácilmente un formateador simplemente proporcionando un nombre simple, sin tener que conocer los nombres de las clases específicas.

Ahora, probemos la función fábrica. Usaremos algunas funciones y clases existentes para leer datos de un archivo CSV e imprimirlos en diferentes formatos.

import stock
import reader
from tableformat import create_formatter, print_table

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)

## Test with text formatter
formatter = create_formatter('text')
print("\nText Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

## Test with CSV formatter
formatter = create_formatter('csv')
print("\nCSV Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

## Test with HTML formatter
formatter = create_formatter('html')
print("\nHTML Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

En este código, primero importamos los módulos y funciones necesarios. Luego leemos datos del archivo portfolio.csv y creamos un objeto portfolio. Después de eso, probamos la función create_formatter() con diferentes nombres de formato: 'text', 'csv' y 'html'. Para cada formato, creamos un objeto formateador, imprimimos el nombre del formato y luego usamos la función print_table() para imprimir los datos del portfolio en el formato especificado.

Cuando ejecutes este código, deberías ver la salida en los tres formatos, separados por el nombre del formato:

Text Format:
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

CSV Format:
name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44

HTML Format:
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>

La función fábrica hace que el código sea más amigable para el usuario porque oculta los detalles de la instanciación de la clase. Los usuarios no necesitan saber cómo crear objetos formateadores; solo necesitan especificar el formato que desean.

Este patrón de usar una función fábrica para crear objetos es un patrón de diseño común en la programación orientada a objetos, conocido como el Patrón Fábrica. Proporciona una capa de abstracción entre el código cliente (el código que utiliza el formateador) y las clases de implementación reales (las clases de formateadores). Esto hace que el código sea más modular y fácil de usar.

Revisión de Conceptos Clave:

  1. Clase Base Abstracta: La clase TableFormatter sirve como una interfaz. Una interfaz define un conjunto de métodos que todas las clases que la implementan deben tener. En nuestro caso, todas las clases de formateadores deben implementar los métodos definidos en la clase TableFormatter.

  2. Herencia: Las clases de formateadores concretas, como TextTableFormatter, CSVTableFormatter y HTMLTableFormatter, heredan de la clase base TableFormatter. Esto significa que obtienen la estructura básica y los métodos de la clase base y pueden proporcionar sus propias implementaciones específicas.

  3. Polimorfismo: La función print_table() puede trabajar con cualquier formateador que implemente la interfaz requerida. Esto significa que puedes pasar diferentes objetos formateadores a la función print_table(), y funcionará correctamente con cada uno.

  4. Patrón Fábrica: La función create_formatter() simplifica la creación de objetos formateadores. Se encarga de los detalles de crear el objeto correcto basado en el nombre del formato, por lo que los usuarios no tienen que preocuparse por ello.

Al utilizar estos principios de programación orientada a objetos, hemos creado un sistema flexible y extensible para formatear datos tabulares en varios formatos de salida.

✨ Revisar Solución y Practicar

Resumen

En este laboratorio, has aprendido varios conceptos clave en la programación orientada a objetos. Has dominado cómo usar la herencia para crear código extensible, definir una clase base abstracta como interfaz e implementar subclases concretas que heredan de una clase base. Además, has aprendido a usar el polimorfismo para escribir código que funcione con diferentes tipos y el Patrón Fábrica para simplificar la creación de objetos.

Estos conceptos son herramientas poderosas para crear código mantenible y extensible. El sistema de formato de tablas que has construido muestra cómo la herencia puede crear un sistema flexible. Puedes aplicar estos principios a otras tareas de programación, haciendo que tu código sea modular, reutilizable y adaptable a los cambios en los requisitos.