Skip to content
This page has been auto-translated and may contain errors.View in English

Classes e objetos

Todo tipo com o qual você trabalhou até agora (strings, listas, dicionários) é na verdade uma classe. Quando você chama "hello".upper(), está chamando um método em um objeto string. Classes permitem que você defina seus próprios tipos com seus próprios dados e comportamento. Uma classe Player pode guardar um nome, uma pontuação e um nível, e saber como se exibir.

Classes são o mecanismo para tipos definidos pelo usuário. Uma classe define um molde: os dados que cada instância guarda (atributos) e as operações que ela suporta (métodos). Em vez de rastrear valores em variáveis paralelas e passá-los por toda parte, você os agrupa em um objeto coeso com uma interface clara.

Uma instrução class cria um novo objeto tipo no namespace atual, usando uma metaclasse (padrão type) para construí-lo. Instâncias são criadas chamando a classe, o que invoca __new__ para alocar o objeto e __init__ para inicializá-lo. O modelo de objetos do Python é uniforme: tudo é um objeto, incluindo classes, funções e os próprios tipos.

Molde e instâncias

Uma classe é um molde. Uma instância é algo específico feito a partir desse molde. Você pode criar quantas instâncias precisar, cada uma com seus próprios dados, mas compartilhando os mesmos métodos definidos na classe.

Uma classe define estrutura e comportamento. Instâncias são objetos criados a partir dessa classe; cada uma tem seu próprio namespace de atributos, mas compartilha os objetos método da classe. Criar uma instância chama a classe como uma função: Dog() cria uma nova instância de Dog.

Chamar uma classe invoca type.__call__, que chama cls.__new__(cls) para alocar a instância e cls.__init__(instance, ...) para inicializá-la. O __class__ do objeto resultante aponta de volta para a classe. A busca por métodos segue a MRO: __dict__ da instância, __dict__ da classe e, em seguida, a cadeia MRO.

python
class Dog:
    def bark(self):
        print("Woof!")

rex  = Dog()
luna = Dog()

rex.bark()    # "Woof!"
luna.bark()   # "Woof!"

Dog é a classe. rex e luna são instâncias: dois cães diferentes, cada um compartilhando o mesmo comportamento definido na classe.

__init__ e self

__init__ é o método que o Python chama automaticamente quando você cria uma nova instância. É onde você define os dados iniciais do objeto. self é como um método se refere à instância específica sobre a qual está operando, e é sempre o primeiro parâmetro.

__init__ inicializa uma instância recém-alocada. self é um nome convencional para o primeiro parâmetro de todo método de instância; o Python passa a instância automaticamente quando você chama alice.display(). Atributos definidos em self dentro de __init__ são atributos de instância: cada instância tem sua própria cópia.

__init__ recebe a instância já alocada de __new__ e a configura. self não é uma palavra-chave; é o primeiro parâmetro posicional, passado implicitamente pelo protocolo de descritor quando você chama instance.method(). Definir self.attr = value chama object.__setattr__, que escreve no __dict__ da instância. Atributos não definidos em __init__ ainda podem ser definidos depois em qualquer instância, já que os dicts do Python são dinâmicos.

python
class Player:
    def __init__(self, name, score=0):
        self.name  = name
        self.score = score

    def add_points(self, points):
        self.score += points

    def display(self):
        print(f"{self.name}: {self.score} points")

alice = Player("Ana")
bob   = Player("Bruno", score=50)

alice.add_points(30)
alice.display()   # "Ana: 30 points"
bob.display()     # "Bruno: 50 points"

self.name e self.score são atributos de instância: pertencem ao objeto específico, não à classe em si. Cada instância de Player tem seu próprio name e score.

Métodos

Qualquer função definida dentro de uma classe é um método. Métodos de instância sempre têm self como primeiro parâmetro; o Python o passa automaticamente. Métodos podem ler e alterar os dados da instância via self.

Métodos de instância são funções comuns armazenadas no namespace da classe. O protocolo de descritor transforma instance.method em um método vinculado (bound method), vinculando self automaticamente. Retornar self de um método permite encadeamento de métodos: obj.scale(2).rotate(90).

instance.method aciona a busca por descritor: type.__getattribute__ encontra method no __dict__ da classe, reconhece-o como uma função (que implementa o protocolo de descritor) e retorna um objeto método vinculado que envolve a função com a instância pré-aplicada. É assim que self é passado automaticamente. Retornar self permite interfaces fluentes.

