Introdução à Programação Orientada a Objetos em Python

Classes e Objetos

Uma classe funciona como um modelo ou um blueprint para a criação de objetos. Ela define um conjunto de atributos (dados) e métodos (funções) que os objetos criados a partir dela possuirão. Um objeto, por outro lado, é uma instância concrtea dessa classe, possuindo seus próprios valores para os atributos definidos.

Estrutura de uma Classe

  • Nome da Classe: Identifica a classe (convenção Python usa CamelCase).
  • Atributos: Variáveis que descrevem as características de um objeto.
  • Métodos: Funções associadas a um objeto, definindo suas ações ou comportamentos.

Criação de Classes e Objetos

Em Python, a palavra-chave class é usada para definir uma classe. O método especial __init__ é o construtor, chamado automaticamente ao criar um novo objeto, e é onde os atributos são geralmente iniciailzados.


class Pessoa:
   # O método __init__ é o construtor da classe
   def __init__(self, nome, genero):
       self.nome = nome  # Atributo de instância
       self.genero = genero # Atributo de instância

   # Método de instância
   def exibir_informacoes(self):
       print(f"Nome: {self.nome}, Gênero: {self.genero}")

# Criando instâncias (objetos) da classe Pessoa
pessoa1 = Pessoa("Alice", "Feminino")
pessoa2 = Pessoa("Bob", "Masculino")

# Chamando um método do objeto
pessoa1.exibir_informacoes()
pessoa2.exibir_informacoes()
   

Atributos de Classe vs. Atributos de Instância

Atributos de classe são compartilhados por todas as instâncias da classe, enquanto atributos de instância são específicos de cada objeto.


class Personagem:
   moedas_ouro = 200  # Atributo de classe (compartilhado)

   def __init__(self, nome, classe, vida, mana, ataque, defesa):
       self.nome = nome
       self.classe = classe
       self.vida = vida
       self.mana = mana
       self.ataque = ataque
       self.defesa = defesa

   def mostrar_status(self):
       print(f"""
       --- Status do Personagem ---
       Nome: {self.nome}
       Classe: {self.classe}
       Vida: {self.vida}
       Mana: {self.mana}
       Ataque: {self.ataque}
       Defesa: {self.defesa}
       Moedas: {self.moedas_ouro}
       """)

# Criando personagens
heroi1 = Personagem("Artemis", "Caçador", 950, 120, 110, 55)
heroi2 = Personagem("Thor", "Guerreiro", 1200, 80, 130, 90)

heroi1.mostrar_status()
heroi2.mostrar_status()

# Modificando um atributo de classe através de uma instância (afeta todas as instâncias)
heroi1.moedas_ouro = 350
print("\nApós Artemis gastar moedas:")
heroi1.mostrar_status()
heroi2.mostrar_status() # Note que as moedas do Thor também mudaram
   

Métodos Especiais __str__ e __del__

  • __str__(self): Retorna uma representação em string do objeto. É chamado quando se usa print(objeto).
  • __del__(self): Chamado quando um objeto está prestes a ser destruído (coleta de lixo).

class Guerreiro:
   def __init__(self, nome_guerreiro):
       self.nome_guerreiro = nome_guerreiro
       print(f"Um novo guerreiro chamado {self.nome_guerreiro} foi criado.")

   def __str__(self):
       return f"Sou o guerreiro {self.nome_guerreiro}, pronto para a batalha!"

   def __del__(self):
       print(f"O guerreiro {self.nome_guerreiro} foi derrotado e sua alma descansa...")

guerreiro_a = Guerreiro("Kael")
guerreiro_b = Guerreiro("Lyra")

# Usando o método __str__
print(guerreiro_a)
print(guerreiro_b)

# O método __del__ é chamado automaticamente quando os objetos saem de escopo
# ou quando 'del' é explicitamente chamado.
del guerreiro_a
del guerreiro_b

print("\n--- Fim da Batalha ---")
   

Resumo de Métodos Especiais

Método Descrição
__init__(self) Chamado automaticamente ao criar um objeto. Usado para inicializar atributos.
__str__(self) Chamado quando print(objeto) é usado. Retorna uma representação legível do objeto.
__del__(self) Chamado quando um objeto está sendo destruído. Útil para limpeza de recursos.

As Três Características Principais da Orientação a Objetos

  1. Encapsulamento
  2. Herança
  3. Polimorfismo

1. Encapsulamento

O encapsulamento permite agrupar dados (atributos) e os métodos que operam sobre esses dados dentro de uma única unidade (a classe), além de controlar o acesso a esses dados, protegendo-os de modificações externas indesejadas. Em Python, o encapsulamento é simulado usando convenções de nomenclatura:

  • Atributos ou métodos prefixados com um único underscore (_) são considerados "protegidos" (acessíveis dentro da classe e subclasses).
  • Atributos ou métodos prefixados com dois underscores (__) são "privados" (dificilmente acessíveis de fora da clase).

class Entidade:
   # Atributo de classe "privado" (convenção)
   _pais_origem = "Terra"

   def __init__(self, nome, nivel):
       self.nome = nome  # Atributo público
       self.__nivel_seguranca = nivel # Atributo "privado"

   def __metodo_interno(self): # Método "privado"
       print(f"Executando método interno de segurança para {self.nome}.")

   def exibir_detalhes(self):
       print(f"Nome: {self.nome}")
       print(f"Nível de Segurança: {self.__nivel_seguranca}")
       self.__metodo_interno()

   def obter_pais(self): # Método público para acessar atributo "protegido"
       return self._pais_origem

