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

Classes e objetos

docs.scrimba.com

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.

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

Uma declaração class cria um novo tipo (um tipo personalizado de valor, o mesmo tipo de coisa que str e int são) e o vincula a um nome, como qualquer outra atribuição. Você faz uma instância (um objeto concreto daquele tipo) chamando a classe: Player("João"). Nos bastidores, Python aloca um objeto em branco e executa __init__ para preenchê-lo. O modelo é uniforme até o fim: uma classe é um objeto em si que você pode armazenar em uma variável, passar para uma função ou inspecionar em tempo de execução (enquanto o programa está em execução), o que torna possíveis construtores alternativos e registros posteriores neste capítulo.

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.

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

Chamar uma classe faz duas coisas em ordem: aloca uma instância em branco e depois executa __init__ nela para preenchê-la com dados. O objeto acabado se lembra de sua classe (você pode lê-la como type(rex)), e esse vínculo é como Python encontra um método quando você chama rex.bark(): procura na instância primeiro, depois na classe. O resultado prático é que os métodos vivem em um lugar (na classe, compartilhados) enquanto os dados vivem por instância, portanto mil objetos Dog custam mil conjuntos de dados, mas apenas uma cópia de cada método.

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.

JunoBlueprint e instâncias Uma classe é o blueprint, uma instância é uma coisa construída a partir dele. Chame a classe como uma função, 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.
JunoBlueprint e instâncias Chamar Dog() cria uma instância: seus próprios dados, os métodos compartilhados da classe. Esse agrupamento é o ponto todo, valores relacionados e o código que age sobre eles viajam juntos em vez de como variáveis soltas que você passa à mão.
JunoBlueprint e instâncias Chamar uma classe aloca um objeto em branco e depois executa __init__ nele. Os métodos ficam uma vez na classe e são compartilhados, os dados ficam por instância, então dez mil instâncias permanecem baratas.

__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.

