Como os Objetos São Representados em Python

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá como os objetos Python são representados internamente e entenderá os mecanismos de atribuição e busca de atributos. Esses conceitos são fundamentais para compreender como o Python gerencia dados e comportamento dentro dos objetos.

Adicionalmente, você explorará a relação entre classes e instâncias e examinará o papel das definições de classe na programação orientada a objetos (POO). Este conhecimento aprimorará sua compreensão dos recursos orientados a objetos do Python.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 94%. Recebeu uma taxa de avaliações positivas de 93% dos estudantes.

Criando uma Classe de Ações Simples

Nesta etapa, vamos criar uma classe simples para representar uma ação. Entender como criar classes é fundamental em Python, pois nos permite modelar objetos do mundo real e seus comportamentos. Esta classe de ações simples será nosso ponto de partida para explorar como os objetos Python funcionam internamente.

Para começar, precisamos abrir um shell interativo do Python. O shell interativo do Python é um ótimo lugar para experimentar o código Python. Você pode digitar e executar comandos Python um por um. Para abri-lo, digite o seguinte comando no terminal:

python3

Depois de inserir o comando, você verá o prompt do Python (>>>). Isso indica que você está dentro do shell interativo do Python e pode começar a escrever código Python.

Agora, vamos definir uma classe SimpleStock. Uma classe em Python é como um projeto para criar objetos. Ela define os atributos (dados) e métodos (funções) que os objetos dessa classe terão. Veja como você define a classe SimpleStock com os atributos e métodos necessários:

>>> class SimpleStock:
...     def __init__(self, name, shares, price):
...         self.name = name
...         self.shares = shares
...         self.price = price
...     def cost(self):
...         return self.shares * self.price
...

No código acima, o método __init__ é um método especial nas classes Python. Ele é chamado de construtor e é usado para inicializar os atributos do objeto quando um objeto é criado. O parâmetro self se refere à instância da classe que está sendo criada. O método cost calcula o custo total das ações multiplicando o número de ações pelo preço por ação.

Depois de definir a classe, podemos criar instâncias da classe SimpleStock. Uma instância é um objeto real criado a partir do projeto da classe. Vamos criar duas instâncias para representar ações diferentes:

>>> goog = SimpleStock('GOOG', 100, 490.10)
>>> ibm = SimpleStock('IBM', 50, 91.23)

Essas instâncias representam 100 ações da Google a $490,10 por ação e 50 ações da IBM a $91,23 por ação. Cada instância tem seu próprio conjunto de valores de atributo.

Vamos verificar se nossas instâncias estão funcionando corretamente. Podemos fazer isso verificando seus atributos e calculando seu custo. Isso nos ajudará a confirmar se a classe e seus métodos estão funcionando como esperado.

>>> goog.name
'GOOG'
>>> goog.shares
100
>>> goog.price
490.1
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5

O método cost() multiplica o número de ações pelo preço por ação para calcular o custo total de posse dessas ações. Ao executar esses comandos, podemos ver que as instâncias têm os valores de atributo corretos e que o método cost está calculando o custo com precisão.

Explorando o Dicionário Interno de Objetos

Em Python, objetos são um conceito fundamental. Um objeto pode ser pensado como um contêiner que armazena dados e possui certos comportamentos. Um dos aspectos interessantes dos objetos Python é como eles armazenam seus atributos. Atributos são como variáveis que pertencem a um objeto. Python armazena esses atributos em um dicionário especial, que pode ser acessado através do atributo __dict__. Este dicionário é uma parte interna do objeto e é onde o Python acompanha todos os dados associados a esse objeto.

Vamos dar uma olhada mais de perto nessa estrutura interna usando nossas instâncias SimpleStock. Em Python, uma instância é um objeto individual criado a partir de uma classe. Por exemplo, se SimpleStock é uma classe, goog e ibm são instâncias dessa classe.

Para ver o dicionário interno dessas instâncias, podemos usar o shell interativo do Python. O shell interativo do Python é uma ótima ferramenta para testar rapidamente o código e ver os resultados. No shell interativo do Python, digite os seguintes comandos para inspecionar o atributo __dict__ de nossas instâncias:

>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1}
>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}

Quando você executa esses comandos, a saída mostra que cada instância tem seu próprio dicionário interno. Este dicionário contém todos os atributos da instância. Por exemplo, na instância goog, os atributos name, shares e price são armazenados no dicionário com seus valores correspondentes. É assim que o Python implementa os atributos do objeto nos bastidores. Cada objeto tem um dicionário privado que contém todos os seus atributos.

É importante entender o que o atributo __dict__ revela sobre a implementação interna de nossos objetos:

  1. As chaves no dicionário correspondem aos nomes dos atributos. Por exemplo, na instância goog, a chave 'name' corresponde ao atributo name do objeto.
  2. Os valores no dicionário são os valores dos atributos. Portanto, o valor 'GOOG' é o valor do atributo name para a instância goog.
  3. Cada instância tem seu próprio __dict__ separado. Isso significa que os atributos de uma instância são independentes dos atributos de outra instância. Por exemplo, o atributo shares da instância goog pode ser diferente do atributo shares da instância ibm.

Essa abordagem baseada em dicionário permite que o Python seja muito flexível com objetos. Como veremos na próxima etapa, podemos usar essa flexibilidade para modificar e acessar atributos de objetos de várias maneiras.

Adicionando e Modificando Atributos de Objetos

Em Python, os objetos são implementados com base em dicionários. Essa implementação dá ao Python um alto grau de flexibilidade ao lidar com atributos de objetos. Ao contrário de algumas outras linguagens de programação, o Python não limita os atributos de um objeto apenas àqueles definidos em sua classe. Isso significa que você pode adicionar novos atributos a um objeto a qualquer momento, mesmo depois que o objeto foi criado.

Vamos explorar essa flexibilidade adicionando um novo atributo a uma de nossas instâncias. Suponha que tenhamos uma instância chamada goog de uma classe. Vamos adicionar um atributo date a ela:

>>> goog.date = "6/11/2007"
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007'}

Aqui, adicionamos um novo atributo date à instância goog. Observe que esse atributo date não foi definido na classe SimpleStock. Esse novo atributo existe apenas na instância goog. Para confirmar isso, vamos verificar a instância ibm:

>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}
>>> hasattr(ibm, 'date')
False

Como podemos ver, a instância ibm não possui o atributo date. Isso mostra três características importantes dos objetos Python:

  1. Cada instância tem seu próprio conjunto exclusivo de atributos.
  2. Os atributos podem ser adicionados a um objeto após sua criação.
  3. Adicionar um atributo a uma instância não afeta outras instâncias.

Agora, vamos tentar uma maneira diferente de adicionar um atributo. Em vez de usar a notação de ponto, vamos manipular diretamente o dicionário subjacente do objeto. Em Python, cada objeto tem um atributo especial __dict__ que armazena todos os seus atributos como pares chave-valor.

>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007', 'time': '9:45am'}

Ao modificar diretamente o dicionário __dict__, adicionamos um novo atributo time à instância goog. Quando acessamos goog.time, o Python procura a chave 'time' no dicionário __dict__ e retorna seu valor correspondente.

Esses exemplos ilustram que os objetos Python são essencialmente dicionários com alguns recursos extras. A flexibilidade dos objetos Python permite a modificação dinâmica, o que é muito poderoso e conveniente na programação.

Compreendendo a Relação entre Classes e Instâncias

Agora, vamos explorar como classes e instâncias se relacionam em Python e como a busca por métodos funciona. Este é um conceito importante porque ajuda você a entender como o Python encontra e usa métodos e atributos quando você trabalha com objetos.

Primeiro, vamos verificar a qual classe nossas instâncias pertencem. Conhecer a classe de uma instância é crucial, pois nos diz onde o Python procurará métodos e atributos relacionados a essa instância.

>>> goog.__class__
<class '__main__.SimpleStock'>
>>> ibm.__class__
<class '__main__.SimpleStock'>

Ambas as instâncias têm uma referência de volta à classe SimpleStock. Essa referência é como um ponteiro que o Python usa quando precisa procurar métodos. Quando você chama um método em uma instância, o Python usa essa referência para encontrar a definição de método apropriada.

