Python3 07 – Dicionários

Janeiro 6th, 2018 by Rudson Alves Leave a reply »
Este artigo é a parte 7 de 7 na série Python3

Dicionário é um tipo de estrutura de dados nativa do Python muito versátil, podendo se situar como uma base de elementos em um banco de dados a aplicações mais diretas, como: um simples dicionário de palavras, transladando entre idioma1 x idioma2, mnemônico x código de máquina, …; como elementos em um banco de dados, se auxiliado por outras estruturas de dados como lista, tuplas ou mesmo dicionários de um dicionário; como a base na criação de objetos, compondo os atributos de uma classe; entre outras. As aplicações são muitas, se bem compreendidas suas nuances e tendo um pouco de criatividade.

1. Dicionários

Dicionário é uma estrutura de dados mutável e desordenada, a qual implementa o mapeamento de pares (chave, valor), onde as chaves são os índices para mapear os valores armazenados. As Chaves podem ser qualquer conjunto (set), ou seja, qualquer coletânea de objetos hashables e únicos. Portanto, uma chave pode ser tando um inteiro, um ponto flutuante, uma string, um número complexo ou mesmo uma tupla, além de outros objetos hashables.

O par (chave, valor) é comumente chamado de item do dicionário e o valor, em algumas situações, pode ser generalizado como conteúdo, dando uma ideia mais ampla.

Dicionários podem ser definidos pelo comando dict() ou entre chaves, como em conjuntos. Justamente por este motivo, os itens de um dicionário devem respeitar alguns padrões para serem corretamente interpretados pelo Python. Para ilustrar, considere as linhas de comando abaixo:

>>> a = dict(one=1, two=2, three=3)
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> c = dict([('two', 2), ('one', 1), ('three', 3)])
>>> d = dict({'three': 3, 'one': 1, 'two': 2})
>>> e = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
>>> a == b == c == d == e
True

Estas linhas foram tomadas do manual online do Python, por se apresentar como um bom ponto de partida para discutir as possíveis inicializações de dicionários.

Na primeira linha, o dicionário a é inicializado através do comando dict(), passando como atributo os pares na forma chave = valor, sendo estes itens separados por vírgula. Está é uma forma bastante elegante para iniciar dicionários, mas possui algumas limitações:

  • somente palavras simples podem ser empregadas como chaves. Palavras como Nome Completo, Número CI,… geram um erro de sintaxe,
    >>> m = dict(nome completo = 'Albert Einstein')
      File "", line 1
        m = dict(nome completo = 'Albert Einstein')
                             ^
    SyntaxError: invalid syntax
    >>> m = dict(nome_completo = 'Albert Einstein')
    >>>

    Isto pode ser contornado usando um underline entre as palavras;

  • não pode usar aspas simples ou duplas para envolver as chaves. Neste caso o interpretador entenderá como uma expressão, gerando um erro de sintaxe novamente.
    >>> m = dict('nome completo' = 'Albert Einstein')
      File "", line 1
    SyntaxError: keyword can't be an expression
  • este formato aceita apenas strings simples como chaves, recusando qualquer índices numéricos. O uso de números como chaves gera o mesmo erro de sintaxe anterior, sendo o conteúdo interpretado como uma expressão.
    >>> m = dict(1 = 'numeral um', 2.14 = 'raiz de dois')
      File "", line 1
    SyntaxError: keyword can't be an expression
    >>> m = {1: 'numeral um', 2.14: 'raiz de dois'}
    >>>

Nas demais formas de inicialização de dicionários, é obrigatório o uso de aspas simples ou duplas para envolver as strings empregadas como chaves.

Continuando com a apresentação do primeiro quadro, na inicialização do dicionário b é empregado chaves, {}. Neste caso, os pares (chave, conteúdo) são separados por dois pontos em uma sintaxe chave:conteúdo, formando um item do dicionário. Estes itens, por sua vez, são separados por vírgula dentro das chaves. Este é a forma mais empregada na inicialização de dicionários, provavelmente por sua sintaxe simples.

Na criação do dicionário apontado por c, a função dict() recebe como argumento uma lista cujos elementos são tuplas com os pares (chave, conteúdo). Observe que o mesmo poderia ser feito com uma tupla de tuplas ou uma lista de listas, além da lista de tuplas apresentada aqui.

Na criação do dicionário d, a função dict() recebeu como argumento outro dicionário semelhante aos anteriores. Essencialmente, a função dict() faz uma cópia deste dicionário, a qual é passada para o apontador d. O mesmo poderia ser feito apenas com d = dict(a), mas acredito que a intenção aqui era salientar que dicionários são estruturas desordenadas, alternando a entrada dos itens, embora isto não faça muita diferença.

