<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 [Py-OO] Aula 01
Introdução a Orientação a Objetos em Python
O que você vai aprender nesta aula?
Após o término da aula você terá aprendido
Step1:  O dicionários possui diversos métodos que usamos para alterar os objetos
Step2:  Podemos usar a função dir() para inspecionar os métodos e atributos do dict notas
Step3:  Aqui vemos vários métodos que o nome contém underscores no começo e fim como __len__, __getitem__, __setitem__. Esses métodos são chamados de métodos especiais que fazem parte do modelo de dados do Python. Esses métodos são chamados pelo interpretador quando uma sintaxe especial é acionada. Como, por exemplo, quando acessamos os itens do dicionário por sua chave o interpretador invoca a função dict.__getitem__()
Step4:  O dict também possui atributos de dados especiais como __class__, que armazena o nome da classe do objeto, e __doc__ que retém a docstring do objeto
Step5:  Para ver a docstring formatada para saída use a função print()
Step6:  Números são objetos
Step7:  Possuem métodos e atributos
Step8:  Só lembrando que os métodos especiais não devem ser chamados diretamente, os exemplos anteriores só servem para ilustrar o funcionamento e existência desses métodos. Caso você queira consultar a documentação de um objeto use a função help()
Step10:  Como explicado na [py-intro] Aula 05 funções também são objetos. Na terminologia utilizada pelos livros isso quer dizer que, em Python, as funções são objetos de primeira classe ou cidadãos de primeira classe.
Step11:  Podemos a atribuir funções a variáveis
Step12:  Acessar atributos
Step13:  Podemos ver o bytecode que a função executa usando o módudlo dis (disassembly), enviando a função soma() como argumento da função dis.dis()
Step14:  Tipagem dos objetos
Tipagem forte
Os objetos em Python possuem tipagem forte, isso quer dizer que dificilmente são feitas conversões de tipos implícitas na realização de operações. Vamos ver alguns exemplos que ilustram esse conceito
Step15:  Tentamos concatenar o número 10 à string "1", porém uma exceção do tipo TypeError foi levantada dizendo que não foi possível converter um objeto int para str de forma implicita.
Em Javascript e PHP, linguagens que possuem tipagem fraca, não seria levantado uma exceção e o interpretador faria a conversão de um dos tipos. No Javascript (1.5) o resultado seria uma string "110" e no PHP (5.6) o número 11.
Aqui percebemos que a operação "1" + 10 pode produzir dois resultados
Step16:  Tipagem dinâmica
Dizemos que uma linguagem possui tipagem dinâmica quando não é necessário especificar explicitamente os tipos das váriaveis. Os objetos possuem tipos, porém as variáveis podem referenciar objetos de quaisquer tipos. Verificações de tipos são feitas em tempo de execução e não durante a compilação.
Quando definimos uma função dobra(x) que retorna o valor recebido multiplicado por 2 podemos receber qualquer tipo de objeto como argumento
Step17:  Podemos dobrar int
Step18:  Dobrar float
Step19:  strings
Step20:  sequências
Step21:  Tipos que não suportam multiplicação por inteiros levantarão exceção quando executados
Step22:  A função type() nos permite verificar os tipos dos objetos
Step23:  Mutabilidade
No Python existem objetos mutáveis e imutáveis, já vimos vários exemplos disso ao longo do curso. O estado (atributo) de objetos mutáveis podem ser alterados, já obetos imutáveis não podem ser alterados de forma alguma.
A tabela abaixo mostra a mutabilidade dos tipos embutidos do Python
Step24:  Todo objeto python possui uma identidade, um número único que diferencia esse objeto. Podemos acessar a identidade de um objeto usando a função id()
Step25:  Isso quer dizer que a identidade do objeto a é 10894368.
Agora vamos tentar mudar o valor de a
Step26:  A identidade mudou, isso significa que a variável a está referenciando outro objeto que foi criado quando executamos a += b.
Vamos ver agora um exemplo de objeto mutável
Step27:  Vamos verificar a identidade dessa lista
Step28:  Mesmo modificando a lista através da inserção e remoção de valores sua identidade continua a mesma.
Strings também são imutáveis
Step29:  Como vimos na aula dois do módulo de introdução strings são imutáveis e para alterar seu valor precisamos usar slicing
Step30:  Comparando a identidade de s antes e depois da mudança vemos que trata-se de objetos diferentes.
Variáveis
Variáveis são apenas referências para objetos, assim como acontece em Java. Variáveis são apenas rótulos (ou post-its) associados a objetos. Diferentemente de C ou Pascal as variáveis em Python não são caixas que armazenam objetos ou valores.
Por exemplo
Step31:  As variáveis a e b armazenam referências à mesma lista em vez de cópias.
É importante notar que objetos são criados antes da atribuição. A operação do lado direito de uma atribuição ocorre antes que a atribuição
Step32:  Como não foi possível criar o número - por representar uma operação inválida (divisão por zero) para a linguagem - a variável c não foi atribuída a nenhum objeto
Step33:  Como as variáveis são apenas rótulos a forma correta de falar sobre atribuição é "a variável x foi atribuída à (instância) lâmpada" e não "a lâmpada foi atribuída à variável x". Pois é como se colocássemos um "post-it" x em um objeto, e não guardássemos esse objeto em uma caixa x.
Por serem rótulos podemos atribuir diversos rótulos a um mesmo objeto. Isso faz com que apelidos (aliases) sejam criados
Step34:  Vamos supor que exista um impostor - o João - que possua as mesmas credenciais que o José Silva. Suas credenciais são as mesmas, porém João não é José
Step35:  O valor de seus dados (ou credenciais) são iguais, porém eles não são os mesmos
Step36:  Nesse exemplo vimos o apelidamento (aliasing). josé e zé são apelidos (aliases)
Step37:  Classes
Vamos ver como é a sintaxe de definição de classes no Python, para isso vamos fazer alguns exemplos.
Começaremos por criar uma classe que representa um cão. Armazenaremos
Step38:  Na primeira linha definimos uma classe de nome Cão.
Da segunda até a quarta linha definimos os atributos de classe qtd_patas, carnívoro, nervoso. Os atributos de classe representam dados que aparecem em todas as classes.
Na sexta linha definimos o inicializador (também pode ser chamado de construtor) que deve receber o nome do Cão.
Na última linha criamos o atributo da instância nome e associamos à string enviada para o construtor.
Vamos agora criar uma instância de Cão
Step39:  Vamos verificar seus atributos
Step40:  Podemos também alterar esses atributos
Step41:  Mudamos apenas o atributo nervoso da instância rex. O valor de Cão.nervoso continua o mesmo
Step42:  Também podemos criar atributos dinamicamente para nossa instância rex
Step43:  Lembrando mais uma vez que essas mudanças ocorrem somente na instância e não na classe
Step44:  Classes também são objetos e podemos acessar seus atributos
Step45:  Não podemos acessar o nome, pois nome é um atributo que é associado somente a instâncias da classe.
Step47:  Os atributos de classe são usados para fornecerer valores padrão para dados que são compartilhados por todos os "cães" como, por exemplo, a quantidade de patas.
Agora vamos criar métodos (funções associadas a classes) para a classe Cão
Step49:  Vamos brincar um pouco mais com o Cão e implementar ainda  mais métodos
Step50:  Métodos de instância, classe e estático
Por padrão os metódos de uma classe são métodos de instância (ou instance methods), isso significa que os métodos recebem, obrigatoriamente, uma instância da classe.
Como por exemplo
Step51:  Não podemos chamar o método de instância somente com a classe
Step52:  Precisamos criar uma instância para utilizá-lo
Step53:  Já os métodos de classe (ou class methods) são métodos referentes à classe como um todo e recebem - não uma instância mas o - objeto da classe. 
Para tornar o método de uma classe um classmethod usamos o decorador @classmethod. Decoradores são usados para "decorar" (ou marcar) funções e modificar seu comportamento de alguma maneira. Na Aula 05 deste módulo (de orientação a objetos em python) falaremos mais sobre decoradores.
Métodos de classe são definidos e utilizados assim
Step54:  Podemos chamar o método usando o objeto de classe ExemploClasse
Step55:  Também podemos chamar o método a partir de uma instância dessa classe. Por ser um classmethod o método continuará a receber como argumento o objeto da classe e não a instância
Step56:  Por fim também temos os métodos estáticos que funcionam como funções simples agregadas a objetos ou classes. Eles não recebem argumentos de forma automática
Step57:  Também podemos chamar o método estático a partir de uma instância
<ASSISTANT_TASK:>
Python Code:
notas = {'bia': 10, 'pedro': 0, 'ana': 7}
notas
Explanation: [Py-OO] Aula 01
Introdução a Orientação a Objetos em Python
O que você vai aprender nesta aula?
Após o término da aula você terá aprendido:
Objetos em Python
Como funcionam
Tipagem
Mutabilidade
Como funciona atribuição e variáveis
Classes
Sintaxe básica, criando instâncias, métodos de instância, de classe e estáticos
Revisão dos conceitos de Orientação a Objetos
Vamos começar com uma rápida revisão dos conceitos de Orientação a Objetos e como eles são aplicados em Python.
O paradigma de programação orientada a objetos tem por objetivo fornecer uma abstração do mundo real e aplicá-la para a programação.
Objetos são componentes de software que incluem dados e comportamentos. Por exemplo cães possuem estados (nome, raça e cor) e comportamentos (latir, abanar o rabo e pegar objetos). Já bicicletas possuem outros estados (modelo, marcha atual e velocidade atual) e comportamentos (mudar marcha, frear).
Outro conceito importante é o de classe. Estas representam a estrutura de um objeto, por exemplo: a receita de um bolo. Nesse exemplo a receita seria a classe que contém instruções de como criar o objeto, além de ter informações sobre a instância (bolo).
Em Python os objetos possuem atributos que podem ser tanto métodos (funções vinculadas ao objeto) ou atributos de dados do objeto. Este último geralmente é geralmente chamado de atributo.
É importante saber que em Python instâncias de classes são chamadas exatamente de instâncias. É comum em Java/C++ chamar "instâncias de classes" de "objetos de classes". Isso não acontece em Python, pois nesta linguagem tudo é um objeto e, portanto, chamar a instância de uma classe de objeto é redundante.
Quando falamos sobre linguagens que implementam o paradigma de orientação a objetos elas devem fornecer quatro conceitos básicos:
- Abstração: habilidade de modelar características do mundo real (?)
- Encapsulamento: permitir a proteção de dados e que operações internas tenham acesso a esses dados.
- Herança: mecanismo que possibilitam a criação de novos objetos por meio da alteração de algo já exisitente; e vincular o objeto criado com o antigo.
- Polimorfismo: capacidade de uma unidade de ter várias formas.
Objetos em Python
Como dito anteriormente tudo em Python é objeto. Vamos começar analisando um objeto dict:
End of explanation
notas.keys()
notas.pop('bia')
notas
Explanation: O dicionários possui diversos métodos que usamos para alterar os objetos:
End of explanation
dir(notas)
Explanation: Podemos usar a função dir() para inspecionar os métodos e atributos do dict notas:
End of explanation
notas
notas.__getitem__('ana')
notas['ana']
notas.__getitem__('joselito')
notas['joselito']
Explanation: Aqui vemos vários métodos que o nome contém underscores no começo e fim como __len__, __getitem__, __setitem__. Esses métodos são chamados de métodos especiais que fazem parte do modelo de dados do Python. Esses métodos são chamados pelo interpretador quando uma sintaxe especial é acionada. Como, por exemplo, quando acessamos os itens do dicionário por sua chave o interpretador invoca a função dict.__getitem__():
End of explanation
notas.__class__
notas.__doc__
Explanation: O dict também possui atributos de dados especiais como __class__, que armazena o nome da classe do objeto, e __doc__ que retém a docstring do objeto:
End of explanation
print(notas.__doc__)
Explanation: Para ver a docstring formatada para saída use a função print():
End of explanation
3 + 4
Explanation: Números são objetos:
End of explanation
print(3 .__doc__)
3 .__add__(4)
3 .__sub__(4)
Explanation: Possuem métodos e atributos:
End of explanation
help(3)
Explanation: Só lembrando que os métodos especiais não devem ser chamados diretamente, os exemplos anteriores só servem para ilustrar o funcionamento e existência desses métodos. Caso você queira consultar a documentação de um objeto use a função help():
End of explanation
def soma(a, b):
     retorna a + b 
    soma = a + b
    return soma