Quando você chama um método em uma instância, o Python segue estas etapas:

  1. Ele procura o atributo no __dict__ da instância. O __dict__ de uma instância é como uma área de armazenamento onde todos os atributos específicos da instância são mantidos.
  2. Se não for encontrado, ele verifica o __dict__ da classe. O __dict__ da classe armazena todos os atributos e métodos que são comuns a todas as instâncias dessa classe.
  3. Se encontrado na classe, ele retorna esse atributo.

Vamos ver isso em ação. Primeiro, verifique se o método cost não está nos dicionários da instância. Essa etapa nos ajuda a entender que o método cost não é específico para cada instância, mas é definido no nível da classe.

>>> 'cost' in goog.__dict__
False
>>> 'cost' in ibm.__dict__
False

Então, de onde vem o método cost? Vamos verificar a classe. Ao olhar para o __dict__ da classe, podemos descobrir onde o método cost é definido.

>>> SimpleStock.__dict__['cost']
<function SimpleStock.cost at 0x7f...>

O método é definido na classe, não nas instâncias. Quando você chama goog.cost(), o Python não encontra cost em goog.__dict__, então ele procura em SimpleStock.__dict__ e o encontra lá.

Você pode realmente chamar o método diretamente do dicionário da classe, passando a instância como o primeiro argumento (que se torna self). Isso mostra como o Python internamente chama métodos quando você usa a sintaxe normal instance.method().

>>> SimpleStock.__dict__['cost'](goog)
49010.0
>>> SimpleStock.__dict__['cost'](ibm)
4561.5

Isso é essencialmente o que o Python faz nos bastidores quando você chama goog.cost().

Agora, vamos adicionar um atributo de classe. Atributos de classe são compartilhados por todas as instâncias. Isso significa que todas as instâncias da classe podem acessar esse atributo, e ele é armazenado apenas uma vez no nível da classe.

>>> SimpleStock.exchange = 'NASDAQ'
>>> goog.exchange
'NASDAQ'
>>> ibm.exchange
'NASDAQ'

Ambas as instâncias podem acessar o atributo exchange, mas ele não é armazenado em seus dicionários individuais. Vamos verificar isso verificando os dicionários de instância e classe.

>>> 'exchange' in goog.__dict__
False
>>> 'exchange' in SimpleStock.__dict__
True
>>> SimpleStock.__dict__['exchange']
'NASDAQ'

Isso demonstra que:

  1. Os atributos de classe são compartilhados por todas as instâncias. Todas as instâncias podem acessar o mesmo atributo de classe sem ter sua própria cópia.
  2. O Python verifica o dicionário da instância primeiro, depois o dicionário da classe. Essa é a ordem em que o Python procura atributos quando você tenta acessá-los em uma instância.
  3. As classes atuam como um repositório para dados e comportamentos compartilhados (métodos). A classe armazena todos os atributos e métodos comuns que podem ser usados por todas as suas instâncias.

Se modificarmos um atributo de instância com o mesmo nome, ele sombreia o atributo de classe. Isso significa que, quando você acessa o atributo nessa instância, o Python usará o valor específico da instância em vez do valor no nível da classe.

>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange  ## Ainda usando o atributo da classe
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'

Agora, ibm tem seu próprio atributo exchange que sombreia o atributo da classe, enquanto goog ainda usa o atributo da classe.

Resumo

Neste laboratório, você aprendeu sobre o funcionamento interno do sistema de objetos do Python e vários conceitos-chave. Primeiro, os objetos Python armazenam atributos em um dicionário acessível através do atributo __dict__, oferecendo flexibilidade. Segundo, você compreendeu como a atribuição e a busca de atributos funcionam, incluindo a adição dinâmica de atributos e a ordem de verificação de atributos.

Além disso, você explorou a relação entre classes e instâncias, onde as classes contêm dados e comportamentos compartilhados, e as instâncias mantêm seu próprio estado. Você também descobriu como as chamadas de método operam, com métodos na classe agindo nas instâncias através do parâmetro self. Compreender esses conceitos aprofunda sua compreensão do modelo OOP do Python e é útil para depuração, design de hierarquias de classes e aprendizado de recursos avançados.