Por fim, o apontamento e é iniciado empregando a função zip()1 para gerar um iterador com as tuplas (chave, valor), o qual é passado ao comando dict() para criar o dicionário.

Observe que este último processo de criação é basicamente o mesmo empregado na criação dos dicionários c e d, visto que listas, tuplas e dicionários também criam iteradores. Isto é algo recorrente em Python, onde funções, sequências, conjuntos e dicionários empregam muito bem iteradores em suas entradas.

Para terminar este primeiro contato com dicionários, considere os dicionários estruturados na forma de registros, empregando diferentes campos com diferentes conteúdos:

>>> registro = { 'nome': 'Albert', \
...              'idade': 53, \
...              'altura': 1.68, \
...              'telefones': ('(27) 3062-2112', '(27) 99246-1195')}
>>> planeta = dict(nome = 'Vênus', 
...                massa = '0.92 MT', 
...                diâmetro = '0.949 DT', 
...                período = '0.62 anos')
>>>
>>> registro = { 'nome': 'Albert', 'idade': 53, 'altura': 1.68, 'telefones': ('(27) 3062-2112', '(27) 99246-1195')}
>>> planeta = dict(nome = 'Vênus', massa = '0.92 MT', diâmetro = '0.949 DT', período = '0.62 anos')
>>> registro
{'nome': 'Albert', 'idade': 53, 'altura': 1.68, 
'telefones': ('(27) 3062-2112', '(27) 99246-1195')}
>>> planeta
{'nome': 'Vênus', 'massa': '0.92 MT', 'diâmetro': '0.949 DT', 
'período': '0.62 anos'}

O caractere \ é comumente empregado como sinalizador de quebra de linha, mas seu uso se tornou dispensável. O interpretador do Python irá aguardar o fechamento dos parenteses, colchetes e chaves abertos em uma entrada antes de processar a linha de comando, tornando completamente dispensável o uso do caractere de quebra de linha. Espaços e quebras de linha excedentes em uma linha de comando são ignoradas pelo interpretador. No caso de uma linha de comando, a indentação das quebras de linha é meramente estética.

1.1. Dicionários são Mutáveis

Dicionários são mutáveis e, portanto, como em toda estrutura de dados mutável, é importante tomar alguns cuidados na hora de fazer suas cópias.

>>> f = dict(a)
>>> g = a.copy()
>>> h = g
>>> a == f == g == h
True

O dicionário f é construído pelo comando dict, passando como argumento o dicionário a, gerando uma cópia deste. O dicionário g é construído empregado o método de cópia do dicionário a. Já h é apenas uma cópia do apontamento de g. Considere as operações a seguir:

>>> f.clear()
>>> del(g['two'])
>>> f
{}
>>> g
{'one': 1, 'three': 3}
>>> h
{'one': 1, 'three': 3}
>>> a
{'one': 1, 'two': 2, 'three': 3}

No primeiro comando, é apagado o conteúdo do dicionário f. No segundo, o elemento indexado pela chave 'two' é removido do dicionário g. As linhas seguintes apresentam os conteúdos dos três dicionários. Como f e g são cópias de a, após as operações os três dicionários se tornam diferentes, mas o dicionário h permanece idêntico ao g. Isto é esperado, já que este é apenas um apontamento para o dicionário g, ou seja, são o mesmo dicionário.

>>> h is g
True

Embora um dicionário seja uma coletânea desordenada, seus elementos podem ser indexados pela sua respectiva chave, como em uma lista ou tupla:

>>> a['one']
1
>>> b['three']
3
>>> planeta['nome']
'Vênus'
>>> registro['idade']
53

Mas diferente de uma lista, dicionários aceitam a inserção conteúdos em índices inexistentes, o que não é permitido em uma lista:

>>> L = [4, 3, 5]
>>> L[8] = 0
Traceback (most recent call last):
  File "", line 1, in 
IndexError: list assignment index out of range
>>>
>>> planeta['Período de Rotação'] = '−243.02 anos'
>>> planeta
{'nome': 'Vênus', 'massa': '0.92 MT', 'diâmetro': '0.949 DT', 
'período': '0.62 anos', 'Período de Rotação': '−243.02 anos'}

Nos comandos acima, a lista L possui apenas três elementos indexados pelos inteiros 0, 1 e 2, enquanto que o dicionário planeta possui quatro itens indexados pelas chaves 'nome', 'massa', 'diâmetro' e 'período'.