soma(1, 2)
soma
Explanation: Como explicado na [py-intro] Aula 05 funções também são objetos. Na terminologia utilizada pelos livros isso quer dizer que, em Python, as funções são objetos de primeira classe ou cidadãos de primeira classe.
End of explanation
adição = soma
adição
Explanation: Podemos a atribuir funções a variáveis:
End of explanation
adição.__name__
adição.__doc__
Explanation: Acessar atributos:
End of explanation
import dis
dis.dis(soma)
Explanation: Podemos ver o bytecode que a função executa usando o módudlo dis (disassembly), enviando a função soma() como argumento da função dis.dis():
End of explanation
"1" + 10
Explanation: Tipagem dos objetos
Tipagem forte
Os objetos em Python possuem tipagem forte, isso quer dizer que dificilmente são feitas conversões de tipos implícitas na realização de operações. Vamos ver alguns exemplos que ilustram esse conceito:
End of explanation
"1" + str(10)
int("1") + 10
Explanation: Tentamos concatenar o número 10 à string "1", porém uma exceção do tipo TypeError foi levantada dizendo que não foi possível converter um objeto int para str de forma implicita.
Em Javascript e PHP, linguagens que possuem tipagem fraca, não seria levantado uma exceção e o interpretador faria a conversão de um dos tipos. No Javascript (1.5) o resultado seria uma string "110" e no PHP (5.6) o número 11.
Aqui percebemos que a operação "1" + 10 pode produzir dois resultados: uma string ou um número. Conforme consta no Zen do Python: "In the face of ambiguity, refuse the temptation to guess" (Ao encontrar uma ambiguidade recuse a tentação de inferir) e é exatamente o que o Python faz: a linguagem recusa-se a inferir o tipo do resultado e levanta uma exceção.
Para fazermos esse exemplo funcionar precisamos converter os tipos explicitamente:
End of explanation
def dobra(x):
    return x * 2
