Internos do Modelo de Objetos Python

Beginner

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

Introdução

Esta seção introduz mais detalhes sobre o modelo de objeto interno do Python e discute algumas questões relacionadas ao gerenciamento de memória, cópia e verificação de tipos.

Atribuição

Muitas operações em Python estão relacionadas à atribuição ou armazenamento de valores.

a = value         ## Assignment to a variable
s[n] = value      ## Assignment to a list
s.append(value)   ## Appending to a list
d['key'] = value  ## Adding to a dictionary

Uma advertência: operações de atribuição nunca fazem uma cópia do valor que está sendo atribuído. Todas as atribuições são meramente cópias de referência (ou cópias de ponteiro, se preferir).

Exemplo de atribuição

Considere este fragmento de código.

a = [1,2,3]
b = a
c = [a,b]

Uma imagem das operações de memória subjacentes. Neste exemplo, existe apenas um objeto lista [1,2,3], mas existem quatro referências diferentes a ele.

Memory reference diagram example

Isso significa que modificar um valor afeta todas as referências.

>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>

Observe como uma alteração na lista original aparece em todos os outros lugares (ai!). Isso ocorre porque nenhuma cópia foi feita. Tudo está apontando para a mesma coisa.

Reatribuição de valores

Reatribuir um valor nunca sobrescreve a memória usada pelo valor anterior.

a = [1,2,3]
b = a
a = [4,5,6]

print(a)      ## [4, 5, 6]
print(b)      ## [1, 2, 3]    Holds the original value

Lembre-se: Variáveis são nomes, não localizações de memória.

Alguns perigos

Se você não souber sobre esse compartilhamento, em algum momento você vai se prejudicar. Cenário típico. Você modifica alguns dados pensando que é sua própria cópia privada e, acidentalmente, corrompe alguns dados em alguma outra parte do programa.

Comentário: Esta é uma das razões pelas quais os tipos de dados primitivos (int, float, string) são imutáveis (somente leitura).

Identidade e Referências

Use o operador is para verificar se dois valores são exatamente o mesmo objeto.

>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>>

is compara a identidade do objeto (um inteiro). A identidade pode ser obtida usando id().

>>> id(a)
3588944
>>> id(b)
3588944
>>>

Observação: É quase sempre melhor usar == para verificar objetos. O comportamento de is é frequentemente inesperado:

>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>

Cópias rasas (Shallow copies)

Listas e dicionários têm métodos para copiar.

>>> a = [2,3,[100,101],4]
>>> b = list(a) ## Make a copy
>>> a is b
False

É uma nova lista, mas os itens da lista são compartilhados.

>>> a[2].append(102)
>>> b[2]
[100,101,102]
>>>
>>> a[2] is b[2]
True
>>>

Por exemplo, a lista interna [100, 101, 102] está sendo compartilhada. Isso é conhecido como uma cópia rasa (shallow copy). Aqui está uma imagem.

Shallow copy

Cópias profundas (Deep copies)

Às vezes, você precisa fazer uma cópia de um objeto e de todos os objetos contidos nele. Você pode usar o módulo copy para isso:

>>> a = [2,3,[100,101],4]
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[2].append(102)
>>> b[2]
[100,101]
>>> a[2] is b[2]
False
>>>

Nomes, Valores, Tipos

Nomes de variáveis não têm um tipo. É apenas um nome. No entanto, os valores têm um tipo subjacente.

>>> a = 42
>>> b = 'Hello World'
>>> type(a)
<type 'int'>
>>> type(b)
<type 'str'>

type() irá dizer o que é. O nome do tipo é geralmente usado como uma função que cria ou converte um valor para esse tipo.

Verificação de Tipos (Type Checking)

Como verificar se um objeto é de um tipo específico.

if isinstance(a, list):
    print('a is a list')

Verificando um entre muitos tipos possíveis.

if isinstance(a, (list,tuple)):
    print('a is a list or tuple')

*Cuidado: Não exagere na verificação de tipos. Isso pode levar a uma complexidade excessiva no código. Geralmente, você só faria isso se isso impedisse erros comuns cometidos por outras pessoas que usam seu código.

Tudo é um objeto

Números, strings, listas, funções, exceções, classes, instâncias, etc. são todos objetos. Isso significa que todos os objetos que podem ser nomeados podem ser passados como dados, colocados em contêineres, etc., sem quaisquer restrições. Não existem tipos especiais de objetos. Às vezes, diz-se que todos os objetos são "first-class" (de primeira classe).

Um exemplo simples:

>>> import math
>>> items = [abs, math, ValueError ]
>>> items
[<built-in function abs>,
  <module 'math' (builtin)>,
  <type 'exceptions.ValueError'>]
>>> items[0](-45)
45
>>> items[1].sqrt(2)
1.4142135623730951
>>> try:
        x = int('not a number')
    except items[2]:
        print('Failed!')