python
class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def scale(self, factor):
        self.radius *= factor
        return self    # retornar self permite encadeamento: c.scale(2).scale(0.5)

c = Circle(5)
print(c.area())    # 78.53975
c.scale(2)
print(c.area())    # 314.159

Variáveis de classe vs variáveis de instância

Variáveis definidas diretamente na classe (não dentro de __init__) são variáveis de classe. Todas as instâncias compartilham a mesma variável de classe. Variáveis definidas em self dentro de __init__ são variáveis de instância, exclusivas de cada objeto.

Variáveis de classe vivem no __dict__ da classe e são compartilhadas por todas as instâncias. Variáveis de instância vivem no __dict__ de cada instância. Quando você lê self.attr, o Python verifica primeiro a instância, depois a classe. Quando você escreve self.attr = value, sempre cria ou atualiza o atributo da instância, sombreando a variável de classe se existir.

A busca de atributo em uma instância segue esta ordem: __dict__ da instância, __dict__ da classe e, em seguida, a MRO. Escrever em self.attr sempre tem como alvo o __dict__ da instância (a menos que __setattr__ seja sobrescrito). Uma variável de classe é compartilhada até que uma escrita em self.attr crie uma sombra de instância. Variáveis de classe mutáveis (como listas) são particularmente traiçoeiras: todas as instâncias compartilham o mesmo objeto, então a mutação via qualquer instância é visível para todas.

python
class Player:
    max_lives = 3    # variável de classe, igual para todo Player

    def __init__(self, name):
        self.name  = name   # variável de instância, única para cada Player
        self.lives = Player.max_lives

    def die(self):
        self.lives -= 1

alice = Player("Ana")
bob   = Player("Bruno")

Player.max_lives = 5    # muda para todas as instâncias atuais e futuras

Use variáveis de classe para valores compartilhados entre todas as instâncias: constantes, contadores, padrões. Use variáveis de instância para dados que diferem por objeto.

__str__ e __repr__

__str__ controla o que print() e f-strings mostram para o seu objeto. __repr__ controla a visão do desenvolvedor mostrada no console e para depuração. Sempre defina __repr__. Defina __str__ quando quiser uma exibição limpa voltada ao usuário, separada da visão de depuração.

__str__ é chamado por str(), print() e f-strings. __repr__ é chamado por repr() e mostrado no REPL. Se apenas __repr__ for definido, o Python o usa para ambos. A convenção: __repr__ deve retornar uma string que poderia reconstruir o objeto; __str__ deve retornar um resumo legível para humanos.

str(obj) chama type(obj).__str__(obj), recorrendo a __repr__ se __str__ não estiver definido. repr(obj) chama type(obj).__repr__(obj). Dentro de f-strings, {obj} chama __format__, que por padrão chama __str__. {obj!r} aplica repr() antes da formatação. A convenção de __repr__: retornar uma string da forma ClassName(arg1=..., arg2=...) que, quando avaliada, reproduziria o objeto.

python
class Player:
    def __init__(self, name, score):
        self.name  = name
        self.score = score

    def __str__(self):
        return f"{self.name} ({self.score} pts)"

    def __repr__(self):
        return f"Player(name={self.name!r}, score={self.score})"

alice = Player("Ana", 87)
print(alice)        # "Ana (87 pts)"   (usa __str__)
repr(alice)         # "Player(name='Ana', score=87)"  (usa __repr__)

Sempre defina __repr__. Defina __str__ quando quiser uma representação limpa voltada ao usuário, separada da visão de depuração. Se apenas __repr__ for definido, o Python o usa para ambos.

Convenção de privacidade

O Python não tem variáveis verdadeiramente privadas, mas um único sublinhado no início de um nome (_balance) é uma convenção que sinaliza "isto é interno, não use diretamente de fora da classe". Não é imposto pela linguagem; é uma comunicação para outros desenvolvedores.

Um único sublinhado (_attr) é uma convenção que sinaliza uso interno. O Python não a impõe, mas todos os linters, IDEs e desenvolvedores a respeitam. Um sublinhado duplo (__attr) aciona name mangling: o Python o reescreve para _ClassName__attr, o que evita colisões acidentais em subclasses. Não é privacidade verdadeira; é um mecanismo de prevenção de colisões.