Explanation: Tipagem dinâmica
Dizemos que uma linguagem possui tipagem dinâmica quando não é necessário especificar explicitamente os tipos das váriaveis. Os objetos possuem tipos, porém as variáveis podem referenciar objetos de quaisquer tipos. Verificações de tipos são feitas em tempo de execução e não durante a compilação.
Quando definimos uma função dobra(x) que retorna o valor recebido multiplicado por 2 podemos receber qualquer tipo de objeto como argumento:
End of explanation
dobra(2)
Explanation: Podemos dobrar int:
End of explanation
dobra(1.15)
Explanation: Dobrar float:
End of explanation
dobra('bo')
Explanation: strings:
End of explanation
dobra([1, 2, 3])
dobra((4, 5, 6))
Explanation: sequências:
End of explanation
dobra(None)
Explanation: Tipos que não suportam multiplicação por inteiros levantarão exceção quando executados:
End of explanation
type(1)
type([1, 2, 3])
type((1, 2, 3))
type({})
type('lalala')
type(False)
Explanation: A função type() nos permite verificar os tipos dos objetos:
End of explanation
a = 10
a
Explanation: Mutabilidade
No Python existem objetos mutáveis e imutáveis, já vimos vários exemplos disso ao longo do curso. O estado (atributo) de objetos mutáveis podem ser alterados, já obetos imutáveis não podem ser alterados de forma alguma.
A tabela abaixo mostra a mutabilidade dos tipos embutidos do Python:
<table>
<thead>
    <th>Imutáveis</th>
    <th>Mutáveis</th>