# Tentativas de acesso direto (geralmente falham ou não são recomendadas)
entidade_a = Entidade("Alpha", 5)

# Acesso público é permitido
print(f"Nome da entidade: {entidade_a.nome}")

# Acesso a atributo "privado" (causará erro ou comportamento inesperado)
# print(entidade_a.__nivel_seguranca) # Isso daria erro

# Acesso a método "privado" (causará erro)
# entidade_a.__metodo_interno() # Isso daria erro

# Acesso controlado através de métodos públicos
entidade_a.exibir_detalhes()
print(f"País de origem: {entidade_a.obter_pais()}")

# Acesso a atributo "protegido" (possível, mas não ideal)
print(f"Pais de origem (acesso direto): {entidade_a._pais_origem}")
   

Para acessar atributos ou métodos privados de forma controlada, podemos definir métodos "getters" e "setters" se necessário.


class ContaBancaria:
   def __init__(self, titular, saldo_inicial):
       self.titular = titular
       self.__saldo = saldo_inicial # Saldo privado

   # Método getter para o saldo
   def get_saldo(self):
       return self.__saldo

   # Método setter para depósito (permite validação)
   def depositar(self, valor):
       if valor > 0:
           self.__saldo += valor
           print(f"Depósito de R${valor:.2f} realizado. Novo saldo: R${self.__saldo:.2f}")
       else:
           print("Valor de depósito inválido.")

   # Método setter para saque (permite validação)
   def sacar(self, valor):
       if 0 < valor <= self.__saldo:
           self.__saldo -= valor
           print(f"Saque de R${valor:.2f} realizado. Novo saldo: R${self.__saldo:.2f}")
       elif valor <= 0:
           print("Valor de saque inválido.")
       else:
           print("Saldo insuficiente.")

conta1 = ContaBancaria("Carlos", 1000.00)
print(f"Saldo inicial de {conta1.titular}: R${conta1.get_saldo():.2f}")

conta1.depositar(500.50)
conta1.sacar(200.00)
conta1.sacar(1500.00) # Tentativa de saque com saldo insuficiente

print(f"Saldo final de {conta1.titular}: R${conta1.get_saldo():.2f}")
   

2. Herança

A herança permite que uma classe (subclasse ou classe derivada) herde atributos e métodos de outra classe (superclasse ou classe base). Isso promove a reutilização de código e a criação de hierarquias de classes.


# Classe base (Superclasse)
class Veiculo:
   def __init__(self, marca, modelo):
       self.marca = marca
       self.modelo = modelo

   def mover(self):
       print(f"O {self.marca} {self.modelo} está em movimento.")

# Classe derivada (Subclasse)
class Carro(Veiculo):
   def __init__(self, marca, modelo, numero_portas):
       # Chama o construtor da classe pai (Veiculo)
       super().__init__(marca, modelo)
       self.numero_portas = numero_portas

   # Sobrescrevendo um método da classe pai
   def mover(self):
       print(f"O carro {self.marca} {self.modelo} está acelerando na estrada.")

   def abrir_porta_malas(self):
       print(f"Abrindo o porta-malas do {self.modelo}.")

# Outra classe derivada
class Moto(Veiculo):
   def __init__(self, marca, modelo, cilindradas):
       super().__init__(marca, modelo)
       self.cilindradas = cilindradas

   # Método adicional na subclasse
   def empinar(self):
       print(f"A moto {self.marca} {self.modelo} está empinando!")

# Criando instâncias
meu_carro = Carro("Toyota", "Corolla", 4)
minha_moto = Moto("Honda", "CBR", 600)

# Usando métodos herdados e sobrescritos
meu_carro.mover()
minha_moto.mover()

# Usando métodos específicos das subclasses
meu_carro.abrir_porta_malas()
minha_moto.empinar()
   

Herança Múltipla

Python suporta herança múltipla, onde uma classe pode herdar de mais de uma classe base.


class Voar:
   def voar(self):
       print("Esta entidade pode voar.")

class Nadar:
   def nadar(self):
       print("Esta entidade pode nadar.")

# Classe derivada que herda de Voar e Nadar
class CriaturaMagica(Voar, Nadar):
   def __init__(self, nome):
       self.nome = nome

   def atacar(self):
       print(f"{self.nome} está atacando!")

# Criando uma instância
dragao = CriaturaMagica("Fafnir")

dragao.voar()
dragao.nadar()
dragao.atacar()
   

3. Polimorfismo

Polimorfismo (muitas formas) permite que objetos de diferentes classes respondam ao mesmo método de maneiras específicas para suas classes. Isso significa que podemos tratar objetos de classes diferentes de forma uniforme se eles compartilharem uma interface comum (métodos com o mesmo nome).


class Cachorro:
   def emitir_som(self):
       return "Au au!"

class Gato:
   def emitir_som(self):
       return "Miau!"

class Pato:
   def emitir_som(self):
       return "Quack!"

# Função que demonstra polimorfismo
def fazer_barulho(animal):
   print(animal.emitir_som())

# Criando instâncias de diferentes classes
rex = Cachorro()
felix = Gato()
donald = Pato()

# Passando objetos de diferentes tipos para a mesma função
fazer_barulho(rex)
fazer_barulho(felix)
fazer_barulho(donald)

# Lista de animais onde cada um pode ter um som diferente
animais = [Cachorro(), Gato(), Pato()]
for animal in animais:
   print(animal.emitir_som())
   

Tags: Python Orientação a Objetos classes objetos encapsulamento

Publicado em 7-21 00:39