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 definir seus próprios tipos com seus próprios dados e comportamento. Uma classe Player pode armazenar um nome, uma pontuação e um nível, e saber como se exibir.
Blueprint e instâncias
Uma classe é um blueprint. Uma instância é uma coisa específica feita a partir daquele blueprint. Você pode fazer 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.
Dog(), e você obtém uma instância nova de volta. Cada instância compartilha os métodos da classe, mas mantém seus próprios dados, então rex e luna podem se comportar da mesma forma enquanto são cães separados. __init__ e self
__init__ é o método que Python chama automaticamente quando você cria uma nova instância. É onde você configura os dados iniciais do objeto. self é como um método se refere à instância específica em que 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("Alice")
bob = Player("Bob", score=50)
alice.add_points(30)
alice.display() # "Alice: 30 points"
bob.display() # "Bob: 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 Player tem seu próprio name e score.
__init__ é executado no momento em que você cria uma instância, portanto é onde você define os dados iniciais com self.name = value. self é a instância em que Python está trabalhando, e é sempre o primeiro parâmetro de um método, passado a você automaticamente. Você nunca passa self a si mesmo quando chama alice.display(). Métodos
Qualquer função definida dentro de uma classe é um método. Métodos de instância sempre têm self como o primeiro parâmetro; Python passa automaticamente. Os métodos podem ler e alterar os dados da instância por meio de 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.159self, a instância em que ele está operando. Python passa self para você, então você chama c.area() sem nada extra. Por meio de self, um método lê e altera os próprios dados daquele objeto. 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, únicas para cada objeto.
class Player:
max_lives = 3 # variável de classe, igual para cada 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("Alice")
bob = Player("Bob")
Player.max_lives = 5 # alterar 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.
self em __init__ pertence a aquele objeto. Escrever self.attr = value sempre cria ou atualiza a cópia da instância. Então procure variáveis de classe quando um valor é igual para todos, variáveis de instância quando difere por objeto. __str__ e __repr__
__str__ controla o que print() e f-strings mostram para seu objeto. __repr__ controla a visualização do desenvolvedor mostrada no console e para depuração. Sempre defina __repr__. Defina __str__ quando você quer uma exibição limpa voltada para o usuário separada da visualizaçã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("Alice", 87)
print(alice) # "Alice (87 pts)" (usa __str__)
repr(alice) # "Player(name='Alice', score=87)" (usa __repr__)Sempre defina __repr__. Defina __str__ quando você quer uma representação limpa voltada para o usuário separada da visualização de depuração. Se apenas __repr__ for definido, Python o usa para ambos.
__str__ é o que print() e f-strings mostram, a versão amigável. __repr__ é a visualização do desenvolvedor que você vê no console. Sempre escreva __repr__; é aquele que tem um trabalho mesmo quando você esquece __str__. Adicione __str__ apenas quando o texto voltado para o usuário deve ler diferentemente. Convenção privada
Python não tem variáveis privadas reais, mas um único sublinhado no início de um nome (_balance) é uma convenção que sinaliza "isso é interno, não use diretamente de fora da classe". Não é aplicado pela linguagem; é uma comunicação aos outros desenvolvedores.
class BankAccount:
def __init__(self, balance):
self._balance = balance # _ significa "mãos afastadas"
def deposit(self, amount):
if amount > 0:
self._balance += amount
def balance(self):
return self._balanceUm duplo sublinhado (__name) dispara name mangling; Python renomeia o atributo para _ClassName__name para evitar conflitos em subclasses. Raramente é necessário. Um único sublinhado é a convenção na maioria do código.
_balance) é o sinal acordado para "interno, deixe isso em paz de fora". Nada para você de alcançar, é uma mensagem aos outros desenvolvedores, incluindo você do futuro. Um duplo sublinhado é uma ferramenta rara para evitar choques de nome em subclasses; o único sublinhado é o que você vai usar dia a dia. 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 subclass para alterar seu comportamento. Isso permite reusar uma base comum e especializar onde 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 da sua própria. Elas sobrescrevem speak() com seu comportamento específico.
super()
super() chama um método da classe pai. Use quando você quer estender o comportamento do pai em vez de substituir inteiramente: chame o __init__ do pai para executar sua configuração, depois adicione qualquer coisa que sua subclass precisa em cima.
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 subclass tem seu próprio __init__ e o pai também.
super() alcança a classe pai, então super().__init__() executa a configuração do pai antes de você adicionar a sua. Use quando sua subclass escreve seu próprio `__init__` e o pai tem um também. Pule e a configuração do pai nunca é executada, deixando o objeto meio construído. Métodos de classe e métodos estáticos
@classmethod cria um método que recebe a classe em si, em vez de uma instância. É útil para construtores alternativos: criando uma instância a partir de uma string, arquivo ou outro formato. @staticmethod é uma função simples 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("Alice,87")class Player:
@staticmethod
def is_valid_name(name):
return name.isalpha() and len(name) >= 2
Player.is_valid_name("Alice") # 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 classe.
@classmethod passa a classe em vez de uma instância, o que a torna o go-to para construtores alternativos: construir um `Player` a partir de uma string, arquivo, qualquer formato que você tenha. `@staticmethod` é uma função ordinária embutida dentro da classe para arrumação; não recebe nem a classe nem a instância. Métodos simples tocam dados de um objeto, esses dois não. @property
@property permite acessar um método como um atributo, sem parênteses necessários. Use para valores que são calculados a partir de outros atributos e se sentem naturais para ler como acesso de atributo simples.
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, funciona como um método)
print(c.diameter) # 10Properties são úteis para valores calculados: coisas derivadas de outros atributos que se sentem naturais para acessar sem ().
@property permite ler um método como um atributo, sem parênteses: c.area em vez de c.area(). Encaixa valores que são desenvolvidos a partir de outros atributos e se sentem naturais para ler como dados simples. Nos bastidores, ele ainda executa seu método cada vez que você o acessa. Na prática
Uma classe Player com atributos de instância, métodos, uma @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("Alice")
alice.earn_points(50)
alice.take_hit()
print(alice) # "Alice | Score: 50 | Lives: 2"
print(alice.is_alive) # True
