Python Admin

O que Todo Dev Deve Saber sobre Classes e Objetos em Python: Atributos, Métodos e __init__ Já leu

Entendendo Classes e Objetos: O Fundamento da Programação Orientada a Objetos Quando começamos a programar, frequentemente utilizamos variáveis simples e funções soltas para resolver problemas. No entanto, quando os projetos crescem e a complexidade aumenta, essa abordagem se torna caótica e difícil de manter. Classes e objetos são a resposta que a Programação Orientada a Objetos (POO) oferece para organizar código de forma estruturada, reutilizável e intuitiva. Uma classe é um molde ou blueprint que define como um objeto deve ser estruturado. Um objeto é uma instância concreta dessa classe — é o resultado de usar esse molde. Pense em uma classe como a receita de um bolo e o objeto como o bolo propriamente dito. Você pode fazer vários bolos usando a mesma receita, cada um com suas características particulares. Em Python, as classes nos permitem encapsular dados (atributos) e comportamentos (métodos) em uma unidade coesa que modela entidades do mundo real. A Estrutura Básica: Definindo Uma Classe Criando

Entendendo Classes e Objetos: O Fundamento da Programação Orientada a Objetos

Quando começamos a programar, frequentemente utilizamos variáveis simples e funções soltas para resolver problemas. No entanto, quando os projetos crescem e a complexidade aumenta, essa abordagem se torna caótica e difícil de manter. Classes e objetos são a resposta que a Programação Orientada a Objetos (POO) oferece para organizar código de forma estruturada, reutilizável e intuitiva.

Uma classe é um molde ou blueprint que define como um objeto deve ser estruturado. Um objeto é uma instância concreta dessa classe — é o resultado de usar esse molde. Pense em uma classe como a receita de um bolo e o objeto como o bolo propriamente dito. Você pode fazer vários bolos usando a mesma receita, cada um com suas características particulares. Em Python, as classes nos permitem encapsular dados (atributos) e comportamentos (métodos) em uma unidade coesa que modela entidades do mundo real.

A Estrutura Básica: Definindo Uma Classe

Criando sua primeira classe

Uma classe em Python é definida usando a palavra-chave class. A estrutura mais básica segue este padrão:

class Pessoa:
    pass

Isso cria uma classe vazia. Para que ela seja útil, precisamos adicionar atributos e métodos. Os atributos são características (dados) e os métodos são ações (funções) que a classe pode executar.

class Carro:
    def __init__(self, marca, modelo, ano):
        self.marca = marca
        self.modelo = modelo
        self.ano = ano

    def descrever(self):
        return f"{self.ano} {self.marca} {self.modelo}"

    def idade(self, ano_atual=2024):
        return ano_atual - self.ano

Agora vamos criar instâncias dessa classe:

carro1 = Carro("Toyota", "Corolla", 2020)
carro2 = Carro("Honda", "Civic", 2018)

print(carro1.descrever())  # 2020 Toyota Corolla
print(carro2.idade())      # 6

O papel crucial do método __init__

O método __init__ é o construtor da classe — ele é automaticamente chamado quando você cria uma nova instância. Seu principal objetivo é inicializar os atributos do objeto com valores específicos passados como argumentos. Sem __init__, você teria que atribuir manualmente cada atributo após criar o objeto, o que seria trabalhoso e propenso a erros.

O primeiro parâmetro de qualquer método, incluindo __init__, é sempre self. Esse parâmetro especial representa a instância do objeto em questão. Quando você escreve self.marca = marca, está dizendo: "no objeto que estou criando, defina o atributo 'marca' com o valor passado como argumento".

class Produto:
    def __init__(self, nome, preco, estoque=0):
        self.nome = nome
        self.preco = preco
        self.estoque = estoque
        self.desconto = 0  # Atributo com valor padrão

produto1 = Produto("Notebook", 3500.00, 5)
produto2 = Produto("Mouse", 50.00)  # estoque não fornecido, usa padrão

print(f"{produto1.nome}: R$ {produto1.preco}")
print(f"Estoque de {produto2.nome}: {produto2.estoque}")