Sublinhado único: apenas convenção, sem imposição. Sublinhado duplo: name mangling transforma __attr em _ClassName__attr em tempo de compilação, não em tempo de execução. Isso é uma proteção contra colisões em subclasses, não um mecanismo de privacidade; o nome transformado ainda é acessível externamente. __slots__ é um controle mais fundamental: substitui o __dict__ da instância por um conjunto fixo de descritores slot, evitando a criação arbitrária de atributos e reduzindo o overhead de memória.

python
class BankAccount:
    def __init__(self, balance):
        self._balance = balance    # _ significa "não mexa"

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

    def balance(self):
        return self._balance

Um sublinhado duplo (__name) aciona name mangling; o Python renomeia o atributo para _ClassName__name para evitar conflitos em subclasses. Raramente é necessário. O sublinhado único é a convenção na maior parte do código.

Herança

Uma classe pode herdar de outra classe, obtendo automaticamente todos os seus atributos e métodos. Você pode então sobrescrever métodos específicos na subclasse para mudar seu comportamento. Isso permite reutilizar uma base comum e especializar onde for necessário.

Herança cria uma relação "é um". A subclasse herda todos os métodos e atributos do pai e pode sobrescrever qualquer um deles. A ordem de resolução de métodos (MRO) define a sequência de busca por atributos. O Python suporta herança múltipla; a MRO é computada com o algoritmo de linearização C3.

A MRO é computada por type.__mro_entries__ usando a linearização C3, acessível como ClassName.__mro__. A resolução de métodos segue a MRO da esquerda para a direita. isinstance(obj, cls) percorre a MRO para verificar. Herança múltipla é poderosa, mas pode criar herança em diamante; a MRO a resolve deterministicamente. super() segue a MRO, não apenas a classe pai direta.

python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

pets = [Dog("Rex"), Cat("Luna"), Dog("Max")]
for pet in pets:
    print(pet.speak())

Dog e Cat herdam __init__ de Animal, então não precisam ter os próprios. Eles sobrescrevem speak() com seu comportamento específico.

super()

super() chama um método da classe pai. Use-o quando quiser estender o comportamento do pai em vez de substituí-lo totalmente: chame o __init__ do pai para executar sua configuração e adicione qualquer coisa que sua subclasse precise em cima disso.

super() retorna um objeto proxy que delega chamadas de método para a próxima classe na MRO. Sempre chame super().__init__() a partir do __init__ de uma subclasse quando o pai tiver um. Pular essa chamada significa que o código de configuração do pai não é executado, o que pode deixar o objeto em um estado quebrado.

super() sem argumentos (Python 3) usa __class__ (uma variável de célula injetada pelo compilador) e o primeiro argumento do método envolvente para determinar a posição na MRO. super(cls, self) é a forma explícita. O proxy resolve a busca de métodos começando pela próxima classe em type(self).__mro__ depois de cls. Isso é essencial para herança múltipla correta: super() encadeia o __init__ de cada classe pela MRO completa.

python
class Animal:
    def __init__(self, name, sound):
        self.name  = name
        self.sound = sound

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name, "Woof")   # chama Animal.__init__
        self.tricks = []                  # adiciona algo extra

    def learn(self, trick):
        self.tricks.append(trick)

rex = Dog("Rex")
rex.learn("sit")
print(rex.tricks)   # ["sit"]

Sempre chame super().__init__() quando sua subclasse tem seu próprio __init__ e o pai também tem.

Métodos de classe e métodos estáticos

@classmethod cria um método que recebe a própria classe em vez de uma instância. É útil para construtores alternativos: criar uma instância a partir de uma string, de um arquivo ou de outro formato. @staticmethod é uma função comum que vive dentro da classe por razões organizacionais; não recebe nem a instância nem a classe.

@classmethod recebe cls (a classe) como primeiro argumento, não uma instância. O principal uso é para construtores alternativos que criam instâncias a partir de diferentes formatos de entrada. @staticmethod é uma função comum colocada no namespace da classe; não tem acesso à classe nem à instância. Use @classmethod para construtores, @staticmethod para funções utilitárias logicamente associadas à classe.

@classmethod usa o protocolo de descritor: o descritor classmethod retorna um método vinculado onde o primeiro argumento é a classe, não a instância. cls é a classe real no momento da chamada, então métodos chamados em subclasses recebem a subclasse. @staticmethod é um descritor mais simples que retorna a função subjacente inalterada, sem primeiro argumento implícito. Ambos os decoradores modificam o comportamento do descritor; nenhum afeta o objeto código da função.

