Programação Orientada a Objetos e Funções Avançadas em Python

Conteúdo do Artigo

    1. Herança Múltipla
    1. Polimorfismo
    1. Decoradores de Dados
    1. Funções Anônimas
    1. Funções de Fechamento
    1. Decoradores

1. Herança Múltipla

Quando todos os atributos e métodos das classes pai não entram em conflito (não têm nomes duplicados), o uso não é afetado.

class ClasseBase:
    valor_base = 1

    def __init__(self):
        self.atributo_instancia = 2

    def metodo_instancia(self):
        print("Método de instância da ClasseBase foi chamado")

    @classmethod
    def metodo_classe(cls):
        print("Método de classe da ClasseBase foi chamado")

    @staticmethod
    def metodo_estatico():
        print('Método estático da ClasseBase foi chamado')


class ClasseSecundaria():
    valor_secundario = 666

    def metodo_secundario(self):
        print("Método de instância da ClasseSecundaria foi chamado")


class ClasseDerivada(ClasseBase, ClasseSecundaria):
    pass


objeto = ClasseDerivada()
# Usando atributos de classe e instância da ClasseBase
print(objeto.valor_base) # 1
print(objeto.atributo_instancia) # 2
# Usando método de instância da ClasseBase
objeto.metodo_instancia() # Método de instância da ClasseBase foi chamado
# Usando atributo de classe da ClasseSecundaria
print(objeto.valor_secundario) # 666
# Método de instância da ClasseSecundaria foi chamado
objeto.metodo_secundario() # Método de instância da ClasseSecundaria foi chamado



Se os atributos ou métodos das classes pai tiverem nomes duplicados, prioriza-se o uso dos atributos e métodos da classe pai herdada primiero:

class ClasseA:
    atributo = 1
    numero = 9
    def metodo(self):
        print("Método da ClasseA foi chamado")
        
class ClasseB: 
    valor = 666
    numero = 8
    def metodo(self):
        print("Método da ClasseB foi chamado")
        
    def outro_metodo(self):
        print("Outro método da ClasseB foi chamado")
        
class ClasseC(ClasseA, ClasseB): 
    pass

objeto = ClasseC()

# Se houver duplicidade, usa-se o método ou atributo da classe herdada primeiro
print(objeto.numero) # 9 para ClasseC(ClasseA, ClasseB)
# print(objeto.numero) # 8 para ClasseC(ClasseB, ClasseA)
objeto.metodo() # Método da ClasseA foi chamado para ClasseC(ClasseA, ClasseB)

# A ordem de herança pode ser obtida com o atributo mro
# Ponto importante: a relação de herança é obtida pelo atributo mro da classe
# (<class '__main__.ClasseC'>, <class '__main__.ClasseA'>, <class '__main__.ClasseB'>, <class 'object'>)
# (<class '__main__.ClasseC'>, <class '__main__.ClasseB'>, <class '__main__.ClasseA'>, <class 'object'>)
print(ClasseC.__mro__)
print(ClasseC.mro())


Se atributos ou métodos na classe derivada tiverem nomes duplicados com os da classe pai, prioriza-se o uso dos da classe derivada:

class ClassePai:
    numero = 9
    def metodo(self):
        print("Método da ClassePai foi chamado")
        
class ClasseFilha: 
    valor = 666
    def outro_metodo(self):
        print("Outro método da ClasseFilha foi chamado")
        
class ClasseComum(ClassePai, ClasseFilha):
    numero = 1
    def metodo(self):
        print("Método da ClasseComum foi chamado")
        
objeto = ClasseComum()
print(objeto.numero) # 1
objeto.metodo() # Método da ClasseComum foi chamado


Quando atributos ou métodos na classe derivada e na classe pai têm nomes duplicados, para usar os da classe pai, pode-se usar a função embutida super()

class ClasseBase:
    numero = 9
    def metodo(self):
        print("Método da ClasseBase foi chamado")
        
class ClasseDerivada(ClasseBase):
    numero = 1
    def metodo(self):
        # Chamando o método da classe pai a partir do método da classe derivada
        super().metodo()
        
    def metodo_alternativo(self):
        super().metodo()
        
    def obter_numero_pai(self):
        print(super().numero)
        