Atributos: Armazenando Dados no Objeto

Atributos de instância versus atributos de classe

Existem dois tipos de atributos em Python. Os atributos de instância são específicos de cada objeto e são definidos dentro de __init__. Os atributos de classe pertencem à classe como um todo e são compartilhados por todas as instâncias.

class Conta:
    juros = 0.05  # Atributo de classe - compartilhado por todas as contas

    def __init__(self, titular, saldo=0):
        self.titular = titular  # Atributo de instância
        self.saldo = saldo      # Atributo de instância

conta1 = Conta("Alice", 1000)
conta2 = Conta("Bob", 2000)

print(conta1.titular)  # Alice
print(conta1.saldo)    # 1000
print(Conta.juros)     # 0.05 - acessado pela classe

# Cada instância tem seus próprios atributos de instância
print(conta1.juros)    # 0.05 - acessa via instância
print(conta2.juros)    # 0.05 - mesmo valor

Modificando atributos após a criação

Os atributos não são imutáveis após a criação. Você pode modificá-los a qualquer momento:

class Estudante:
    def __init__(self, nome, matricula):
        self.nome = nome
        self.matricula = matricula
        self.notas = []

    def adicionar_nota(self, nota):
        self.notas.append(nota)

    def media(self):
        return sum(self.notas) / len(self.notas) if self.notas else 0

aluno = Estudante("Carlos", "2024001")
aluno.adicionar_nota(7.5)
aluno.adicionar_nota(8.0)
aluno.adicionar_nota(9.5)

print(f"Média de {aluno.nome}: {aluno.media():.2f}")  # 8.33

Métodos: Definindo Comportamentos

O que são métodos e como funcionam

Um método é uma função definida dentro de uma classe. Ele permite que objetos realizem ações e modifiquem seus próprios dados. Todo método recebe self como primeiro parâmetro, permitindo acesso aos atributos e outros métodos da instância.

class Banco:
    def __init__(self, saldo_inicial=0):
        self.saldo = saldo_inicial

    def depositar(self, valor):
        """Adiciona valor ao saldo"""
        if valor > 0:
            self.saldo += valor
            return f"Depósito de R$ {valor:.2f} realizado. Saldo: R$ {self.saldo:.2f}"
        return "Valor deve ser positivo"

    def sacar(self, valor):
        """Remove valor do saldo se houver fundos"""
        if valor > self.saldo:
            return f"Saldo insuficiente. Disponível: R$ {self.saldo:.2f}"
        if valor > 0:
            self.saldo -= valor
            return f"Saque de R$ {valor:.2f} realizado. Saldo: R$ {self.saldo:.2f}"
        return "Valor deve ser positivo"

    def obter_saldo(self):
        """Retorna o saldo atual"""
        return self.saldo

conta = Banco(500)
print(conta.depositar(200))      # Depósito de R$ 200.00 realizado. Saldo: R$ 700.00
print(conta.sacar(150))          # Saque de R$ 150.00 realizado. Saldo: R$ 550.00
print(f"Saldo final: R$ {conta.obter_saldo():.2f}")

Métodos especiais em Python

Python oferece vários métodos especiais (dunder methods) que começam e terminam com dois underscores. Eles permitem que seus objetos se comportem como objetos nativos do Python. Alguns dos mais importantes são:

class Livro:
    def __init__(self, titulo, autor, paginas):
        self.titulo = titulo
        self.autor = autor
        self.paginas = paginas

    def __str__(self):
        """Retorna representação legível para humanos"""
        return f"{self.titulo} por {self.autor}"

    def __repr__(self):
        """Retorna representação técnica (útil para debug)"""
        return f"Livro('{self.titulo}', '{self.autor}', {self.paginas})"

    def __len__(self):
        """Permite usar len() no objeto"""
        return self.paginas

    def __eq__(self, outro):
        """Permite comparar dois livros com =="""
        if not isinstance(outro, Livro):
            return False
        return self.titulo == outro.titulo and self.autor == outro.autor