Failed!
>>>

Aqui, items é uma lista contendo uma função, um módulo e uma exceção. Você pode usar diretamente os itens na lista no lugar dos nomes originais:

items[0](-45)       ## abs
items[1].sqrt(2)    ## math
except items[2]:    ## ValueError

Com grande poder vem grande responsabilidade. Só porque você pode fazer isso não significa que você deva.

Neste conjunto de exercícios, analisamos um pouco do poder que vem dos objetos de primeira classe.

Exercício 2.24: Dados de Primeira Classe (First-class Data)

No arquivo portfolio.csv, lemos dados organizados como colunas que se parecem com isto:

name,shares,price
"AA",100,32.20
"IBM",50,91.10
...

No código anterior, usamos o módulo csv para ler o arquivo, mas ainda tivemos que realizar conversões manuais de tipo. Por exemplo:

for row in rows:
    name   = row[0]
    shares = int(row[1])
    price  = float(row[2])

Este tipo de conversão também pode ser realizado de uma maneira mais inteligente usando algumas operações básicas de lista.

Crie uma lista Python que contenha os nomes das funções de conversão que você usaria para converter cada coluna no tipo apropriado:

>>> types = [str, int, float]
>>>

A razão pela qual você pode até criar esta lista é que tudo em Python é first-class (de primeira classe). Então, se você quiser ter uma lista de funções, tudo bem. Os itens na lista que você criou são funções para converter um valor x em um determinado tipo (por exemplo, str(x), int(x), float(x)).

Agora, leia uma linha de dados do arquivo acima:

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>

Como observado, esta linha não é suficiente para fazer cálculos porque os tipos estão errados. Por exemplo:

>>> row[1] * row[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>>

No entanto, talvez os dados possam ser emparelhados com os tipos que você especificou em types. Por exemplo:

>>> types[1]
<type 'int'>
>>> row[1]
'100'
>>>

Tente converter um dos valores:

>>> types[1](row[1])     ## Same as int(row[1])
100
>>>

Tente converter um valor diferente:

>>> types[2](row[2])     ## Same as float(row[2])
32.2
>>>

Tente o cálculo com valores convertidos:

>>> types[1](row[1])*types[2](row[2])
3220.0000000000005
>>>

Zipe os tipos de coluna com os campos e veja o resultado:

>>> r = list(zip(types, row))
>>> r
[(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
>>>

Você notará que isso emparelhou uma conversão de tipo com um valor. Por exemplo, int é emparelhado com o valor '100'.

A lista zipada é útil se você quiser realizar conversões em todos os valores, um após o outro. Tente isto:

>>> converted = []
>>> for func, val in zip(types, row):
          converted.append(func(val))
...
>>> converted
['AA', 100, 32.2]
>>> converted[1] * converted[2]
3220.0000000000005
>>>

Certifique-se de entender o que está acontecendo no código acima. No loop, a variável func é uma das funções de conversão de tipo (por exemplo, str, int, etc.) e a variável val é um dos valores como 'AA', '100'. A expressão func(val) está convertendo um valor (como um type cast).

O código acima pode ser compactado em uma única compreensão de lista.

>>> converted = [func(val) for func, val in zip(types, row)]
>>> converted
['AA', 100, 32.2]
>>>

Exercício 2.25: Criando dicionários

Lembre-se de como a função dict() pode facilmente criar um dicionário se você tiver uma sequência de nomes de chaves e valores? Vamos criar um dicionário a partir dos cabeçalhos das colunas:

>>> headers
['name', 'shares', 'price']
>>> converted
['AA', 100, 32.2]
>>> dict(zip(headers, converted))
{'price': 32.2, 'name': 'AA', 'shares': 100}
>>>

Claro, se você domina as list-comprehensions, pode fazer toda a conversão em uma única etapa usando uma dict-comprehension:

>>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA', 'shares': 100}
>>>

Exercício 2.26: A Visão Geral (The Big Picture)

Usando as técnicas neste exercício, você pode escrever instruções que convertem facilmente campos de praticamente qualquer arquivo de dados orientado a colunas em um dicionário Python.

Só para ilustrar, suponha que você leia dados de um arquivo de dados diferente como este:

>>> f = open('dowstocks.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> headers
['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']
>>> row
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']
>>>

Vamos converter os campos usando um truque semelhante:

>>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted))
>>> record
{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,
'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,
'change': -0.18}
>>> record['name']
'AA'
>>> record['price']
39.48
>>>

Bônus: Como você modificaria este exemplo para, adicionalmente, analisar a entrada date em uma tupla como (6, 11, 2007)?

Dedique algum tempo para refletir sobre o que você fez neste exercício. Revisitaremos essas ideias um pouco mais tarde.

Resumo

Parabéns! Você concluiu o laboratório de Objetos. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.