Orientação a Objetos¶
O que é programação orientada a objetos?¶
A programação orientada a objetos é um paradigma de programação que fornece um meio de estruturar programas para que propriedades e comportamentos sejam agrupados em objetos individuais .
Por exemplo, um objeto pode representar uma pessoa com propriedades como nome, idade e endereço e comportamentos como andar, falar, respirar e correr. Ou pode representar um e-mail com propriedades como lista de destinatários, assunto e corpo e comportamentos como adicionar anexos e enviar.
Dito de outra forma, a programação orientada a objetos é uma abordagem para modelar coisas concretas do mundo real, como carros, bem como relações entre coisas, como empresas e funcionários ou estudantes e professores. A OOP modela entidades do mundo real como objetos de software que possuem alguns dados associados a eles e podem realizar determinadas operações.
A principal conclusão é que os objetos estão no centro da programação orientada a objetos em Python. Em outros paradigmas de programação, os objetos representam apenas os dados. Na OOP, eles informam adicionalmente a estrutura geral do programa.
Como você define uma classe em Python?¶
Em Python, você define uma classe usando a palavra-chave class seguida por um nome e dois pontos. Depois você usa .init() para declarar quais atributos cada instância da classe deve ter:
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
emp = Employee('Joe', 32)
emp
<__main__.Employee at 0x2234b123cb0>
print(emp.age)
emp.name
32
'Joe'
Estruturas de dados primitivas¶
Mas o que tudo isso significa? E por que você precisa de aulas em primeiro lugar? Dê um passo atrás e considere o uso de estruturas de dados primitivas integradas como alternativa.
Estruturas de dados primitivas — como números , strings e listas — são projetadas para representar informações simples, como o custo de uma maçã, o nome de um poema ou suas cores favoritas, respectivamente. E se você quiser representar algo mais complexo?
Por exemplo, você pode querer rastrear funcionários de uma organização. Você precisa armazenar algumas informações básicas sobre cada funcionário, como nome, idade, cargo e ano em que começou a trabalhar.
Uma maneira de fazer isso é representar cada funcionário como uma lista :
kirk = ["James Kirk", 34, "Captain", 2265]
spock = ["Spock", 35, "Science Officer", 2254]
mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
Existem vários problemas com essa abordagem.
Primeiro, pode dificultar o gerenciamento de arquivos de código maiores. Se você fizer referência kirk[0], a várias linhas de onde declarou a kirklista, você lembrará que o elemento com índice 0 é o nome do funcionário?
Em segundo lugar, pode introduzir erros se os funcionários não tiverem o mesmo número de elementos nas respectivas listas. Na mccoylista acima, a idade está faltando, então mccoy[1]retornará "Chief Medical Officer"em vez da idade do Dr. McCoy .
Uma ótima maneira de tornar esse tipo de código mais gerenciável e de fácil manutenção é usar classes .
Classes vs Instâncias¶
As classes permitem criar estruturas de dados definidas pelo usuário. As classes definem funções chamadas métodos, que identificam os comportamentos e ações que um objeto criado a partir da classe pode realizar com seus dados.
Neste roteiro, você criará uma classe Dog que armazena algumas informações sobre as características e comportamentos que um cão individual pode ter.
Uma classe é um modelo de como definir algo. Na verdade, não contém nenhum dado. A classe Dog especifica que um nome e uma idade são necessários para definir um cachorro, mas não contém o nome ou a idade de nenhum cachorro específico.
Embora a classe seja o modelo, uma instância é um objeto construído a partir de uma classe e contém dados reais. Uma instância da classe Dog não é mais um projeto. É um cachorro de verdade com um nome, como Miles, que tem quatro anos.
Dito de outra forma, uma classe é como um formulário ou questionário. Uma instância é como um formulário que você preencheu com informações. Assim como muitas pessoas podem preencher o mesmo formulário com suas próprias informações exclusivas, você pode criar muitas instâncias a partir de uma única classe.
Definição de classe¶
Você inicia todas as definições de classe com a palavra-chave class e, em seguida, adiciona o nome da classe e dois pontos. O Python considerará qualquer código recuado abaixo da definição da classe como parte do corpo da classe.
Aqui está um exemplo de classe Dog:
# dog.py
class Dog:
pass
dog = Dog()
print(dog)
<__main__.Dog object at 0x000002234B2E4EC0>
O corpo da classe Dog consiste em uma única instrução: a palavra-chave pass. Os programadores Python costumam usar o pass como um espaço reservado para indicar para onde o código irá eventualmente. Ele permite que você execute este código sem que o Python gere um erro.
Nota: Os nomes das classes Python são escritos em notação CapitalizedWords por convenção. Por exemplo, uma classe para uma raça específica de cachorro, como o Jack Russell Terrier, seria escrita como JackRussellTerrier.
A classe Dog não é muito interessante no momento, então você vai enfeitá-la um pouco definindo algumas propriedades que todos Dogs objetos deveriam ter. Existem várias propriedades que você pode escolher, incluindo nome, idade, cor da pelagem e raça. Para manter o escopo do exemplo pequeno, você usará apenas nome e idade.
Você define as propriedades que todos os objetos Dog devem ter em um método chamado .init(). Cada vez que você cria um novo objeto Dog, define o estado inicial do objeto .init() atribuindo os valores das propriedades do objeto. Ou seja, inicializa cada nova instância da classe. .init()
Você pode fornecer .init() qualquer número de parâmetros, mas o primeiro parâmetro sempre será uma variável chamada self. Quando você cria uma nova instância de classe, o Python passa automaticamente a instância para o parâmetro self no .init() para que o Python possa definir os novos atributos no objeto.
Atualize a classe Dog com um método .init() que cria .name e .age:
# dog.py
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
dog = Dog("Fido", 3)
dog.age
3
Certifique-se de recuar a assinatura .init() do método em quatro espaços e o corpo do método em oito espaços. Este recuo é de vital importância. Diz ao Python que o método .init() pertence à classe Dog.
No corpo de .init(), existem duas instruções usando a variável self:
self.name = name - cria um atributo chamado name e atribui o valor do parâmetro name a ele.
self.age = age - cria um atributo chamado age e atribui o valor do parâmetro age a ele.
Os atributos criados em .init() são chamados de atributos de instância. O valor de um atributo de instância é específico para uma instância específica da classe. Todos os objetos Dog têm um nome e uma idade, mas os valores dos atributos name e age variam dependendo da instância Dog.
Por outro lado, atributos de classe são atributos que possuem o mesmo valor para todas as instâncias de classe. Você pode definir um atributo de classe atribuindo um valor a um nome de variável fora de .init().
Por exemplo, a classe Dog a seguir possui um atributo de classe chamado species com o valor "Canis familiaris":
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
auau = Dog("Auau", 3)
print(auau.species)
Canis familiaris
miau = Dog("Miau", 2)
print(auau.species)
Canis familiaris
Você define os atributos da classe diretamente abaixo da primeira linha do nome da classe e os recua com quatro espaços. Você sempre precisa atribuir a eles um valor inicial. Quando você cria uma instância da classe, o Python cria e atribui automaticamente atributos de classe aos seus valores iniciais.
Use atributos de classe para definir propriedades que devem ter o mesmo valor para cada instância de classe. Use atributos de instância para propriedades que variam de uma instância para outra.
Agora que você tem Dogaula, é hora de criar alguns cachorros!
Como você instancia uma classe em Python?¶
Criar um novo objeto a partir de uma classe é chamado de instanciar uma classe. Você pode criar um novo objeto digitando o nome da classe, seguido de abertura e fechamento de parênteses:
class Dog:
pass
Dog() # Execute
<__main__.Dog at 0x2077a2c54f0>
Primeiro você cria uma nova classe Dog sem atributos ou métodos e, em seguida, instancia a classe Dog para criar um objeto Dog.
Na saída acima, você pode ver que agora tem um novo objeto Dog em 0x106702d30. Essa sequência engraçada de letras e números é um endereço de memória que indica onde o Python armazena o objeto Dog na memória do seu computador. Observe que o endereço na sua tela será diferente.
Agora instancie a classe Dog uma segunda vez para criar outro objeto Dog:
Dog() #execute
<__main__.Dog at 0x2077a23ec00>
A nova instância Dog está localizada em um endereço de memória diferente. Isso ocorre porque é uma instância totalmente nova e completamente exclusiva do primeiro Dogobjeto que você criou.
Para ver isso de outra maneira, digite o seguinte:
a = Dog("Fido", 3)
print(a) # Execute
b = Dog("Fido", 2)
print(b)
a.name == b.name # Execute
<__main__.Dog object at 0x000002234B128E50> <__main__.Dog object at 0x000002234B0975C0>
True
Neste código, você cria dois novos objetos Dog e os atribui às variáveis a e b. Quando você compara a e b com o operador ==, o resultado é False. Embora a e b sejam instâncias da classe Dog, eles representam dois objetos distintos na memória.
Atributos de classe e instância¶
Agora crie uma nova classe Dog com um atributo de classe chamado .species e dois atributos de instância chamados .name e .age:
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
Para instanciar esta Classe Dog, você precisa fornecer valores para name e age. Caso contrário, o Python gera um TypeError:
rex = Dog('rex', 5) # Execute
rex.name
'rex'
Para passar argumentos para os parâmetros name e age, coloque os valores entre parênteses após o nome da classe:
miles = Dog("Miles", 4)
buddy = Dog("Buddy", 9)
type(buddy)
__main__.Dog
Isso cria duas novas instâncias Dog – uma para um cachorro de quatro anos chamado Miles e outra para um cachorro de nove anos chamado Buddy.
O método .init() da classe Dog tem três parâmetros, então por que você está passando apenas dois argumentos para ele no exemplo?
Quando você instancia a classe Dog, o Python cria uma nova instância de Dog e a passa para o primeiro parâmetro de .init(). Isso essencialmente remove o parâmetro self, então você só precisa se preocupar com os parâmetros name e age.
Nota: Nos bastidores, o Python cria e inicializa um novo objeto quando você usa esta sintaxe.
Depois de criar as instâncias Dog, você poderá acessar seus atributos de instância usando a notação de ponto:
print(miles.name)
print(miles.age)
print(buddy.name)
print(buddy.age)
Miles 4 Buddy 9
Você pode acessar atributos de classe da mesma maneira:
buddy.species
'Canis familiaris'
Uma das maiores vantagens de usar classes para organizar dados é que as instâncias têm a garantia de ter os atributos esperados. Todas as instâncias Dog possuem atributos .species, .name, e .age, portanto você pode usar esses atributos com confiança, sabendo que eles sempre retornarão um valor.
Embora seja garantido que os atributos existam, seus valores podem mudar dinamicamente:
buddy.age = 10
print(buddy.age)
10
Neste exemplo, você altera o atributo .age do objeto buddy para 10. Então você altera o atributo .species do objeto miles para "Felis silvestris", que é uma espécie de gato. Isso faz de Miles um cachorro muito estranho, mas é um Python válido!
A principal conclusão aqui é que os objetos personalizados são mutáveis por padrão. Um objeto é mutável se você puder alterá-lo dinamicamente. Por exemplo, listas e dicionários são mutáveis, mas strings e tuplas são imutáveis .
Métodos de instância¶
Métodos de instância são funções que você define dentro de uma classe e só podem chamar uma instância dessa classe. Assim como .init(), um método de instância sempre leva self como primeiro parâmetro.
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
# Instance method
def description(self):
return f"{self.name} is {self.age} years old"
# Another instance method
def speak(self, sound):
return f"{self.name} says {sound}"
Esta classe Dog possui dois métodos de instância:
.description() - retorna uma string exibindo o nome e a idade do cachorro.
.speak() - tem um parâmetro chamado sounde retorna uma string contendo o nome do cachorro e o som que o cachorro faz.
Salve a classe Dog modificada em um arquivo chamado dog.py e pressione F5 para executar o programa. Em seguida, abra a janela interativa e digite o seguinte para ver seus métodos de instância em ação:
miles = Dog("Miles", 4)
miles.description()
'Miles is 4 years old'
miles.speak("Woof Woof")
'Miles says Woof Woof'
miles.speak("Bow Wow")
'Miles says Bow Wow'
Na classe Dog acima, .description() retorna uma string contendo informações sobre a instância Dog miles. Ao escrever suas próprias classes, é uma boa ideia ter um método que retorne uma string contendo informações úteis sobre uma instância da classe. No entanto, .description() não é a maneira mais pitônica de fazer isso.
Ao criar um objeto list, você pode usar print() para exibir uma string semelhante à lista:
names = ["Miles", "Buddy", "Jack"]
print(names)
['Miles', 'Buddy', 'Jack']
Vá em frente e imprima o objeto miles para ver qual saída você obtém:
print(miles)
<__main__.Dog object at 0x000002077A633740>
Ao imprimir miles, você recebe uma mensagem de aparência enigmática informando que miles é um objeto Dog no endereço de memória 0x00aeff70. Esta mensagem não é muito útil. Você pode alterar o que é impresso definindo um método de instância especial chamado .str().
Na janela do editor, altere o nome do método .description() da classe Dog para .str():
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
# Instance method
def description(self):
return f"{self.name} is {self.age} years old"
# Another instance method
def speak(self, sound):
return f"{self.name} says {sound}"
def __str__(self):
return f"{self.name}"
Agora, ao imprimir miles, você obtém uma saída muito mais amigável:
miles = Dog("Miles", 4)
print(miles)
print(miles.description())
Miles Miles is 4 years old
Métodos como .init() e .str() são chamados de métodos mágicos porque começam e terminam com sublinhados duplos. Existem muitos métodos mágicos que você pode usar para personalizar classes em Python. Compreender os métodos mágicos é uma parte importante do domínio da programação orientada a objetos em Python, mas para sua primeira exploração do tópico, você se limitará a esses dois métodos mágicos.
Nota: Confira Quando você deve usar .repr() vs .str() em Python? Para saber mais sobre .str() e seu primo .repr().
Se quiser reforçar seu entendimento com um exercício prático, então você pode clicar no bloco abaixo e trabalhar na resolução do desafio:
Quando terminar sua própria implementação do desafio, você poderá expandir o bloco abaixo para ver uma possível solução:
Quando estiver pronto, você pode passar para a próxima seção. Lá, você verá como levar seu conhecimento um passo adiante e criar classes a partir de outras classes.
Exercicio¶
Crie uma classe Car com dois atributos de instância:¶
.color, que armazena o nome da cor do carro como uma string
.mileage, que armazena o número de milhas do carro como um número inteiro
Em seguida, crie dois objetos Car – um carro azul com trinta mil milhas e um carro vermelho com trinta mil milhas – e imprima suas cores e quilometragem. Sua saída deve ficar assim:
The blue car has 20,000 miles
The red car has 30,000 miles
Existem várias maneiras de resolver esse desafio. Para praticar efetivamente o que você aprendeu até agora, tente resolver a tarefa com as informações sobre classes em Python que você reuniu nesta seção.
Primeiro, crie uma classe Car com atributos .color e .mileage de instância e um método .str() para formatar a exibição de objetos quando você os passa para print():
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def __str__(self):
return f"The {self.color} car has {self.mileage:,} miles"
ferrari = Car('red', 1000)
print(ferrari)
The red car has 1,000 miles
Os parâmetros color e mileage são atribuídos, o que cria os dois atributos de instância.
mileage.init() self.color self.mileage
O método .str() interpola ambos os atributos da instância em uma string f e usa o :, especificador de formato para imprimir a quilometragem agrupada por milhares e separada por vírgula.
Agora você pode criar as duas instâncias Car:
blue_car = Car(color="blue", mileage=20_000)
red_car = Car(color="red", mileage=30_000)
print(blue_car)
The blue car has 20,000 miles
Você cria a instância blue_car passando o valor "blue" para o parâmetro color e 20_000 para o parâmetro mileage. Da mesma forma, você cria red_car com os valores "red" e 30_000.
Para imprimir a cor e a quilometragem de cada objeto Car, você pode fazer um loop sobre um objeto tuple que contém os dois objetos e imprimir cada objeto:
The blue car has 20,000 miles
The red car has 30,000 miles
Como você definiu sua representação de string em .str(), imprimir os objetos fornece a saída de texto desejada.
for car in (blue_car, red_car):
print(car)
The blue car has 20,000 miles The red car has 30,000 miles
Herança¶
Como você herda de outra classe em Python?¶
Herança é o processo pelo qual uma classe assume os atributos e métodos de outra. As classes recém-formadas são chamadas de classes filhas , e as classes das quais você deriva classes filhas são chamadas de classes pai .
Você herda de uma classe pai criando uma nova classe e colocando o nome da classe pai entre parênteses:
# inheritance.py
class Parent:
hair_color = "brown"
class Child(Parent):
pass
joe = Child()
joe.hair_color
'brown'
Neste exemplo mínimo, a classe filha Child herda da classe pai Parent. Como as classes filhas assumem os atributos e métodos das classes pai, Child.hair_color também é "brown" sem que você defina isso explicitamente.
As classes filhas podem substituir ou estender os atributos e métodos das classes pai. Em outras palavras, as classes filhas herdam todos os atributos e métodos dos pais, mas também podem especificar atributos e métodos que são exclusivos delas mesmas.
Embora a analogia não seja perfeita, você pode pensar na herança de objetos como uma espécie de herança genética.
Você pode ter herdado a cor do cabelo de seus pais. É um atributo com o qual você nasceu. Mas talvez você decida pintar seu cabelo de roxo. Supondo que seus pais não tenham cabelo roxo, você acabou de substituir o atributo de cor do cabelo que herdou de seus pais:
# inheritance.py
class Parent:
hair_color = "brown"
class Child(Parent):
hair_color = "purple"
ann = Child()
ann.hair_color
'purple'
Se você alterar o exemplo de código assim, Child.hair_color será "purple".
Você também herda, de certa forma, a sua língua dos seus pais. Se seus pais falam inglês, você também falará inglês. Agora imagine que você decide aprender um segundo idioma, como o alemão. Nesse caso, você estendeu seus atributos porque adicionou um atributo que seus pais não possuem:
# inheritance.py
class Parent:
speaks = ["English"]
class Child(Parent):
def __init__(self):
super().__init__()
self.speaks.append("German")
mary = Child()
mary.speaks
['English', 'German']
Você aprenderá mais sobre como o código acima funciona nas seções abaixo. Mas antes de se aprofundar na herança em Python, você fará uma caminhada até um parque para cães para entender melhor por que pode querer usar herança em seu próprio código. Exemplo: Parque para Cachorros
Finja por um momento que você está em um parque para cães. Existem muitos cães de raças diferentes no parque, todos engajados em vários comportamentos caninos.
Suponha agora que você deseja modelar o parque para cães com classes Python. A classe Dog que você escreveu na seção anterior pode distinguir cães por nome e idade, mas não por raça.
Você poderia modificar a classe Dog na janela do editor adicionando um atributo .breed:
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
Agora você pode modelar o parque para cães criando vários cães diferentes na janela interativa:
miles = Dog("Miles", 4, "Jack Russell Terrier")
buddy = Dog("Buddy", 9, "Dachshund")
jack = Dog("Jack", 3, "Bulldog")
jim = Dog("Jim", 5, "Bulldog")
Cada raça de cachorro tem comportamentos ligeiramente diferentes. Por exemplo, os buldogues têm um latido baixo, mas os bassês têm um latido mais agudo.
Usando apenas a classe Dog, você deve fornecer uma string para o argumento sound .speak() toda vez que você a chamar em uma instância Dog:
buddy.speak("Yap")
'Buddy says Yap'
jim.speak("Woof")
'Jim says Woof'
jack.speak("Woof")
'Jack says Woof'
Passar uma string para cada chamada .speak() é repetitivo e inconveniente. Além disso, o atributo .breed deve determinar a string que representa o som que cada instância Dog emite, mas aqui você deve passar manualmente a string correta .speak() sempre que chamá-la.
Você pode simplificar a experiência de trabalhar com a class Dog criando uma turma filha para cada raça de cachorro. Isso permite estender a funcionalidade que cada classe filha herda, incluindo a especificação de um argumento padrão para .speak().
Classes Pais vs Classes Filhos¶
Nesta seção, você criará uma classe filha para cada uma das três raças mencionadas acima: Jack Russell terrier, dachshund e bulldog.
Para referência, aqui está a definição completa da Dogclasse com a qual você está trabalhando atualmente:
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
Depois de fazer o exemplo do parque para cães na seção anterior, você removeu .breed novamente. Agora você escreverá código para monitorar a raça de um cachorro usando classes filhas.
Para criar uma classe filha, você cria uma nova classe com seu próprio nome e depois coloca o nome da classe pai entre parênteses. Adicione o seguinte ao arquivo dog.py para criar três novas classes filhas da classe Dog:
# dog.py
# ...
class JackRussellTerrier(Dog):
pass
class Dachshund(Dog):
pass
class Bulldog(Dog):
pass
Com as classes filhas definidas, agora você pode criar alguns cães de raças específicas na janela interativa:
miles = JackRussellTerrier("Miles", 4)
buddy = Dachshund("Buddy", 9)
jack = Bulldog("Jack", 3)
jim = Bulldog("Jim", 5)
As instâncias das classes filhas herdam todos os atributos e métodos da classe pai:
miles.species
'Canis familiaris'
buddy.name
'Buddy'
print(jack)
Jack is 3 years old
jim.speak("Woof")
'Jim says Woof'
Para determinar a qual classe um determinado objeto pertence, você pode usar o built-in type():
type(miles)
__main__.JackRussellTerrier
E se você quiser determinar se miles também é uma instância da classe Dog? Você pode fazer isso com o integrado isinstance():
isinstance(miles, Dog)
True
Observe que isinstance() leva dois argumentos, um objeto e uma classe. No exemplo acima, isinstance() verifica se miles é uma instância da classe Dog e retorna True.
Os objetos miles, buddy, jack e jim são todos instâncias dog.py, mas miles não é uma instância Bulldog e jack não é uma instância Dachshund:
isinstance(miles, Bulldog)
False
isinstance(jack, Dachshund)
False
De forma mais geral, todos os objetos criados a partir de uma classe filha são instâncias da classe pai, embora possam não ser instâncias de outras classes filhas.
Agora que você criou classes filhas para algumas raças diferentes de cães, você pode dar a cada raça seu próprio som.
Extensão de funcionalidade da classe pai
Como diferentes raças de cães têm latidos ligeiramente diferentes, você deseja fornecer um valor padrão para o soundargumento de seus respectivos .speak()métodos. Para fazer isso, você precisa substituir .speak()a definição de classe de cada raça.
Para substituir um método definido na classe pai, você define um método com o mesmo nome na classe filha. Aqui está o que parece para a classe JackRussellTerrier:
# dog.py
# ...
# class JackRussellTerrier(Dog):
# def speak(self, sound="Arf"):
# return f"{self.name} says {sound}"
# ...
# dog.py
# ...
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
class Dachshund(Dog):
pass
class Bulldog(Dog):
pass
Agora .speak() é definido na classe JackRussellTerrier com o argumento padrão para soundset to "Arf".
Atualize dog.py com a nova classe JackRussellTerrier. Agora você pode chamar .speak() de uma instância JackRussellTerrier sem passar um argumento para sound:
miles = JackRussellTerrier("Miles", 4)
miles.speak()
'Miles says Arf'
Às vezes, os cães fazem barulhos diferentes, então, se Miles ficar com raiva e rosnar, você ainda poderá ligar .speak() com um som diferente:
miles.speak("Grrr")
'Miles says Grrr'
Uma coisa a ter em mente sobre a herança de classe é que as alterações na classe pai se propagam automaticamente para as classes filhas. Isso ocorre desde que o atributo ou método que está sendo alterado não seja substituído na classe filha.
Por exemplo, na janela do editor, altere a string retornada por .speak() na lasse Dog:
# dog.py
class Dog:
# ...
def speak(self, sound):
return f"{self.name} barks: {sound}"
# ...
Agora, quando você cria uma nova instância Bulldog chamada jim, jim.speak() retorna a nova string:
jim = Bulldog("Jim", 5)
jim.speak("Woof")
'Jim says Woof'
Às vezes faz sentido substituir completamente um método de uma classe pai. Mas neste caso, você não quer que a classe JackRussellTerrier perca quaisquer alterações que você possa fazer na formatação string da Dog.speak() de saída.
Para fazer isso, você ainda precisa definir um método .speak() na classe filha JackRussellTerrier. Mas em vez de definir explicitamente a string de saída, você precisa chamar a classe Dog .speak() de dentro da classe filha .speak() usando os mesmos argumentos que você passou para JackRussellTerrier.speak().
Você pode acessar a classe pai de dentro de um método de uma classe filha usando super():
# dog.py
# ...
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound)
# ...
Quando você chama super().speak(sound) de dentro de JackRussellTerrier, o Python procura um método Dog.speak() na classe pai, e o chama com a variável sound.
Atualize dog.py com a nova classe JackRussellTerrier.
miles = JackRussellTerrier("Miles", 4)
miles.speak()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[29], line 1 ----> 1 miles = JackRussellTerrier("Miles", 4) 2 miles.speak() TypeError: JackRussellTerrier() takes no arguments
Agora, ao chamar miles.speak(), você verá a saída refletindo a nova formatação da classe Dog.
Nota: Nos exemplos acima, a hierarquia de classes é muito direta. A classe JackRussellTerrier tem uma única classe pai, Dog. Em exemplos do mundo real, a hierarquia de classes pode ficar bastante complicada.
A função super() faz muito mais do que apenas pesquisar um método ou atributo na classe pai. Ele percorre toda a hierarquia de classes em busca de um método ou atributo correspondente. Se você não tomar cuidado, super() pode ter resultados surpreendentes.
# dog.py
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
# Crie uma classe chamada GoldenRetriever que herde da classe Dog e substitua o método .speak():
# dog.py
# ...
class GoldenRetriever(Dog):
def speak(self, sound="Bark"):
return super().speak(sound)
amarok = GoldenRetriever('amarok', 3)
amarok.speak()
'amarok says Bark'
Você fornece "Bark" como valor padrão o parâmetro sound em GoldenRetriever.speak(). Então você usa super()para chamar o método .speak() da classe pai com o mesmo argumento passado sound como o método GoldenRetriever da classe ..speak()