livro1 = Livro("1984", "George Orwell", 328)
livro2 = Livro("1984", "George Orwell", 328)

print(str(livro1))           # 1984 por George Orwell
print(repr(livro1))          # Livro('1984', 'George Orwell', 328)
print(len(livro1))           # 328
print(livro1 == livro2)      # True

Boas Práticas e Padrões Comuns

Encapsulamento com atributos privados

Em Python, não existe encapsulamento forçado como em outras linguagens, mas usa-se a convenção de prefixar nomes com underscore para indicar que um atributo é "privado" (não deve ser acessado diretamente de fora):

class Senha:
    def __init__(self, valor):
        self._valor = valor  # Convenção: privado

    def validar(self, tentativa):
        return self._valor == tentativa

    def mudar(self, antiga, nova):
        if self._valor == antiga:
            self._valor = nova
            return "Senha alterada com sucesso"
        return "Senha antiga incorreta"

conta = Senha("minhasenha123")
print(conta.validar("minhasenha123"))      # True
print(conta.mudar("minhasenha123", "nova")) # Senha alterada com sucesso

Usando propriedades (properties) para controle de acesso

Para mais controle sobre como os atributos são acessados e modificados, use o decorador @property:

class Temperatura:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def celsius(self):
        """Getter para celsius"""
        return self._celsius

    @celsius.setter
    def celsius(self, valor):
        """Setter com validação"""
        if valor < -273.15:
            raise ValueError("Temperatura abaixo do zero absoluto")
        self._celsius = valor

    @property
    def fahrenheit(self):
        """Propriedade calculada"""
        return (self._celsius * 9/5) + 32

temp = Temperatura(25)
print(f"{temp.celsius}°C = {temp.fahrenheit}°F")  # 25°C = 77.0°F

temp.celsius = 0
print(f"{temp.celsius}°C = {temp.fahrenheit}°F")  # 0°C = 32.0°F

Um exemplo prático completo

Vamos criar uma classe que demonstra todos os conceitos aprendidos:

class Funcionario:
    # Atributo de classe
    empresa = "TechCorp"
    salario_minimo = 1200

    def __init__(self, nome, cargo, salario):
        self.nome = nome
        self.cargo = cargo
        self._salario = salario
        self.horas_trabalhadas = 0

    @property
    def salario(self):
        return self._salario

    @salario.setter
    def salario(self, valor):
        if valor < self.salario_minimo:
            raise ValueError(f"Salário não pode ser menor que R$ {self.salario_minimo}")
        self._salario = valor

    def registrar_hora(self, horas):
        self.horas_trabalhadas += horas
        return f"{self.nome} registrou {horas} horas"

    def calcular_bonus(self):
        if self.horas_trabalhadas > 160:
            return self._salario * 0.10
        return 0

    def __str__(self):
        return f"{self.nome} - {self.cargo} (R$ {self._salario:.2f})"

    def __repr__(self):
        return f"Funcionario('{self.nome}', '{self.cargo}', {self._salario})"

# Criando instâncias
func1 = Funcionario("Ana", "Desenvolvedora", 5000)
func2 = Funcionario("Bruno", "Designer", 3500)

print(func1)  # Ana - Desenvolvedora (R$ 5000.00)
print(func1.registrar_hora(40))  # Ana registrou 40 horas
print(f"Bônus: R$ {func1.calcular_bonus():.2f}")

# Tentando alterar salário
try:
    func2.salario = 800  # Vai lançar exceção
except ValueError as e:
    print(f"Erro: {e}")

Conclusão

Nesta aula, você compreendeu que classes e objetos são a base da Programação Orientada a Objetos em Python, permitindo modelar entidades do mundo real de forma intuitiva e organizada. O método __init__ é o construtor que inicializa seus objetos, eliminando a necessidade de configuração manual após instanciação — isso torna o código mais limpo e seguro. Finalmente, atributos e métodos trabalham juntos para encapsular dados e comportamentos, criando abstrações poderosas que facilitam manutenção, reutilização e escalabilidade de código.

Referências


Artigos relacionados