__init__ inicializa uma instância recém-alocada. self é um nome convencional para o primeiro parâmetro de cada método de instância; 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__ é executado na instância já alocada e a configura. self não é uma palavra-chave, é o primeiro parâmetro, e Python o preenche para você: quando você chama alice.display(), Python passa alice como self nos bastidores. Definir self.attr = value escreve naquela instância específica, então dois objetos Player nunca pisam nos dados um do outro. Uma coisa que surpreende pessoas vindo de linguagens mais rigorosas: Python permite adicionar atributos em qualquer lugar, não apenas em __init__, então um erro de digitação como self.scor = 0 cria um novo atributo em vez de lançar. Defina cada atributo que uma instância será usada em __init__, até mesmo como None, para que a forma do objeto seja declarada em um lugar e um erro de digitação em outro lugar se destaque.

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("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.

Juno__init__ e self__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().
Juno__init__ e self__init__ configura uma instância nova, e qualquer coisa que você armazene em self é um atributo de instância, uma cópia por objeto. self é o primeiro parâmetro de cada método de instância; Python passa a instância para você quando você escreve alice.display(), você apenas a nomeia na definição.
Juno__init__ e selfself é a instância, passada para você em cada chamada, e escritas em self.attr chegam naquele objeto. Python não vai impedir você de adicionar atributos fora de __init__, então um self.scor com erro de digitação silenciosamente cria um novo atributo. Declare cada atributo em __init__, até mesmo como None, e os erros de digitação não têm para onde se esconder.

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.

Métodos de instância são funções comuns armazenadas na classe. Quando você acessa instance.method, Python devolve um método vinculado: a mesma função com a instância já conectada como self, então você passa apenas os argumentos restantes. Retornar self de um método permite encadeamento: obj.scale(2).rotate(90).

Um método é uma função na classe, compartilhada por cada instância. Acessar c.scale não devolve a função bruta, devolve um método vinculado: aquela mesma função com c já conectado como self, que é como self é passado sem você fazer. Dois padrões compensam em código real. Retornar self de um método mutável oferece uma interface fluente (chamadas encadeadas como c.scale(2).scale(0.5)); reserve para objetos no estilo builder onde o encadeamento lê bem, não para tudo, pois um método que retorna self e um que retorna um novo valor parecem idênticos no local da chamada. E como um método vinculado é um valor de primeira classe, você pode armazenar um (handler = obj.save) e chamá-lo depois, que é no que callbacks e manipuladores de eventos dependem.

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
JunoMétodos Um método é uma função definida dentro de uma classe, e seu primeiro parâmetro é sempre self, 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.
JunoMétodos Métodos são funções simples na classe; alcançá-los através de uma instância vincula self automaticamente, então você passa apenas o resto. Retorne self quando você quer encadeamento como c.scale(2).scale(0.5). Caso contrário, retorne o valor que o chamador pediu.
JunoMétodosobj.method oferece um método vinculado, a função com obj conectado como self, é por isso que você nunca passa. Retornar self compra você encadeamento fluente, vale a pena para builders, confuso se cada método faz isso. E como um método vinculado é um valor, handler = obj.save o armazena para um callback depois.

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.

Uma variável de classe é armazenada uma vez na classe e compartilhada por cada instância. Uma variável de instância é armazenada no objeto individual. Quando você lê self.attr, Python verifica a instância primeiro, depois a classe. Quando você escreve self.attr = value, ela sempre cria ou atualiza a cópia da instância, ocultando a variável de classe apenas para esse objeto.

Ler self.attr verifica a instância primeiro, depois a classe, então uma variável de classe atua como um padrão compartilhado até que alguma instância escreva seu próprio valor e o sombreie. A armadilha que morde em produção é uma variável de classe mutável (uma que você pode alterar no lugar, como uma lista ou dicionário). Atribuir self.x = ... cria uma cópia privada, mas self.x.append(...) não: ela atinge o objeto compartilhado e cada instância vê a alteração. Se você quer uma lista nova por instância, construa em __init__ com self.history = [], nunca como history = [] no nível de classe. Reserve variáveis de classe para coisas que realmente são compartilhadas e as mantenha imutáveis: constantes, padrões, contadores.

python
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 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.

JunoVariáveis de classe vs variáveis de instância Uma variável definida direito na classe é compartilhada por cada instância; uma variável definida em 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.
JunoVariáveis de classe vs variáveis de instância Ler self.attr verifica a instância, depois a classe, então uma variável de classe é um padrão compartilhado. Escrever self.attr = value sempre desemboca na instância e oculta a de classe para esse objeto. Mantenha variáveis de classe para constantes e padrões, estado por objeto vai em self.
JunoVariáveis de classe vs variáveis de instância O bug quieto é uma variável de classe mutável: self.x = ... cria uma cópia privada, mas self.x.append(...) muta o objeto compartilhado, então cada instância vê. Quer uma lista por instância, construa em __init__ com self.history = [], nunca history = [] no nível de classe.

__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.

__str__ é chamado por str(), print() e f-strings do capítulo de saída, o texto voltado para o usuário. __repr__ é chamado por repr() e mostrado quando um objeto é impresso no console, a visualização do desenvolvedor. Se apenas __repr__ for definido, Python o usa para ambos. A convenção: __repr__ retorna uma string que parece o código para recriar o objeto; __str__ retorna um resumo legível.

Esses são dois métodos dunder (abreviação para double-underscore: métodos nomeados com __ iniciais e finais que Python chama para você no momento certo). print(obj) e str(obj) chamam __str__, caindo para __repr__ quando não há __str__. repr(obj) e um objeto mostrado nu no console chamam __repr__. Portanto __repr__ é aquele que sempre tem um trabalho, e é por isso que a regra é: sempre defina __repr__, adicione __str__ apenas quando o texto voltado para o usuário deve diferir do texto de depuração. Faça __repr__ parecer a chamada do construtor que construiu o objeto (Player(name='João', score=87)) e use !r nos campos, então strings mantêm suas aspas e um espaço ou quebra de linha desaparecido mostra em vez de se esconder. Um bom __repr__ é o que torna uma linha de log ou um rastreamento de stack legível às 2 da manhã.

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("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.

Juno__str__ e __repr____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.
Juno__str__ e __repr____str__ é a versão legível para print() e f-strings; __repr__ é a versão de depuração, e Python volta para ela quando não há __str__. Sempre defina __repr__, faça-a parecer a chamada que construiu o objeto, e você se poupa depois quando um objeto aparece em um log.
Juno__str__ e __repr____repr__ sempre tem um trabalho, __str__ apenas quando a visualização do usuário deve diferir, então defina __repr__ primeiro e sempre. Forme-a como o construtor e coloque !r nos campos, então um espaço ou quebra de linha desaparecido mostra em vez de se esconder. Esse único hábito é o que torna um rastreamento de stack de 2 da manhã legível.

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.

Um único sublinhado (_attr) é uma convenção sinalizando uso interno. Python não a aplica, mas todos linters, IDEs e desenvolvedores a respeitam. Um duplo sublinhado (__attr) dispara name mangling: Python o reescreve como _ClassName__attr, o que impede colisão acidental em subclasses. Não é privacidade real; é um mecanismo de prevenção de colisão.

Um único sublinhado é apenas convenção: nada a aplica, mas linters (ferramentas que verificam seu código para estilo e prováveis bugs), editores e revisores leem _balance como "interno, não toque de fora". Um duplo sublinhado dispara name mangling: Python reescreve __attr como _ClassName__attr, então uma subclass que define seu próprio __attr não pode colidir com o pai por acidente. É um protetor de colisão, não privacidade: o nome mangled ainda é alcançável de fora se alguém insistir. Procure por ele apenas em uma classe base destinada a ser subclassificada amplamente, onde você precisa de um atributo que as subclasses não podem pisar; em código ordinário um único sublinhado é o padrão certo e duplo sublinhado principalmente cria confusão. Se você quer parar atributos arbitrários de serem definidos em tudo (o self.scor com erro de digitação de antes), defina __slots__ com a lista fixa de nomes de atributos: ele diz ao Python que esses são os únicos atributos que uma instância pode ter, portanto qualquer outra atribuição levanta em vez de silenciosamente ficar, e apara memória por instância como bônus.

python
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._balance

Um 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.

JunoConvenção privada Python não tem variáveis privadas reais, mas um sublinhado inicial (_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.
JunoConvenção privada Um único sublinhado significa "interno", e toda ferramenta e revisor o respeita, mesmo que Python não o force. Um duplo sublinhado dispara name mangling para _ClassName__attr, que protege contra choques de nome de subclass. Procure pelo único sublinhado por padrão; duplo é raro.
JunoConvenção privada Um único sublinhado é convenção, um duplo sublinhado mangled para _ClassName__attr para parar choques de subclass, nenhum é privacidade real. Mantenha __attr para classes base amplamente subclassificadas; em outro lugar apenas adiciona confusão. Quer banir atributos aleatórios inteiramente e pegar o self.scor com erro de digitação, defina `__slots__` para a lista fixa.

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.

Herança modela uma relação "é-um": um Dog é um Animal. A subclass recebe todos os métodos e atributos do pai e pode sobrescrever qualquer um deles definindo sua própria versão. Quando você chama um método, Python procura na subclass primeiro, depois sobe para o pai, então um método não sobrescrito cai automaticamente. Esse caminho de busca é a ordem de resolução de método (MRO), a lista ordenada de classes que Python procura.

Quando você chama obj.method, Python procura as classes em uma ordem fixa, a MRO (method resolution order: a lista achatada de uma classe e seus ancestrais, legível como Dog.__mro__), e usa a primeira correspondência. Herança única é direta, subclass depois pai. A razão pela qual o MRO existe é herança múltipla e o caso de diamante (dois pais que ambos herdam de um avô comum): Python ordena as classes para que o avó apareça uma vez, depois ambos os pais, e resolve chamadas deterministicamente. A orientação prática: prefira hierarquias rasas e herança única, porque árvores de herança profundas ou múltiplas tornam "qual método realmente executou" difícil de raciocinar. Quando você mistura várias bases, rotear cada __init__ por meio de super() (próxima seção) para que cada uma seja executada exatamente uma vez ao longo dessa ordem.

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 da sua própria. Elas sobrescrevem speak() com seu comportamento específico.

JunoHerança Uma subclass herda tudo que o pai tem, então sobrescreve apenas os métodos que quer alterar. Qualquer coisa que você não sobrescrever cai para o pai de graça, que é o ponto todo: escreva o comportamento compartilhado uma vez, especialize onde difere. `Dog` e `Cat` reutilizam `__init__` de `Animal` e apenas redefinem `speak()`.
JunoHerança Herança é "é-um": a subclass recebe os métodos do pai e sobrescreve o que precisa, e chamadas não sobrescritas caem. Python encontra um método caminhando pelo MRO, subclass primeiro, depois para o pai. Reutilize a base compartilhada, redefina apenas o que difere.
JunoHerança Python resolve um método caminhando pelo MRO e pegando a primeira correspondência, que é o que torna o caso de diamante determinístico. Mantenha hierarquias rasas e confie em herança única; árvores profundas ou com herança múltipla tornam "qual método executou" em um jogo de adivinhação. Quando você deve misturar bases, corrente cada `__init__` por meio de `super()`.

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.

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

super() não significa "meu pai direto", significa "a próxima classe ao longo do MRO" (a ordem de busca da seção anterior). Em uma subclass simples, aquela próxima classe é o pai, então a distinção parece acadêmica, mas é exatamente o que torna herança múltipla funcionar: se cada classe na árvore chama super().__init__(...), cada __init__ é executado uma vez, em ordem MRO, sem nenhuma classe hard-coding um pai pelo nome. Essa é a razão pela qual você escreve super().__init__() e não Animal.__init__(self): nomear o pai diretamente quebra a corrente no momento que outra classe é misturada, e pode executar uma base compartilhada duas vezes. O bare super() sem argumentos é a forma moderna; o super(Dog, self) mais antigo é a mesma coisa digitada por extenso, que você ainda verá em código que alveja versões mais antigas.

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 subclass tem seu próprio __init__ e o pai também.

Junosuper()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.
Junosuper()super() delega para a próxima classe acima, então chame super().__init__() do `__init__` de uma subclass sempre que o pai tiver um. Esquecida e a configuração do pai é pulada, que pode deixar o objeto em estado quebrado. É a linha que conecta sua subclass no pai que ela estende.
Junosuper()super() significa "próxima classe no MRO", não "meu pai", que é por que hard-coding Animal.__init__(self) quebra a corrente uma vez que uma classe é misturada. Se cada `__init__` chama `super()`, cada um é executado uma vez em ordem. Bare `super()` é a forma moderna; `super(Dog, self)` é a mesma coisa digitada por extenso.

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.

@classmethod recebe cls (a classe) como seu primeiro argumento, não uma instância. O uso principal é construtores alternativos que criam instâncias a partir de diferentes formatos de entrada. @staticmethod é uma função regular nomeada sob a classe; não tem acesso à classe ou instância. Use @classmethod para construtores, @staticmethod para funções utilitárias logicamente vinculadas à classe.

@classmethod recebe a classe como seu primeiro argumento (cls) em vez de uma instância, e o detalhe que ganha seu lugar é que cls é a classe em que o método foi chamado, não aquela em que foi definido. Então se Player.from_string constrói com cls(...) e uma subclass ProPlayer chama ProPlayer.from_string(...), ele constrói um ProPlayer, não um Player. É exatamente por isso que construtores alternativos usam @classmethod em vez de hard-coding o nome da classe: eles continuam funcionando sob herança. @staticmethod não toma nem a instância nem a classe; é uma função simples estacionada dentro da classe para nomeação de namespace, quando a lógica pertence à classe, mas precisa de nenhum de seus dados. Procure por um classmethod quando você está fazendo uma instância a partir de algum outro formato, um staticmethod para um ajudante relacionado, e um método normal para qualquer coisa que toque nos dados de um objeto.

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("Alice,87")
python
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")      # 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 classe.

JunoMétodos de classe e métodos estáticos@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.
JunoMétodos de classe e métodos estáticos `@classmethod` pega `cls`, a classe em si, então use para construtores alternativos que retornam `cls(...)`. `@staticmethod` pega nada implicitamente, um ajudante nomeado sob a classe. Método para dados por objeto, classmethod para construir instâncias, staticmethod para um ajudante relacionado.
JunoMétodos de classe e métodos estáticos O ponto de um construtor de classmethod é que `cls` é quem chamou, então `ProPlayer.from_string(...)` constrói um `ProPlayer`, não um `Player`. Hard-code o nome da classe e você perde isso. Classmethod para construir, staticmethod para um ajudante relacionado que precisa de nenhum dado, método normal para tudo que toca a instância.

@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.

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

@property transforma um método em algo que você lê como um atributo: c.area, sem parênteses, executa o método cada vez. Adicione @area.setter e torna-se gravável também, com seu código executado na entrada, que é onde a validação vai (rejeitar um raio negativo antes de ser armazenado). O valor real é que isto não muda nada para chamadores: você pode começar com um atributo self.radius simples e promovê-lo posteriormente para uma propriedade com validação sem tocar uma única linha que o lê ou escreve. É por isso que Python não tem cerimônia getter/setter, você expõe atributos simples e procura por uma propriedade apenas quando um precisa de computação ou proteção. Duas precauções para produção: o acesso parece livre, mas executa código, então mantenha-o barato (cache um resultado caro em vez de recomputar em cada leitura), e uma propriedade que silenciosamente faz trabalho pesado ou levanta surpreende quem quer que assumisse ser um campo simples.

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, funciona como um método)
print(c.diameter)  # 10

Properties são úteis para valores calculados: coisas derivadas de outros atributos que se sentem naturais para acessar sem ().

Juno@property@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.
Juno@property@property lê um método como um atributo, e um `@name.setter` emparelhado o torna gravável para você validar na entrada. A vitória: você pode transformar um atributo simples em uma propriedade depois sem alterar qualquer chamador. Use para valores calculados ou protegidos, não como um hábito em cada campo.
Juno@property A razão para gostar de `@property`: um atributo simples pode se tornar computado-ou-validado depois sem alteração a chamadores, então você pula getters e setters até que realmente precise de um. O acesso parece livre, mas executa código, então mantenha barato, cache os caros, e não esconda trabalho pesado ou um raise atrás do que lê como um campo.

Na prática

Uma classe Player com atributos de instância, métodos, uma @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("Alice")
alice.earn_points(50)
alice.take_hit()
print(alice)            # "Alice | 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, "alice", "[email protected]")
print(alice.to_dict())
alice.deactivate()
print(alice.active)   # False

Uma classe DataSplit que encapsula fatiamento train/validação atrás de properties, com __repr__ para 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 de sublinhado em _train e _val sinaliza que chamadores devem ir através das properties em vez de mutar as listas brutas diretamente. Python não vai forçar isto, mas define um contrato claro.