A inserção de um novo conteúdo no índice 8 da lista L gera um erro de indexação fora de intervalo, já que seu maior índice é o 2. No entanto, o dicionário planeta aceita inserção em qualquer índice apontado por uma chave inexistente. Este processo apenas cria o mapeamento de um novo item na nova chave e aponta a um novo conteúdo. Desta forma, a linha planeta['Período de Rotação'] = '−243.02 anos' apenas irá adicionar a chave Período de Rotação e irá apontar seu conteúdo para '−243.02 anos', no dicionário planeta.

1.2. Operadores e Métodos

A lista de métodos e operadores da estrutura de dados dicionário é bem curta, por isto estes serão apresentados juntos. Como feito em textos anteriores, não será dado destaque a sintaxe dos métodos e operadores iniciados por duplo underline, dando prioridade à função e operador para o qual o código foi criado.

Segue abaixo a lista com os operadores e métodos da classe dicionário:

  • __contains__ – implementa o operador está contido (in) para as chaves do dicionário,
    >>> 'two' in a
    True
    >>> 2 in a
    False
  • __delitem__ – implementa a função del() para apagar um item do dicionário. Neste caso, a função del() deve receber como argumento o item a ser removido do dicionário, como no código abaixo.
    >>> a
    {'one': 1, 'two': 2, 'three': 3}
    >>> del(a['three'])
    >>> a
    {'one': 1, 'two': 2}
  • __eq__ e __ne__ – equivalente aos operadores condicionais == e !=, respectivamente. Dicionários suportam apenas as condicionais == e !=. Os demais operadores geram o erro de tipagem TypeError.
    >>> a != b == c
    True
    >>> a > b
    Traceback (most recent call last):
      File "", line 1, in 
    TypeError: '>' not supported between instances of 'dict' and 'dict'

    Não sei se é o caso, mas eventualmente é feita a implementação de um operador apenas para gerar o código de erro adequado para a situação, o que pode ser o caso das implementações dos demais operadores em dicionário.

  • __getitem__ e __setitem__ – implementação da indexação e criação de novos elementos no dicionário.
    >>> a['one']
    1
    >>> a['four'] = 4
    >>> a['three'] = 3
    >>> a
    {'one': 1, 'two': 2, 'four': 4, 'three': 3}
  • __iter__ – gera um iterável com as chaves do dicionário,
    >>> for k in a:
    ...   print(k)
    ... 
    one
    two
    four
    three

    e também implementa a função iter(),

    >>> keys = iter(a)
    >>> for k in keys:
    ...   print(k)
    ... 
    one
    two
    four
    three
  • __len__ – implementa a função len() para retornar o comprimento do dicionário.
    >>> len(a)
    4
    >>> len(b)
    3
  • clear() – apaga o conteúdo do dicionário.
    >>> b
    {'one': 1, 'two': 2, 'three': 3}
    >>> b.clear()
    >>> b
    {}
  • copy() – retorna uma cópia do dicionário.
    >>> b = a
    >>> a['five'] = 5
    >>> a == b
    True
    >>> b = a.copy()
    >>> del(a['five'])
    >>> a == b
    False
  • keys(), values() e items() – retorna um dict_keys, dict_values e um dict_items com as chaves, valores e a itens do dicionário, respectivamente. No Python 3 os retornos destes métodos de dicionários deixaram de ser listas com as chaves, valores e itens como tuplas (chave, valor), para retornarem objetos dict_views. Estes dict_views são objetos iteráveis, somente de leitura, rodando sobre as chaves, valores e itens do dicionário, respectivamente.
    >>> a.keys()
    dict_keys(['one', 'two', 'four', 'three'])
    >>> a.values()
    dict_values([1, 2, 4, 3])
    >>> a.items()
    dict_items([('one', 1), ('two', 2), ('four', 4), ('three', 3)])
    >>> k = list(a.keys())
    >>> v = list(a.values())
    >>> i = list(a.items())
    >>> k
    ['one', 'two', 'four', 'three']
    >>> v
    [1, 2, 4, 3]
    >>> i
    [('one', 1), ('two', 2), ('four', 4), ('three', 3)]

    Observe que k, v e i utilizam o comando list() para gerar as listas com os iteráveis dict_keys, dict_values e dict_items, retornados dos respectivos métodos. Como os dict_views são iteráveis, eles podem ser empregados diretamente em laços de repetição.

    >>> for k in a.keys():
    ...   print(k)
    ... 
    one
    two
    four
    three
  • fromkeys(Iterável, Valor) – retorna um dicionário utilizando o Iterável para criar as chaves e Valor, como seus conteúdos. Se o Valor for omitido, os conteúdos são preenchidos com None, nada.
    >>> {}.fromkeys(a.keys(), 0)
    {'one': 0, 'two': 0, 'three': 0, 'four': 0}
    >>> {}.fromkeys(a, 0)
    {'one': 0, 'two': 0, 'three': 0, 'four': 0}
  • get(Chave, Valor) – retorna o item indexado pela Chave. Se não existir um conteúdo com esta Chave, retorna Valor. Caso o Valor não seja declarado e o item não exista, este método retornará nada.
    >>> a.get('one')
    1
    >>> a.get('six', 99)
    99
  • pop(Chave, Valor) – remove o item de índice Chave e retorna seu valor. Caso o item não exista, retorna Valor.
    >>> a.pop('one')
    1
    >>> a
    {'two': 2, 'four': 4, 'three': 3}
    >>> a.pop('nine', 99)
    99
  • popitem() – retorna uma tupla com o item (chave, valor). Se o dicionário estiver vazio, retorna um erro KeyError.
    >>> a.popitem()
    ('three', 3)
    >>> a.popitem()
    ('four', 4)
    >>> a.popitem()
    ('two', 2)
    >>> a.popitem()
    Traceback (most recent call last):
      File "", line 1, in 
    KeyError: 'popitem(): dictionary is empty'
  • setdefault(Chave, Valor) – retorna o elemento no índice Chave, caso exista. Caso não exista, retorna Valor e adiciona um novo item (Chave, Valor).
    >>> c
    {'one': 1, 'two': 2, 'three': 3, 'six': 200}
    >>> c.setdefault('two', 'DOIS')
    2
    >>> c.setdefault('six', 6)
    6
    >>> c.setdefault('five', 5)
    5
    >>> c
    {'one': 1, 'two': 2, 'three': 3, 'six': 6, 'five': 5}
  • update(Dic) – atualiza o dicionário com as chaves e valores do dicionário Dic.
    >>> c.update({'six': 'seis', 'one': 'um', 'four': 'quatro'})
    >>> c
    {'one': 'um', 'two': 2, 'three': 3, 'six': 'seis', 'five': 5, 'four': 'quatro'}

    Observe que o dicionário c foi atualizado com a adição dos itens ('six', 'seis') e ('five', 5) e a atualização do conteúdo na chave 'one' para o valor 'um'.

