Nivel bajo de creación de clases

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 los pasos de bajo nivel implicados en la creación de una clase en Python. Comprender cómo se construyen las clases utilizando la función type() proporciona una comprensión más profunda de las características orientadas a objetos de Python.

También implementarás técnicas personalizadas de creación de clases. Los archivos validate.py y structure.py se modificarán durante este laboratorio, lo que te permitirá aplicar tus nuevos conocimientos en un contexto práctico.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") subgraph Lab Skills python/conditional_statements -.-> lab-132517{{"Nivel bajo de creación de clases"}} python/function_definition -.-> lab-132517{{"Nivel bajo de creación de clases"}} python/classes_objects -.-> lab-132517{{"Nivel bajo de creación de clases"}} python/constructor -.-> lab-132517{{"Nivel bajo de creación de clases"}} end

Creación manual de clases

En la programación en Python, las clases son un concepto fundamental que te permite agrupar datos y funciones. Por lo general, definimos clases utilizando la sintaxis estándar de Python. Por ejemplo, aquí está una simple clase Stock. Esta clase representa una acción con atributos como name, shares y price, y tiene métodos para calcular el costo y vender acciones.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

Pero, ¿alguna vez te has preguntado cómo Python realmente crea una clase detrás de escena? ¿Qué pasaría si quisiéramos crear esta clase sin utilizar la sintaxis estándar de clases? En esta sección, exploraremos cómo se construyen las clases de Python a un nivel más bajo.

Lanzar la shell interactiva de Python

Para comenzar a experimentar con la creación manual de clases, necesitamos abrir una shell interactiva de Python. Esta shell nos permite ejecutar código Python línea por línea, lo cual es excelente para aprender y probar.

Abre una terminal en WebIDE y comienza la shell interactiva de Python escribiendo los siguientes comandos. El primer comando cd ~/project cambia el directorio actual al directorio del proyecto, y el segundo comando python3 inicia la shell interactiva de Python 3.

cd ~/project
python3

Definir métodos como funciones regulares

Antes de crear una clase manualmente, necesitamos definir los métodos que formarán parte de la clase. En Python, los métodos son simplemente funciones asociadas a una clase. Entonces, definamos los métodos que queremos en nuestra clase como funciones regulares de Python.

def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price

def cost(self):
    return self.shares * self.price

def sell(self, nshares):
    self.shares -= nshares

Aquí, la función __init__ es un método especial en las clases de Python. Se llama constructor y se utiliza para inicializar los atributos del objeto cuando se crea una instancia de la clase. El método cost calcula el costo total de las acciones, y el método sell reduce el número de acciones.

Crear un diccionario de métodos

Ahora que hemos definido nuestros métodos como funciones regulares, necesitamos organizarlos de una manera que Python pueda entender al crear la clase. Lo hacemos creando un diccionario que contendrá todos los métodos de nuestra clase.

methods = {
    '__init__': __init__,
    'cost': cost,
    'sell': sell
}

En este diccionario, las claves son los nombres de los métodos tal como se utilizarán en la clase, y los valores son los objetos de función reales que definimos anteriormente.

Utilizar el constructor type() para crear una clase

En Python, la función type() es una función incorporada que se puede utilizar para crear clases a un nivel más bajo. La función type() toma tres argumentos:

  1. El nombre de la clase: Esta es una cadena que representa el nombre de la clase que queremos crear.
  2. Una tupla de clases base: En Python, las clases pueden heredar de otras clases. Aquí, usamos (object,) lo que significa que nuestra clase hereda de la clase base object, que es la clase base de todas las clases en Python.
  3. Un diccionario que contiene métodos y atributos: Este es el diccionario que creamos anteriormente que contiene todos los métodos de nuestra clase.
Stock = type('Stock', (object,), methods)

Esta línea de código crea una nueva clase llamada Stock utilizando la función type(). La clase hereda de la clase object y tiene los métodos definidos en el diccionario methods.

Probar nuestra clase creada manualmente

Ahora que hemos creado nuestra clase manualmente, probémosla para asegurarnos de que funcione como se espera. Crearemos una instancia de nuestra nueva clase y llamaremos a sus métodos.

s = Stock('GOOG', 100, 490.10)
print(s.name)
print(s.cost())
s.sell(25)
print(s.shares)

En la primera línea, creamos una instancia de la clase Stock con el nombre GOOG, 100 acciones y un precio de 490.10. Luego imprimimos el nombre de la acción, calculamos e imprimimos el costo, vendemos 25 acciones y, finalmente, imprimimos el número de acciones restantes.

Deberías ver la siguiente salida:

GOOG
49010.0
75

Esta salida muestra que nuestra clase creada manualmente funciona igual que una clase creada utilizando la sintaxis estándar de Python. Demuestra que una clase es fundamentalmente solo un nombre, una tupla de clases base y un diccionario de métodos y atributos. La función type() simplemente construye un objeto de clase a partir de estos componentes.

Sal de la shell de Python cuando hayas terminado:

exit()

Creación de un ayudante para estructuras tipadas

En este paso, vamos a construir un ejemplo más práctico. Implementaremos una función que crea clases con validación de tipos. La validación de tipos es crucial ya que asegura que los datos asignados a los atributos de la clase cumplan con criterios específicos, como ser de un cierto tipo de dato o estar dentro de un rango particular. Esto ayuda a detectar errores temprano y hace que nuestro código sea más robusto.

Comprender la clase Structure

Primero, necesitamos abrir el archivo structure.py en el editor de WebIDE. Este archivo contiene una clase Structure básica. Esta clase proporciona la funcionalidad fundamental para inicializar y representar objetos estructurados. La inicialización significa configurar el objeto con los datos proporcionados, y la representación se refiere a cómo se muestra el objeto cuando lo imprimimos.

Para abrir el archivo, usaremos el siguiente comando en la terminal:

cd ~/project

Después de ejecutar este comando, estarás en el directorio correcto donde se encuentra el archivo structure.py. Cuando abras el archivo, notarás la clase Structure básica. Nuestro objetivo es extender esta clase para que admita la validación de tipos.

Implementar la función typed_structure

Ahora, agreguemos la función typed_structure al archivo structure.py. Esta función creará una nueva clase que herede de la clase Structure e incluya los validadores especificados. La herencia significa que la nueva clase tendrá toda la funcionalidad de la clase Structure y también puede agregar sus propias características. Los validadores se utilizan para comprobar si los valores asignados a los atributos de la clase son válidos.

Aquí está el código para la función typed_structure:

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

El parámetro clsname es el nombre que queremos dar a la nueva clase. El parámetro validators es un diccionario donde las claves son los nombres de los atributos y los valores son los objetos validador. La función type() se utiliza para crear una nueva clase de forma dinámica. Toma tres argumentos: el nombre de la clase, una tupla de clases base (en este caso, solo la clase Structure) y un diccionario de atributos de clase (los validadores).

Después de agregar esta función, tu archivo structure.py debería verse así:

## Structure class definition

class Structure:
    _fields = ()

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        ## Set all of the positional arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        ## Set the remaining keyword arguments
        for name, value in kwargs.items():
            setattr(self, name, value)

    def __repr__(self):
        attrs = ', '.join(f'{name}={getattr(self, name)!r}' for name in self._fields)
        return f'{type(self).__name__}({attrs})'

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

Probar la función typed_structure

Probemos nuestra función typed_structure utilizando los validadores del archivo validate.py. Estos validadores se utilizan para comprobar si los valores asignados a los atributos de la clase son del tipo correcto y cumplen con otros criterios.

Primero, abre una shell interactiva de Python. Usaremos los siguientes comandos en la terminal:

cd ~/project
python3

El primer comando nos lleva al directorio correcto, y el segundo comando inicia la shell interactiva de Python.

Ahora, importa los componentes necesarios y crea una estructura tipada:

from validate import String, PositiveInteger, PositiveFloat
from structure import typed_structure

## Create a Stock class with type validation
Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())

## Create a stock instance
s = Stock('GOOG', 100, 490.1)

## Test the instance
print(s.name)
print(s)

## Test validation
try:
    invalid_stock = Stock('AAPL', -10, 150.25)  ## Should raise an error
except ValueError as e:
    print(f"Validation error: {e}")

Importamos los validadores String, PositiveInteger y PositiveFloat del archivo validate.py. Luego usamos la función typed_structure para crear una clase Stock con validación de tipos. Creamos una instancia de la clase Stock y la probamos imprimiendo sus atributos. Finalmente, intentamos crear una instancia de acción no válida para probar la validación.

Deberías ver una salida similar a:

GOOG
Stock('GOOG', 100, 490.1)
Validation error: Expected a positive value

Cuando termines de probar, sal de la shell de Python:

exit()

Este ejemplo demuestra cómo podemos usar la función type() para crear clases personalizadas con reglas de validación específicas. Este enfoque es muy poderoso ya que nos permite generar clases de forma programática, lo que puede ahorrar mucho tiempo y hacer que nuestro código sea más flexible.

✨ Revisar Solución y Practicar

Generación eficiente de clases

