Criação de Classes em Baixo Nível

Beginner

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

Introdução

Neste laboratório, você aprenderá sobre as etapas de baixo nível envolvidas na criação de uma classe em Python. Compreender como as classes são construídas usando a função type() fornece uma visão mais profunda dos recursos orientados a objetos do Python.

Você também implementará técnicas personalizadas de criação de classes. Os arquivos validate.py e structure.py serão modificados durante este laboratório, permitindo que você aplique seus novos conhecimentos em um ambiente prático.

Criação Manual de Classes

Na programação Python, as classes são um conceito fundamental que permite agrupar dados e funções. Normalmente, definimos classes usando a sintaxe padrão do Python. Por exemplo, aqui está uma classe Stock simples. Esta classe representa uma ação com atributos como name (nome), shares (ações) e price (preço), e possui métodos para calcular o custo e vender ações.

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

Mas você já se perguntou como o Python realmente cria uma classe nos bastidores? E se quiséssemos criar esta classe sem usar a sintaxe padrão de classe? Nesta seção, exploraremos como as classes Python são construídas em um nível inferior.

Iniciar o Shell Interativo do Python

Para começar a experimentar a criação manual de classes, precisamos abrir um shell interativo do Python. Este shell nos permite executar o código Python linha por linha, o que é ótimo para aprender e testar.

Abra um terminal no WebIDE e inicie o shell interativo do Python digitando os seguintes comandos. O primeiro comando cd ~/project altera o diretório atual para o diretório do projeto, e o segundo comando python3 inicia o shell interativo do Python 3.

cd ~/project
python3

Definindo Métodos como Funções Regulares

Antes de criarmos uma classe manualmente, precisamos definir os métodos que farão parte da classe. Em Python, os métodos são apenas funções que estão associadas a uma classe. Então, vamos definir os métodos que queremos em nossa classe como funções Python regulares.

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

Aqui, a função __init__ é um método especial nas classes Python. É chamado de construtor e é usado para inicializar os atributos do objeto quando uma instância da classe é criada. O método cost calcula o custo total das ações, e o método sell reduz o número de ações.

Criando um Dicionário de Métodos

Agora que definimos nossos métodos como funções regulares, precisamos organizá-los de uma forma que o Python possa entender ao criar a classe. Fazemos isso criando um dicionário que conterá todos os métodos para nossa classe.

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

Neste dicionário, as chaves são os nomes dos métodos como serão usados na classe, e os valores são os objetos de função reais que definimos anteriormente.

Usando o Construtor type() para Criar uma Classe

Em Python, a função type() é uma função embutida que pode ser usada para criar classes em um nível inferior. A função type() recebe três argumentos:

  1. O nome da classe: Esta é uma string que representa o nome da classe que queremos criar.
  2. Uma tupla de classes base: Em Python, as classes podem herdar de outras classes. Aqui, usamos (object,) o que significa que nossa classe herda da classe base object, que é a classe base para todas as classes em Python.
  3. Um dicionário contendo métodos e atributos: Este é o dicionário que criamos anteriormente que contém todos os métodos da nossa classe.
Stock = type('Stock', (object,), methods)

Esta linha de código cria uma nova classe chamada Stock usando a função type(). A classe herda da classe object e possui os métodos definidos no dicionário methods.

Testando Nossa Classe Criada Manualmente

Agora que criamos nossa classe manualmente, vamos testá-la para garantir que ela funcione como esperado. Criaremos uma instância de nossa nova classe e chamaremos seus métodos.

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

Na primeira linha, criamos uma instância da classe Stock com o nome GOOG, 100 ações e um preço de 490.10. Em seguida, imprimimos o nome da ação, calculamos e imprimimos o custo, vendemos 25 ações e, finalmente, imprimimos o número restante de ações.

Você deve ver a seguinte saída:

GOOG
49010.0
75

Esta saída mostra que nossa classe criada manualmente funciona como uma classe criada usando a sintaxe padrão do Python. Demonstra que uma classe é fundamentalmente apenas um nome, uma tupla de classes base e um dicionário de métodos e atributos. A função type() simplesmente constrói um objeto de classe a partir desses componentes.

Saia do shell do Python quando terminar:

exit()

Criando um Auxiliar de Estrutura Tipada

Nesta etapa, vamos construir um exemplo mais prático. Implementaremos uma função que cria classes com validação de tipo. A validação de tipo é crucial, pois garante que os dados atribuídos aos atributos da classe atendam a critérios específicos, como serem de um determinado tipo de dados ou estarem dentro de uma faixa específica. Isso ajuda a detectar erros precocemente e torna nosso código mais robusto.

Entendendo a Classe Structure

Primeiro, precisamos abrir o arquivo structure.py no editor WebIDE. Este arquivo contém uma classe Structure básica. Esta classe fornece a funcionalidade fundamental para inicializar e representar objetos estruturados. Inicialização significa configurar o objeto com os dados fornecidos, e representação é sobre como o objeto é exibido quando o imprimimos.

Para abrir o arquivo, usaremos o seguinte comando no terminal:

cd ~/project

Após executar este comando, você estará no diretório correto onde o arquivo structure.py está localizado. Ao abrir o arquivo, você notará a classe Structure básica. Nosso objetivo é estender esta classe para suportar a validação de tipo.

Implementando a Função typed_structure