</thead>
<tbody>
    <tr>
        <td>tuple</td>
        <td>list</td>
    </tr>
        <tr>
        <td>números (int, float, complex)</td>
        <td>dict</td>
    </tr>
        <tr>
        <td>frozenset</td>
        <td>set</td>
    </tr>
        <tr>
        <td>str, bytes</td>
        <td>objetos que permitem alteração de atributos por acesso direto, setters ou métodos</td>
    </tr>
</tbody>
</table>
Vamos ver alguns exemplos que demonstram isso:
End of explanation
id(a)
Explanation: Todo objeto python possui uma identidade, um número único que diferencia esse objeto. Podemos acessar a identidade de um objeto usando a função id():
End of explanation
b = 3
b
a += b
a
id(a)
Explanation: Isso quer dizer que a identidade do objeto a é 10894368.
Agora vamos tentar mudar o valor de a:
End of explanation
lista = [1, 2, 3, 4]
lista
Explanation: A identidade mudou, isso significa que a variável a está referenciando outro objeto que foi criado quando executamos a += b.
Vamos ver agora um exemplo de objeto mutável:
End of explanation
id(lista)
lista.append(10)
lista.remove(2)
lista += [-4, -3]
lista
id(lista)
Explanation: Vamos verificar a identidade dessa lista:
End of explanation
s = 'abcd'
id(s)
s[0] = 'z'
Explanation: Mesmo modificando a lista através da inserção e remoção de valores sua identidade continua a mesma.
Strings também são imutáveis:
End of explanation
s = 'z' + s[1:]
s
id(s)
Explanation: Como vimos na aula dois do módulo de introdução strings são imutáveis e para alterar seu valor precisamos usar slicing:
End of explanation
a = [1, 2, 3]
a
b = a
a.append(4)
b
Explanation: Comparando a identidade de s antes e depois da mudança vemos que trata-se de objetos diferentes.
Variáveis
Variáveis são apenas referências para objetos, assim como acontece em Java. Variáveis são apenas rótulos (ou post-its) associados a objetos. Diferentemente de C ou Pascal as variáveis em Python não são caixas que armazenam objetos ou valores.
Por exemplo:
End of explanation
c = 1 / 0
Explanation: As variáveis a e b armazenam referências à mesma lista em vez de cópias.
É importante notar que objetos são criados antes da atribuição. A operação do lado direito de uma atribuição ocorre antes que a atribuição:
End of explanation
c
Explanation: Como não foi possível criar o número - por representar uma operação inválida (divisão por zero) para a linguagem - a variável c não foi atribuída a nenhum objeto:
End of explanation
josé = {'nome': 'José Silva', 'idade': 10}
zé = josé
zé is josé
id(zé), id(josé)
zé['ano_nascimento'] = 2006
josé
Explanation: Como as variáveis são apenas rótulos a forma correta de falar sobre atribuição é "a variável x foi atribuída à (instância) lâmpada" e não "a lâmpada foi atribuída à variável x". Pois é como se colocássemos um "post-it" x em um objeto, e não guardássemos esse objeto em uma caixa x.
Por serem rótulos podemos atribuir diversos rótulos a um mesmo objeto. Isso faz com que apelidos (aliases) sejam criados:
End of explanation
joão = {'nome': 'José Silva', 'idade': 10, 'ano_nascimento': 2006}
joão == josé
Explanation: Vamos supor que exista um impostor - o João - que possua as mesmas credenciais que o José Silva. Suas credenciais são as mesmas, porém João não é José:
End of explanation
joão is josé
Explanation: O valor de seus dados (ou credenciais) são iguais, porém eles não são os mesmos:
End of explanation
a = 10
a is None
b = None
b is None
Explanation: Nesse exemplo vimos o apelidamento (aliasing). josé e zé são apelidos (aliases): duas variáveis associadas ao mesmo objeto. Por outro lado vimos que joão não é um apelido de josé: essas variáveis estão associadas a objetos distintos. O que acontece é que joão e josé possuem o mesmo valor - que é isso que == compara - mas têm identidades diferentes.
O operador == realiza a comparação dos valores de objetos (os dados armazenados por eles), enquanto is compara suas identidades. É mais comum comparar valores do que identidades, por esse motivo == aparece com mais frequência que is em códigos Python. Um caso em que o is é bastante utilizada é para comparação com None:
End of explanation
class Cão:
    qtd_patas = 4
    carnívoro = True
    nervoso = False
    
    def __init__(self, nome):
        self.nome = nome
