Classes e objetos
Todo tipo com o qual você já trabalhou até agora (strings, listas, dicionários) é, na verdade, uma classe. Quando você chama "hello".upper(), você está chamando um método em um objeto string. Classes permitem que você defina seus próprios tipos, com seus próprios dados e comportamentos. Uma classe Player pode guardar um nome, uma pontuação e um nível, e saber como se exibir.
Modelo e instâncias
Uma classe é um modelo (um "molde"). Uma instância é uma coisa específica feita 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 cachorros diferentes, cada um compartilhando o mesmo comportamento definido na classe.
Dog(), e você recebe uma instância nova. Toda instância compartilha os métodos da classe, mas mantém seus próprios dados, de modo que rex e luna podem se comportar da mesma forma sendo cachorros diferentes. __init__ e self
__init__ é o método que o Python chama automaticamente quando você cria uma nova instância. É ali que você configura os dados iniciais do objeto. self é a forma que um método usa para se referir à 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("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 de Player tem seu próprio name e score.
__init__ roda no momento em que você cria uma instância, então é ali que você define os dados iniciais com self.name = value. self é a instância com a qual o Python está trabalhando, e é sempre o primeiro parâmetro de um método, entregue a você automaticamente. Você nunca passa self manualmente 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 primeiro parâmetro; o Python o passa automaticamente. Métodos podem ler e alterar os dados da instância através 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 # returning self allows chaining: 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 sobre a qual ele está trabalhando. O Python passa self para você, então você chama c.area() sem nada extra. Através de self, um método lê e altera os dados daquele próprio 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 # class variable, same for every Player
def __init__(self, name):
self.name = name # instance variable, unique to each Player
self.lives = Player.max_lives
def die(self):
self.lives -= 1
alice = Player("Alice")
bob = Player("Bob")
Player.max_lives = 5 # change for all current and future instancesUse 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 dentro de __init__ pertence só àquele objeto. Escrever self.attr = value sempre cria ou atualiza a cópia própria da instância. Então use variáveis de classe quando um valor é igual para todos, variáveis de instância quando ele varia por objeto. __str__ e __repr__
__str__ controla o que print() e f-strings mostram para o seu objeto. __repr__ controla a visão de desenvolvedor mostrada no console e usada para depuração. Sempre defina __repr__. Defina __str__ quando quiser uma exibição limpa e voltada para o 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("Alice", 87)
print(alice) # "Alice (87 pts)" (uses __str__)
repr(alice) # "Player(name='Alice', score=87)" (uses __repr__)Sempre defina __repr__. Defina __str__ quando quiser uma representação limpa voltada para o usuário, separada da visão de depuração. Se apenas __repr__ estiver definido, o Python o usa para ambos.
__str__ é o que print() e f-strings mostram, a versão amigável. __repr__ é a visão de desenvolvedor que você vê no console. Sempre escreva __repr__; é ele que cumpre uma função mesmo quando você se esquece do __str__. Adicione __str__ só quando o texto voltado para o usuário precisar ser diferente. Convenção de privacidade
O Python não tem variáveis realmente privadas, mas um único underscore no início de um nome (_balance) é uma convenção que sinaliza "isso é 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 # _ means "hands off"
def deposit(self, amount):
if amount > 0:
self._balance += amount
def balance(self):
return self._balanceUm duplo underscore (__name) dispara o name mangling; o Python renomeia o atributo para _ClassName__name para evitar conflitos em subclasses. É raramente necessário. O underscore único é a convenção na maior parte do código.
_balance) é o sinal combinado para "interno, não toque nisso de fora". Nada te impede de acessar, é uma mensagem para outros desenvolvedores, incluindo você no futuro. O duplo underscore é uma ferramenta mais rara para evitar conflitos de nomes em subclasses; o underscore único é o que você vai usar no dia a dia. Herança
Uma classe pode herdar de outra classe, recebendo 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 reaproveitar 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 do seu próprio. Eles sobrescrevem speak() com seu comportamento específico.
Dog e Cat reaproveitam o __init__ de Animal e só redefinem speak(). super()
super() chama um método da classe-mãe. Use-o quando quiser estender o comportamento da classe-mãe em vez de substituí-lo completamente: chame o __init__ da classe-mãe para executar sua configuração, e depois adicione o que sua subclasse precisar por 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") # call Animal.__init__
self.tricks = [] # add something 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 tiver seu próprio __init__ e a classe-mãe também tiver.
super() alcança a classe-mãe, então super().__init__() executa a configuração da classe-mãe antes de você adicionar a sua própria. Use isso quando sua subclasse escrever seu próprio __init__ e a classe-mãe também tiver um. Pule isso e a configuração da classe-mãe nunca roda, deixando o objeto pela metade. 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, um arquivo, ou outro formato. @staticmethod é uma função comum que vive dentro da classe por motivos de organização; 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 nem de classe.
@classmethod te entrega a classe em vez de uma instância, o que o torna a escolha certa para construtores alternativos: montar um Player a partir de uma string, um arquivo, qualquer formato que você tiver. @staticmethod é uma função comum guardada dentro da classe por organização; não recebe nem a classe nem a instância. Métodos comuns tocam nos dados de um objeto, esses dois não. @property
@property permite acessar um método como se fosse um atributo, sem precisar de parênteses. Use isso para valores que são calculados a partir de outros atributos e que parecem naturais de ler como um simples acesso 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 (looks like an attribute, runs like a method)
print(c.diameter) # 10Properties são úteis para valores calculados: coisas derivadas de outros atributos que parecem naturais de acessar sem ().
@property permite ler um método como um atributo, sem parênteses: c.area em vez de c.area(). Se encaixa em valores calculados a partir de outros atributos e que parecem naturais de ler como dados simples. Nos bastidores, ainda roda seu método a cada acesso. 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
