Comportamento da Herança em Python

Beginner

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

Introdução

Neste laboratório, você aprenderá sobre o comportamento da herança em Python. Especificamente, você se concentrará em como a função super() opera e como a herança cooperativa é implementada. Herança é um conceito fundamental na programação orientada a objetos, permitindo que classes herdem atributos e métodos de classes pai para reutilização de código e estruturas de classes hierárquicas.

Nesta experiência prática, você entenderá diferentes tipos de herança em Python, incluindo herança simples e múltipla. Você também aprenderá a usar a função super() para navegar em hierarquias de herança, implementar um exemplo prático de herança múltipla cooperativa e aplicar esses conceitos para construir um sistema de validação. O arquivo principal criado durante este laboratório é validate.py.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível intermediário com uma taxa de conclusão de 69%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Compreendendo Herança Simples e Múltipla

Nesta etapa, aprenderemos sobre os dois principais tipos de herança em Python: herança simples e herança múltipla. Herança é um conceito fundamental na programação orientada a objetos que permite que uma classe herde atributos e métodos de outras classes. Também veremos como o Python determina qual método chamar quando há vários candidatos, um processo conhecido como resolução de método.

Herança Simples

Herança simples é quando as classes formam uma única linha de ancestralidade. Pense nisso como uma árvore genealógica onde cada classe tem apenas um pai direto. Vamos criar um exemplo para entender como funciona.

Primeiro, abra um novo terminal no WebIDE. Depois que o terminal estiver aberto, inicie o interpretador Python digitando o seguinte comando e pressionando Enter:

python3

Agora que você está no interpretador Python, criaremos três classes que formam uma única cadeia de herança. Digite o seguinte código:

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()

class C(B):
    def spam(self):
        print('C.spam')
        super().spam()

Neste código, a classe B herda da classe A, e a classe C herda da classe B. A função super() é usada para chamar o método da classe pai.

Depois de definir essas classes, podemos descobrir a ordem em que o Python procura por métodos. Essa ordem é chamada de Ordem de Resolução de Método (MRO - Method Resolution Order). Para ver o MRO da classe C, digite o seguinte código:

C.__mro__

Você deve ver uma saída semelhante a esta:

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

Esta saída mostra que o Python primeiro procura um método na classe C, depois na classe B, depois na classe A e, finalmente, na classe base object.

Agora, vamos criar uma instância da classe C e chamar seu método spam(). Digite o seguinte código:

c = C()
c.spam()

Você deve ver a seguinte saída:

C.spam
B.spam
A.spam

Esta saída demonstra como super() funciona em uma cadeia de herança simples. Quando C.spam() chama super().spam(), ele chama B.spam(). Então, quando B.spam() chama super().spam(), ele chama A.spam().

Herança Múltipla

Herança múltipla permite que uma classe herde de mais de uma classe pai. Isso dá a uma classe acesso aos atributos e métodos de todas as suas classes pai. Vamos ver como a resolução de método funciona neste caso.

Digite o seguinte código no seu interpretador Python:

class Base:
    def spam(self):
        print('Base.spam')

class X(Base):
    def spam(self):
        print('X.spam')
        super().spam()

class Y(Base):
    def spam(self):
        print('Y.spam')
        super().spam()

class Z(Base):
    def spam(self):
        print('Z.spam')
        super().spam()

Agora, criaremos uma classe M que herda de várias classes pai X, Y e Z. Digite o seguinte código:

class M(X, Y, Z):
    pass

M.__mro__

Você deve ver a seguinte saída:

(<class '__main__.M'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.Base'>, <class 'object'>)

Esta saída mostra a Ordem de Resolução de Método para a classe M. O Python procurará por métodos nesta ordem.

Vamos criar uma instância da classe M e chamar seu método spam():

m = M()
m.spam()

Você deve ver a seguinte saída:

X.spam
Y.spam
Z.spam
Base.spam

Observe que super() não apenas chama o método da classe pai imediata. Em vez disso, ele segue a Ordem de Resolução de Método (MRO) definida pela classe filha.

Vamos criar outra classe N com as classes pai em uma ordem diferente:

class N(Z, Y, X):
    pass

N.__mro__

Você deve ver a seguinte saída:

(<class '__main__.N'>, <class '__main__.Z'>, <class '__main__.Y'>, <class '__main__.X'>, <class '__main__.Base'>, <class 'object'>)

Agora, crie uma instância da classe N e chame seu método spam():

n = N()
n.spam()

Você deve ver a seguinte saída:

Z.spam
Y.spam
X.spam
Base.spam

Isso mostra um conceito importante: na herança múltipla do Python, a ordem das classes pai na definição da classe determina a Ordem de Resolução de Método. A função super() segue esta ordem, não importa de qual classe ela é chamada.

Quando terminar de explorar esses conceitos, você pode sair do interpretador Python digitando o seguinte código:

exit()

Construindo um Sistema de Validação com Herança

Nesta etapa, vamos construir um sistema de validação prático usando herança. Herança é um conceito poderoso em programação que permite criar novas classes com base em classes existentes. Dessa forma, você pode reutilizar código e criar programas mais organizados e modulares. Ao construir este sistema de validação, você verá como a herança pode ser usada para criar componentes de código reutilizáveis que podem ser combinados de diferentes maneiras.

Criando a Classe Base Validator

Primeiro, precisamos criar uma classe base para nossos validadores. Para fazer isso, criaremos um novo arquivo no WebIDE. Veja como você pode fazer isso: clique em "File" > "New File", ou você pode usar o atalho do teclado. Assim que o novo arquivo estiver aberto, nomeie-o validate.py.

Agora, vamos adicionar algum código a este arquivo para criar uma classe base Validator. Esta classe servirá como a base para todos os nossos outros validadores.

## validate.py
class Validator:
    @classmethod
    def check(cls, value):
        return value

Neste código, definimos uma classe Validator com um método check. O método check recebe um valor como argumento e simplesmente o retorna inalterado. O decorador @classmethod é usado para tornar este método um método de classe. Isso significa que podemos chamar este método na própria classe, sem ter que criar uma instância da classe.

Adicionando Validadores de Tipo

Em seguida, adicionaremos alguns validadores que verificam o tipo de um valor. Esses validadores herdarão da classe Validator que acabamos de criar. Volte para o arquivo validate.py e adicione o seguinte código:

class Typed(Validator):
    expected_type = object
    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        return super().check(value)

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

A classe Typed é uma subclasse de Validator. Ela tem um atributo expected_type, que é inicialmente definido como object. O método check na classe Typed verifica se o valor fornecido é do tipo esperado. Se não for, ele levanta um TypeError. Se o tipo estiver correto, ele chama o método check da classe pai usando super().check(value).

As classes Integer, Float e String herdam de Typed e especificam o tipo exato que devem verificar. Por exemplo, a classe Integer verifica se um valor é um inteiro.

Testando os Validadores de Tipo

Agora que criamos nossos validadores de tipo, vamos testá-los. Abra um novo terminal e inicie o interpretador Python executando o seguinte comando:

python3

Depois que o interpretador Python estiver em execução, podemos importar e testar nossos validadores. Aqui está algum código para testá-los:

from validate import Integer, String

Integer.check(10)  ## Should return 10

try:
    Integer.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

String.check('10')  ## Should return '10'

Quando você executar este código, você deve ver algo como isto:

10
Error: Expected <class 'int'>
'10'

Também podemos usar esses validadores em uma função. Vamos tentar isso:

def add(x, y):
    Integer.check(x)
    Integer.check(y)
    return x + y

add(2, 2)  ## Should return 4

try:
    add('2', '3')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

Quando você executar este código, você deve ver:

4
Error: Expected <class 'int'>

Adicionando Validadores de Valor

Até agora, criamos validadores que verificam o tipo de um valor. Agora, vamos adicionar alguns validadores que verificam o próprio valor, em vez do tipo. Volte para o arquivo validate.py e adicione o seguinte código:

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

class NonEmpty(Validator):
    @classmethod
    def check(cls, value):
        if len(value) == 0:
            raise ValueError('Must be non-empty')
        return super().check(value)

O validador Positive verifica se um valor é não negativo. Se o valor for menor que 0, ele levanta um ValueError. O validador NonEmpty verifica se um valor tem um comprimento diferente de zero. Se o comprimento for 0, ele levanta um ValueError.

Compondo Validadores com Herança Múltipla

Agora, vamos combinar nossos validadores usando herança múltipla. Herança múltipla permite que uma classe herde de mais de uma classe pai. Volte para o arquivo validate.py e adicione o seguinte código:

class PositiveInteger(Integer, Positive):
    pass

class PositiveFloat(Float, Positive):
    pass

class NonEmptyString(String, NonEmpty):
    pass

Essas novas classes combinam a verificação de tipo e a verificação de valor. Por exemplo, a classe PositiveInteger verifica se um valor é tanto um inteiro quanto não negativo. A ordem da herança é importante aqui. Os validadores são verificados na ordem especificada na definição da classe.

Testando os Validadores Compostos

Vamos testar nossos validadores compostos. No interpretador Python, execute o seguinte código:

from validate import PositiveInteger, PositiveFloat, NonEmptyString

PositiveInteger.check(10)  ## Should return 10

try:
    PositiveInteger.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

try:
    PositiveInteger.check(-10)  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

NonEmptyString.check('hello')  ## Should return 'hello'

try:
    NonEmptyString.check('')  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

Quando você executar este código, você deve ver:

10
Error: Expected <class 'int'>
Error: Expected >= 0
'hello'
Error: Must be non-empty

Isso mostra como podemos combinar validadores para criar regras de validação mais complexas.

Quando terminar de testar, você pode sair do interpretador Python executando o seguinte comando:

exit()

Aplicando Validadores a uma Classe Stock

Nesta etapa, vamos ver como nossos validadores funcionam em uma situação do mundo real. Validadores são como pequenos verificadores que garantem que os dados que usamos atendam a certas regras. Criaremos uma classe Stock. Uma classe é como um modelo para criar objetos. Neste caso, a classe Stock representará uma ação no mercado de ações, e usaremos nossos validadores para garantir que os valores de seus atributos (como o número de ações e o preço) sejam válidos.

Criando a Classe Stock

Primeiro, precisamos criar um novo arquivo. No WebIDE, crie um novo arquivo chamado stock.py. Este arquivo conterá o código para nossa classe Stock. Agora, adicione o seguinte código ao arquivo stock.py:

## stock.py
from validate import PositiveInteger, PositiveFloat

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

    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        self._shares = PositiveInteger.check(value)

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        self._price = PositiveFloat.check(value)

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

Vamos detalhar o que este código faz:

  1. Começamos importando os validadores PositiveInteger e PositiveFloat do nosso módulo validate. Esses validadores nos ajudarão a garantir que o número de ações seja um inteiro positivo e o preço seja um float positivo.
  2. Em seguida, definimos uma classe Stock. Dentro da classe, temos um método __init__. Este método é chamado quando criamos um novo objeto Stock. Ele recebe três parâmetros: name, shares e price, e os atribui aos atributos do objeto.
  3. Usamos propriedades e setters para validar os valores de shares e price. Uma propriedade é uma maneira de controlar o acesso a um atributo, e um setter é um método que é chamado quando tentamos definir o valor desse atributo. Quando definimos o atributo shares, o método PositiveInteger.check é chamado para garantir que o valor seja um inteiro positivo. Da mesma forma, quando definimos o atributo price, o método PositiveFloat.check é chamado para garantir que o valor seja um float positivo.
  4. Finalmente, temos um método cost. Este método calcula o custo total da ação multiplicando o número de ações pelo preço.

Testando a Classe Stock

Agora que criamos nossa classe Stock, precisamos testá-la para ver se os validadores estão funcionando corretamente. Abra um novo terminal e inicie o interpretador Python. Você pode fazer isso executando o seguinte comando:

python3

Depois que o interpretador Python estiver em execução, podemos importar e testar nossa classe Stock. Insira o seguinte código no interpretador Python:

from stock import Stock

## Create a valid stock
s = Stock('GOOG', 100, 490.10)
print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
print(f"Cost: {s.cost()}")

## Try setting an invalid shares value
try:
    s.shares = -10
except ValueError as e:
    print(f"Error setting shares: {e}")

## Try setting an invalid price value
try:
    s.price = "not a price"
except TypeError as e:
    print(f"Error setting price: {e}")

Quando você executar este código, você deve ver uma saída semelhante à seguinte:

Name: GOOG, Shares: 100, Price: 490.1
Cost: 49010.0
Error setting shares: Expected >= 0
Error setting price: Expected <class 'float'>

Esta saída mostra que nossos validadores estão funcionando como esperado. A classe Stock não nos permite definir valores inválidos para shares e price. Quando tentamos definir um valor inválido, um erro é levantado, e podemos capturar e imprimir esse erro.

Compreendendo Como a Herança Ajuda

Uma das grandes vantagens de usar nossos validadores é que podemos combinar facilmente diferentes regras de validação. Herança é um conceito poderoso em Python que nos permite criar novas classes com base em classes existentes. Com herança múltipla, podemos usar a função super() para chamar métodos de várias classes pai.

Por exemplo, se quisermos garantir que o nome da ação não esteja vazio, podemos seguir estas etapas:

  1. Importe o validador NonEmptyString do módulo validate. Este validador nos ajudará a verificar se o nome da ação não é uma string vazia.
  2. Adicione um setter de propriedade para o atributo name na classe Stock. Este setter usará o método NonEmptyString.check() para validar o nome da ação.

Isso mostra como a herança, especialmente a herança múltipla com a função super(), nos permite construir componentes que são flexíveis e podem ser reutilizados em diferentes combinações.

Quando terminar de testar, você pode sair do interpretador Python executando o seguinte comando:

exit()

Resumo

Neste laboratório, você aprendeu sobre o comportamento da herança em Python e compreendeu vários conceitos-chave. Você explorou a diferença entre herança simples e múltipla, entendeu como a função super() navega na Ordem de Resolução de Métodos (MRO - Method Resolution Order), aprendeu a implementar herança múltipla cooperativa e aplicou a herança para construir um sistema de validação prático.

Você também criou uma estrutura de validação flexível usando herança e a aplicou a um exemplo do mundo real com a classe Stock, o que mostra como a herança pode criar componentes reutilizáveis e compostos. Os principais pontos a serem lembrados incluem como super() funciona em herança simples e múltipla, a capacidade da herança múltipla de compor funcionalidades e o uso de property setters com validadores. Esses conceitos são fundamentais para a programação orientada a objetos do Python e amplamente utilizados em aplicações do mundo real.