Explanation: Classes
Vamos ver como é a sintaxe de definição de classes no Python, para isso vamos fazer alguns exemplos.
Começaremos por criar uma classe que representa um cão. Armazenaremos: o nome, a quantidade de patas, se o cão é carnívoro e se ele está nervoso.
End of explanation
rex = Cão('Rex')
type(rex)
Explanation: Na primeira linha definimos uma classe de nome Cão.
Da segunda até a quarta linha definimos os atributos de classe qtd_patas, carnívoro, nervoso. Os atributos de classe representam dados que aparecem em todas as classes.
Na sexta linha definimos o inicializador (também pode ser chamado de construtor) que deve receber o nome do Cão.
Na última linha criamos o atributo da instância nome e associamos à string enviada para o construtor.
Vamos agora criar uma instância de Cão:
End of explanation
rex.qtd_patas
rex.carnívoro
rex.nervoso
rex.nome
Explanation: Vamos verificar seus atributos:
End of explanation
rex.nervoso = True
rex.nervoso
Explanation: Podemos também alterar esses atributos:
End of explanation
Cão.nervoso
Explanation: Mudamos apenas o atributo nervoso da instância rex. O valor de Cão.nervoso continua o mesmo:
End of explanation
rex.sujo = True
rex.sujo
rex.idade = 5
rex.idade
Explanation: Também podemos criar atributos dinamicamente para nossa instância rex:
End of explanation
Cão.sujo
Cão.idade
Explanation: Lembrando mais uma vez que essas mudanças ocorrem somente na instância e não na classe:
End of explanation
Cão.__name__
Cão.qtd_patas
Cão.nervoso
Cão.carnívoro
Cão.nome
Explanation: Classes também são objetos e podemos acessar seus atributos:
End of explanation
fido = Cão('Fido')
fido.nome
Explanation: Não podemos acessar o nome, pois nome é um atributo que é associado somente a instâncias da classe.
End of explanation
class Cão:
    qtd_patas = 4
    carnívoro = True
    nervoso = False
    
    def __init__(self, nome):
        self.nome = nome
        
    def latir(self, vezes=1):
         Latir do cão. Quanto mais nervoso mais late. 
        
        vezes += self.nervoso * vezes
        latido = 'Au! ' * vezes
        print('{}: {}'.format(self.nome, latido))