python
class Player:
    def __init__(self, name, score):
        self.name  = name
        self.score = score

    @classmethod
    def from_string(cls, data):
        name, score = data.split(",")
        return cls(name, int(score))

alice = Player.from_string("Ana,87")
python
class Player:
    @staticmethod
    def is_valid_name(name):
        return name.isalpha() and len(name) >= 2

Player.is_valid_name("Ana")     # True
Player.is_valid_name("A1")      # False

Use @classmethod para construtores alternativos. Use @staticmethod para funções utilitárias que logicamente pertencem à classe, mas não precisam de dados de instância ou de classe.

@property

@property permite acessar um método como se fosse um atributo, sem precisar de parênteses. Use-o para valores que são calculados a partir de outros atributos e que fica natural ler como acesso simples a atributo.

@property transforma um método em um atributo somente leitura. O método é executado quando o atributo é acessado. Isso é útil para valores computados derivados de dados armazenados e para adicionar validação ao acesso a atributos sem mudar a interface pública. Um @name.setter correspondente torna o atributo gravável.

@property é um descritor: property(fget, fset, fdel, doc). Acessar instance.attr chama fget(instance). @name.setter define fset. @name.deleter define fdel. Propriedades são procuradas na classe, não na instância, e é por isso que instance.__dict__[name] não sombreia uma propriedade (descritores de dados têm prioridade sobre o __dict__ da instância). Propriedades são a alternativa Pythonica aos métodos getter/setter no estilo Java.

python
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2

    @property
    def diameter(self):
        return self.radius * 2

c = Circle(5)
print(c.area)      # 78.53975 (parece um atributo, executa como um método)
print(c.diameter)  # 10

Propriedades são úteis para valores computados: coisas derivadas de outros atributos que fica natural acessar sem ().

Na prática

Uma classe Player com atributos de instância, métodos, um @property e __str__:

python
class Player:
    max_lives = 3

    def __init__(self, name: str):
        self.name  = name
        self.score = 0
        self.lives = Player.max_lives

    def earn_points(self, amount: int) -> None:
        self.score += amount

    def take_hit(self) -> bool:
        self.lives -= 1
        return self.lives > 0

    @property
    def is_alive(self) -> bool:
        return self.lives > 0

    def __str__(self) -> str:
        return f"{self.name} | Score: {self.score} | Lives: {self.lives}"

alice = Player("Ana")
alice.earn_points(50)
alice.take_hit()
print(alice)            # "Ana | Score: 50 | Lives: 2"
print(alice.is_alive)   # True

Uma classe User que usa um atributo privado com um getter @property, um método deactivate e um serializador to_dict:

python
class User:
    def __init__(self, user_id: int, username: str, email: str):
        self.id       = user_id
        self.username = username
        self.email    = email
        self._active  = True

    @property
    def active(self) -> bool:
        return self._active

    def deactivate(self) -> None:
        self._active = False

    def to_dict(self) -> dict:
        return {
            "id":       self.id,
            "username": self.username,
            "email":    self.email,
            "active":   self._active,
        }

    def __repr__(self) -> str:
        return f"User(id={self.id}, username={self.username!r})"

alice = User(1, "ana", "[email protected]")
print(alice.to_dict())
alice.deactivate()
print(alice.active)   # False

Uma classe DataSplit que encapsula o fatiamento de treino/validação por trás de propriedades, com __repr__ para uma saída de depuração limpa:

python
class DataSplit:
    def __init__(self, data: list, train_ratio: float = 0.8):
        split       = int(len(data) * train_ratio)
        self._train = data[:split]
        self._val   = data[split:]

    @property
    def train(self) -> list:
        return self._train

    @property
    def val(self) -> list:
        return self._val

    @property
    def sizes(self) -> tuple[int, int]:
        return len(self._train), len(self._val)

    def __repr__(self) -> str:
        return f"DataSplit(train={len(self._train)}, val={len(self._val)})"

data  = list(range(100))
split = DataSplit(data, train_ratio=0.8)
print(split)         # DataSplit(train=80, val=20)
print(split.sizes)   # (80, 20)

O prefixo com sublinhado em _train e _val sinaliza que quem chama deve passar pelas propriedades em vez de mutar as listas brutas diretamente. O Python não impõe isso, mas estabelece um contrato claro.