Conteúdo do Artigo
-
- Herança Múltipla
-
- Polimorfismo
-
- Decoradores de Dados
-
- Funções Anônimas
-
- Funções de Fechamento
-
- 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()