rex = Cão('Rex')
rex.latir()
rex.nervoso = True
rex.latir()
rex.latir(10)
Explanation: Os atributos de classe são usados para fornecerer valores padrão para dados que são compartilhados por todos os "cães" como, por exemplo, a quantidade de patas.
Agora vamos criar métodos (funções associadas a classes) para a classe Cão:
End of explanation
class Cão:
    qtd_patas = 4
    carnívoro = True
    nervoso = False
    
    def __init__(self, nome, truques=None):
        self.nome = nome
        if not truques:
            self.truques = []
        else:
            self.truques = list(truques)
        
    def latir(self, vezes=1):
         Latir do cão. Quanto mais nervoso mais late. 
        
        vezes += self.nervoso * vezes
        latido = 'Au! ' * vezes
        print('{}: {}'.format(self.nome, latido))
    
    def ensina_truque(self, truque):
        if truque not in self.truques:
            self.truques.append(truque)
fido = Cão('Fido', truques=['Pegar'])
fido.truques
fido.ensina_truque('Rolar')
fido.truques
fido.ensina_truque('Pegar')
fido.truques
Explanation: Vamos brincar um pouco mais com o Cão e implementar ainda  mais métodos:
End of explanation
class ExemploInstancia:
    def metodo_instancia(self):
        print('Recebi {}'.format(self))
Explanation: Métodos de instância, classe e estático
Por padrão os metódos de uma classe são métodos de instância (ou instance methods), isso significa que os métodos recebem, obrigatoriamente, uma instância da classe.
Como por exemplo:
End of explanation
ExemploInstancia.metodo_instancia()
Explanation: Não podemos chamar o método de instância somente com a classe:
End of explanation
inst = ExemploInstancia()
inst.metodo_instancia()
Explanation: Precisamos criar uma instância para utilizá-lo:
End of explanation
class ExemploClasse:
    @classmethod
    def metodo_classe(cls):
        print("Recebi {}".format(cls))
Explanation: Já os métodos de classe (ou class methods) são métodos referentes à classe como um todo e recebem - não uma instância mas o - objeto da classe. 
Para tornar o método de uma classe um classmethod usamos o decorador @classmethod. Decoradores são usados para "decorar" (ou marcar) funções e modificar seu comportamento de alguma maneira. Na Aula 05 deste módulo (de orientação a objetos em python) falaremos mais sobre decoradores.
Métodos de classe são definidos e utilizados assim:
End of explanation
ExemploClasse.metodo_classe()
Explanation: Podemos chamar o método usando o objeto de classe ExemploClasse:
End of explanation
inst = ExemploClasse()
inst.metodo_classe()
Explanation: Também podemos chamar o método a partir de uma instância dessa classe. Por ser um classmethod o método continuará a receber como argumento o objeto da classe e não a instância:
End of explanation
class Exemplo:
    @staticmethod
    def metodo_estático():
        print('Sou estátio e não recebo nada')
Exemplo.metodo_estático()
Explanation: Por fim também temos os métodos estáticos que funcionam como funções simples agregadas a objetos ou classes. Eles não recebem argumentos de forma automática:
End of explanation
inst = Exemplo()
inst.metodo_estático()
Explanation: Também podemos chamar o método estático a partir de uma instância:
End of explanation
<END_TASK>