2. Considerações

Estes sete primeiros textos compõem o básico para iniciar na programação em Python, embora nem mesmo tenha arranhado todo seu conteúdo e diversidade, principalmente em se tratando de bibliotecas. As diversas bibliotecas do Python podem ser aprendidas por demanda com rápidas consultas na documentação da linguagem e infinidade de exemplos e aplicações na internet.

Os próximos textos são mais específicos, mas não menos interessantes e importantes no universo Python, sendo essenciais para discutir algumas das características levemente citadas até aqui.

Para terminar considere o problema de um histograma, proposto pelo livro Think Python, How to Think Like a Computer Scientist de Allen B. Downey, 2ª edição, na seção 11.2 Dictionary as a collection of counters.

O problema consiste em criar uma função para gerar o histograma dos caracteres de uma string passada como argumento, ou seja, contar a ocorrência de cada caractere e retornar seu resultado através de um dicionário. O código proposto pelo professor Allen B. Downey é apresentado a seguir:

>>> def histogram(word):
...   hist = dict()
...   for l in word:
...     if l not in hist:
...       hist[l] = 1
...     else:
...       hist[l] += 1
...   return hist.copy()

Bastante didático e claro. Proponho mais quatro códigos para fazer a mesma coisa, mas com algumas diferenças, aplicando os conteúdos discutidos até o momento. Verifique cada função e tente compreendê-las.

  1. Empregando o método get() do dicionário:
    >>> def histogram(word):
    ...   hist = dict()
    ...   for l in word:
    ...     hist[l] = hist.get(l, 0) + 1
    ...   return hist.copy()
  2. Usando o método count() de string para contar a ocorrência de um caractere na palavra, em parceria com conjuntos, para evitar a repetição de caracteres no laço de repetição for:
    >>> def histogram(word):
    ...   letters = set(word)
    ...   hist = {}
    ...   for l in letters:
    ...     hist[l] = word.count(l)
    ...   return hist.copy()
  3. Empregando uma List Comprehensions para compactar o código anterior:
    >>> def histogram(word):
    ...   letters = set(word)
    ...   hist = dict([(l, word.count(l)) for l in letters])
    ...   return hist.copy()
  4. Tudo em uma única linha:
    >>> def histogram(word):
    ...   return dict([(l, word.count(l)) for l in set(word)]).copy()

QR Code
  1. A funções zip, map e lambda serão apresentadas no nono texto desta série.
Advertisement

Deixe uma resposta

This blog is kept spam free by WP-SpamFree.