Agora, vamos adicionar a função typed_structure ao arquivo structure.py. Esta função criará uma nova classe que herda da classe Structure e inclui os validadores especificados. Herança significa que a nova classe terá toda a funcionalidade da classe Structure e também poderá adicionar seus próprios recursos. Os validadores são usados para verificar se os valores atribuídos aos atributos da classe são válidos.

Aqui está o código para a função 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

O parâmetro clsname é o nome que queremos dar à nova classe. O parâmetro validators é um dicionário onde as chaves são os nomes dos atributos e os valores são os objetos validadores. A função type() é usada para criar uma nova classe dinamicamente. Ela recebe três argumentos: o nome da classe, uma tupla de classes base (neste caso, apenas a classe Structure) e um dicionário de atributos de classe (os validadores).

Após adicionar esta função, seu arquivo structure.py deve ter a seguinte aparência:

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

Testando a Função typed_structure

Vamos testar nossa função typed_structure usando os validadores do arquivo validate.py. Esses validadores são usados para verificar se os valores atribuídos aos atributos da classe são do tipo correto e atendem a outros critérios.

Primeiro, abra um shell interativo do Python. Usaremos os seguintes comandos no terminal:

cd ~/project
python3

O primeiro comando nos leva ao diretório correto, e o segundo comando inicia o shell interativo do Python.

Agora, importe os componentes necessários e crie uma estrutura 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 os validadores String, PositiveInteger e PositiveFloat do arquivo validate.py. Em seguida, usamos a função typed_structure para criar uma classe Stock com validação de tipo. Criamos uma instância da classe Stock e a testamos imprimindo seus atributos. Finalmente, tentamos criar uma instância de ação inválida para testar a validação.

Você deve ver uma saída semelhante a:

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

Quando terminar de testar, saia do shell do Python:

exit()

Este exemplo demonstra como podemos usar a função type() para criar classes personalizadas com regras de validação específicas. Essa abordagem é muito poderosa, pois nos permite gerar classes programaticamente, o que pode economizar muito tempo e tornar nosso código mais flexível.

Geração Eficiente de Classes

Agora que você entende como criar classes usando a função type(), vamos explorar uma maneira mais eficiente de gerar várias classes semelhantes. Este método economizará tempo e reduzirá a duplicação de código, tornando seu processo de programação mais suave.

Entendendo as Classes Validadoras Atuais

Primeiro, precisamos abrir o arquivo validate.py no WebIDE. Este arquivo já contém várias classes validadoras, que são usadas para verificar se os valores atendem a certas condições. Essas classes incluem Validator, Positive, PositiveInteger e PositiveFloat. Adicionaremos uma classe base Typed e vários validadores específicos de tipo a este arquivo.

Para abrir o arquivo, execute o seguinte comando no terminal:

cd ~/project

Adicionando a Classe Validadora Typed

Vamos começar adicionando a classe validadora Typed. Esta classe será usada para verificar se um valor é do 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)

Neste código, expected_type é definido como object por padrão. As subclasses substituirão isso com o tipo específico que estão verificando. O método check usa a função isinstance para verificar se o valor é do tipo esperado. Caso contrário, ele levanta um TypeError.

Tradicionalmente, criaríamos validadores específicos de tipo assim:

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

No entanto, essa abordagem é repetitiva. Podemos fazer melhor usando o construtor type() para gerar essas classes dinamicamente.

Gerando Validadores de Tipo Dinamicamente

Substituiremos as definições de classe individuais por uma abordagem mais eficiente.

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

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

Aqui está o que este código faz:

  1. Define uma lista de tuplas. Cada tupla contém um nome de classe e o tipo Python correspondente.
  2. Usa uma expressão geradora com a função type() para criar cada classe. A função type() recebe três argumentos: o nome da classe, uma tupla de classes base e um dicionário de atributos de classe.
  3. Usa globals().update() para adicionar as classes recém-criadas ao namespace global. Isso torna as classes acessíveis em todo o módulo.

Seu arquivo validate.py completo deve ter uma aparência semelhante a esta:

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

Testando as Classes Geradas Dinamicamente

Agora, vamos testar nossas classes validadoras geradas dinamicamente. Primeiro, abra um shell interativo do Python.

cd ~/project
python3

Depois de entrar no shell do Python, importe e teste nossos 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']])

Você deve ver a saída mostrando os erros de validação de tipo. Isso indica que nossas classes geradas dinamicamente estão funcionando corretamente.

Quando terminar de testar, saia do shell do Python:

exit()

Expandindo a Geração Dinâmica de Classes

Se você quiser adicionar mais validadores de tipo, basta atualizar a lista _typed_classes em validate.py.

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

Essa abordagem fornece uma maneira poderosa e eficiente de gerar várias classes semelhantes sem escrever código repetitivo. Ele permite que você dimensione facilmente seu aplicativo à medida que seus requisitos crescem.

Resumo

Neste laboratório, você aprendeu sobre os mecanismos de baixo nível de criação de classes em Python. Primeiro, você dominou como criar manualmente uma classe usando o construtor type(), que requer um nome de classe, uma tupla de classes base e um dicionário de métodos. Em segundo lugar, você implementou uma função typed_structure para criar dinamicamente classes com capacidades de validação.

Além disso, você usou o construtor type() junto com globals().update() para gerar eficientemente várias classes semelhantes, evitando assim código repetitivo. Essas técnicas oferecem maneiras poderosas de criar e personalizar classes programaticamente, úteis em frameworks, bibliotecas e metaprogramação. Compreender esses mecanismos subjacentes aprofunda sua compreensão dos recursos orientados a objetos do Python e permite uma programação mais avançada.