objeto = ClasseDerivada()
print(objeto.numero) # 1
objeto.metodo() # Método da ClasseBase foi chamado
objeto.metodo_alternativo() # Método da ClasseBase foi chamado
objeto.obter_numero_pai() # 9


Quando a classe derivada e a classe pai ambos têm o método mágico init, apenas o da classe derivada será chamado, não o da classe pai.

  • Se a classe pai tem atributos de instância: no init há atributos de instância
  • Se a classe derivada também tem atributos de instância, por padrão os atributos de instância da classe pai não são obtidos
  • É necessário chamar manualmente o método init da classe pai para obter os atributos de instância da classe pai
class ClasseBase:
    def __init__(self):
        self.atributo1 = 1
        self.atributo2 = 2
        
class ClasseDerivada(ClasseBase):
    def __init__(self):
        self.atributo3 = 3
        self.atributo4 = 4
        super().__init__()
        
objeto = ClasseDerivada()
print(objeto.atributo3) # 3
print(objeto.atributo4) # 4
print(objeto.atributo1) # 1
print(objeto.atributo2) # 2


Quando atributos de instância na classe pai e na classe derivada têm nomes duplicados, a chamada manual do método init da classe pai sobrescreverá os valores dos atributos de instância com nomes duplicados

class ClasseBase:
    def __init__(self):
        self.atributo = 1
        print(f"Apos __init__ da classe pai, self.atributo: {self.atributo}")
        
class ClasseDerivada(ClasseBase):
    def __init__(self):
        self.atributo = 3
        print(f"Antes de chamar __init__ da classe pai, self.atributo: {self.atributo}")
        super().__init__()
        
objeto = ClasseDerivada()
# Quando atributos de instância têm nomes duplicados, se o __init__ da classe pai for chamado manualmente, o valor do atributo de instância com nome duplicado será sobrescrito
print(objeto.atributo)  # 1

# Saída:
# Antes de chamar __init__ da classe pai, self.atributo: 3
# Apos __init__ da classe pai, self.atributo: 1
# 1


2. Polimorfismo

Tipagem de pato: se há um pássaro que anda como um pato, canta como um pato, come e dorme como um pato, então esse pássaro pode ser considerado um pato.

  • Ignora a forma real do objeto, e sim o modo como ele pode desempenhar o tipo
  • Significa que a mesma operação, quando aplicada a diferentes objetos, pode ter diferentes interpretações
# O polimorfismo tem manifestação específica com base na herança
class Animal:
    def fazer_som(self):
        pass
        
class Gato(Animal):
    def fazer_som(self):
        print("Miau miau")
        
class Cachorro(Animal):
    def fazer_som(self):
        print("Au au au")

# Definindo uma função que chama métodos diferentes através de objetos distintos
def som_animal(animal):
    # Quando o objeto é diferente, o método chamado também é diferente
    animal.fazer_som()

# Criando objetos de gato e cachorro
gato = Gato()
cachorro = Cachorro()
som_animal(gato) # Miau miau
som_animal(cachorro) # Au au au


3. Decoradores de Dados

Podemos usar decoradores de classe para decorar classes de dados Características de uso:

  • Primeiro, é necessário importar o módulo dataclasses
  • Usar o decorador @dataclass
  • Cria automaticamente muitos métodos especiais para a classe, incluindo o método mágico init
  • Não é necessário definir manualmente init para poder usar diretamente
  • Pode ser entendido como outro formato de definição de atributos de instância
import dataclasses

@dataclasses.dataclass
class Pessoa:
    # Ao definir atributos de instância em classes de dados, é necessário especificar o tipo de dado
    nome: str = "João Silva"
    idade = 25
    altura: float = 1.75
    
    def apresentar(self):
        print("Olá, meu nome é", self.nome)

pessoa1 = Pessoa()
print(pessoa1.nome) # João Silva
print(pessoa1.idade) # 25
print(Pessoa.nome) # João Silva
print(Pessoa.idade) # 25
print(Pessoa.altura) # 1.75


Classes no módulo também podem ser importadas e usadas: variáveis, funções, classes, etc.

4. Funções Anônimas

Definição e uso de funções anônimas Formato de definição:

Formato de chamada:

  • Trata a função anônima como um todo, usando () para chamada e passagem de argumentos
  • Se houver parâmetros, é necessário passar argumentos reais
  • Não precisa usar return, mas há valor de retorno
# Definindo função anônima com a palavra-chave lambda
print((lambda x, y: x * y)(5, 10)) # 50
# Passando a referência da função anônima e depois chamando
funcao_multiplicacao = lambda x, y: x * y
print(type(funcao_multiplicacao)) # <class 'function'>
print(funcao_multiplicacao(7, 8)) # 56


A função principal das funções anônimas é construir funções simples para implementar lógica simples

5. Funções de Fechamento

Definição de fechamento

  • Ao definir uma função, outra função é aninhada
  • A função interna usa variáveis da função externa
  • A função externa retorna o nome da função interna (referência da função interna)
  • O núcleo do fechamento é que a função interna usa variáveis da função externa, esse processo é chamado de fechamento

Significado do fechamento

  • Após a chamada da função, as variáveis locais dentro da função são destruídas, para facilitar o salvamento e uso de variáveis externas à função
  • Pode ser chamado usando a forma de fechamento
def funcao_externa(valor1):  
    def funcao_interna(valor2):  
        resultado = valor1 + valor2
        print(f"Valor1 da função externa: {valor1}, valor2 da função interna: {valor2}, resultado da soma: {resultado}")
    return funcao_interna  

# A função só é executada quando chamada, não quando definida
funcao = funcao_externa(10)  
funcao(5)  
funcao_externa(3)(7)  


6. Decoradores

Definição de Decoradores

  • Sem modificar o código da função original, pode-se usar decoradores para adicionar funcionalidades extras
  • Adicionar novas funcionalidades ou condições de exibição e saída de ajuda para as funções que precisam ser decoradas
  • Núcleo: sem modificar o código da função original, adicionar desempenho ou condições de restrição à função original

Tipos de Decoradores

  • Dceoradores de função
  • Decoradores de classe
  • Decoradores de método
  • Independentemente do tipo de decorador, deve-se seguir um princípio
  • Princípio de Aberto/Fechado
  • Aberto para extensão, fechado para modificação

Construção de Decoradores

  • O decorador é uma função em si
  • O valor de retorno do decorador deve ser uma referência de função
  • O decorador tem apenas um parâmetro fixo
  • O parâmetro fixo do decorador é usado para receber a função a ser decorada
  • A essência do decorador é um fechamento
  • A função interna usa variáveis da função externa
  • A função externa retorna a referência da função interna

Uso e definição básica de decoradores

# Usuário fazendo cadastro de conta
# A função original não valida o comprimento da conta, agora é necessário adicionar funcionalidade extra para validar se o comprimento da conta tem 6-12 caracteres
# Definindo um decorador para validar o comprimento da conta
def validar_conta(func):  # func recebe a referência da função decorada: func = nome_usuario
    def interna():
        print("Iniciando validação do comprimento da conta")
        func()  # func() = nome_usuario()

    return interna


def nome_usuario():
    print(f"O nome de usuário informado: pythonista")


nome_usuario = validar_conta(nome_usuario)  
nome_usuario()  



Caso: Ir às compras, sem login prévio não é possível adicionar produtos ao carrinho

# Caso: Ir às compras, sem login prévio não é possível adicionar produtos ao carrinho
# O fluxo normal de compra precisa primeiro login depois adicionar produtos ao carrinho, atualmente é possível adicionar diretamente sem login, após adicionar função de login, então o fluxo de compra
def verificar_login(func):  # func = compras
    def interna():
        print("Acessando página de login")
        print("Digitando nome de usuário e senha")
        print("Validando nome de usuário e senha......")
        print("Login realizado com sucesso!")
        print("Pode prosseguir com as compras")
        func()

    return interna


def compras():
    print("Adicionando um iPhone 15 Pro ao carrinho")


# Sintaxe de chamada padrão de decorador
# Após execução de verificar_login: func recebe a função decorada, retorna a referência da função interna
# compras = verificar_login(compras)
# compras()
funcao = verificar_login(compras)  
funcao()  


Tags: Python programacao-orientada-a-objetos herança-múltipla Polimorfismo decoradores

Publicado em 6-27 21:14