Ahora que entiendes cómo crear clases utilizando la función type(), vamos a explorar una forma más eficiente de generar múltiples clases similares. Este método te ahorrará tiempo y reducirá la duplicación de código, haciendo que tu proceso de programación sea más fluido.

Comprender las clases de validación actuales

Primero, necesitamos abrir el archivo validate.py en WebIDE. Este archivo ya contiene varias clases de validación, que se utilizan para comprobar si los valores cumplen con ciertas condiciones. Estas clases incluyen Validator, Positive, PositiveInteger y PositiveFloat. Vamos a agregar una clase base Typed y varios validadores específicos de tipo a este archivo.

Para abrir el archivo, ejecuta el siguiente comando en la terminal:

cd ~/project

Agregar la clase de validación Typed

Comencemos agregando la clase de validación Typed. Esta clase se utilizará para comprobar si un valor es del tipo esperado.

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

En este código, expected_type se establece en object por defecto. Las subclases lo reemplazarán con el tipo específico que están comprobando. El método check utiliza la función isinstance para comprobar si el valor es del tipo esperado. Si no lo es, genera un TypeError.

Tradicionalmente, crearíamos validadores específicos de tipo de esta manera:

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

Sin embargo, este enfoque es repetitivo. Podemos hacerlo mejor utilizando el constructor type() para generar estas clases de forma dinámica.

Generar validadores de tipo de forma dinámica

Reemplazaremos las definiciones de clase individuales con un enfoque más eficiente.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Esto es lo que hace este código:

  1. Define una lista de tuplas. Cada tupla contiene un nombre de clase y el tipo de Python correspondiente.
  2. Utiliza una expresión generadora con la función type() para crear cada clase. La función type() toma tres argumentos: el nombre de la clase, una tupla de clases base y un diccionario de atributos de clase.
  3. Utiliza globals().update() para agregar las clases recién creadas al espacio de nombres global. Esto hace que las clases sean accesibles en todo el módulo.

Tu archivo validate.py completo debería verse algo así:

## Basic validator classes

class Validator:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        self.check(value)
        instance.__dict__[self.name] = value

    @classmethod
    def check(cls, value):
        pass

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value <= 0:
            raise ValueError('Expected a positive value')
        super().check(value)

class PositiveInteger(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, int):
            raise TypeError('Expected an integer')
        super().check(value)

class PositiveFloat(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, float):
            raise TypeError('Expected a float')
        super().check(value)

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

## Generate type validators dynamically
_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Probar las clases generadas dinámicamente

Ahora, probemos nuestras clases de validación generadas dinámicamente. Primero, abre una shell interactiva de Python.

cd ~/project
python3

Una vez que estés en la shell de Python, importa y prueba nuestros validadores.

from validate import Integer, Float, String

## Test the Integer validator
i = Integer()
i.__set_name__(None, 'test_int')
try:
    i.check("not an integer")
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"Integer validation: {e}")

## Test the String validator
s = String()
s.__set_name__(None, 'test_str')
try:
    s.check(123)
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"String validation: {e}")

## Add a new validator class to the list
import sys
print("Current validator classes:", [cls for cls in dir() if cls in ['Integer', 'Float', 'String']])

Deberías ver una salida que muestre los errores de validación de tipo. Esto indica que nuestras clases generadas dinámicamente están funcionando correctamente.

Cuando termines de probar, sal de la shell de Python:

exit()

Expandir la generación dinámica de clases

Si quieres agregar más validadores de tipo, simplemente puedes actualizar la lista _typed_classes en validate.py.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str),
    ('List', list),
    ('Dict', dict),
    ('Bool', bool)
]

Este enfoque proporciona una forma poderosa y eficiente de generar múltiples clases similares sin escribir código repetitivo. Te permite escalar fácilmente tu aplicación a medida que crecen tus requisitos.

✨ Revisar Solución y Practicar

Resumen

En este laboratorio, has aprendido sobre los mecanismos de bajo nivel de creación de clases en Python. Primero, dominaste cómo crear manualmente una clase utilizando el constructor type(), que requiere un nombre de clase, una tupla de clases base y un diccionario de métodos. Segundo, implementaste una función typed_structure para crear dinámicamente clases con capacidades de validación.

Además, utilizaste el constructor type() junto con globals().update() para generar de manera eficiente múltiples clases similares, evitando así el código repetitivo. Estas técnicas ofrecen formas poderosas de crear y personalizar clases de forma programática, útiles en marcos (frameworks), bibliotecas y metaprogramación. Comprender estos mecanismos subyacentes profundiza tu comprensión de las características orientadas a objetos de Python y permite una programación más avanzada.