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.
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.
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.
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.
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.159Variá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.
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 futurasUse 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.
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.
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._balanceUm 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.
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.
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.
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")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") # FalseUse @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.
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) # 10Propriedades 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__:
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
