Aula 1. Estruturas de dados Fundamentais

Nesta aula conheceremos as principais estruturas de dados nativas da linguagem Python.

Conteúdos

1. Tipos básicos

Representamos informação usando um conjunto de tipos básicos, fornecidos pela linguagem. São eles:

Tipo Desc Exemplo
Integer (int) Permite representar informação numérica (números inteiros) 42
Float (float) Permite representar informação numérica, com casas decimais 42.0
String (str) Permite representar informação textual "Hello World"
Boolean (bool) Permite representar valores lógicos (lógica booleana) True ou False

Vamos a seguir estudar cada um destes tipos

Representando informação numérica com os tipos Integer e Float

Uma sequência de dígitos é interpretada como um número inteiro (tipo int).

In [1]:
42
Out[1]:
42

Números reais com casas decimais são representados usando o sistema de ponto flutuante (tipo float):

In [2]:
42.0
Out[2]:
42.0
In [3]:
42.567
Out[3]:
42.567

Dica: Para consultar o tipo de um determinado valor, podemos usar a função nativa type:

In [4]:
type(42)
Out[4]:
int
In [5]:
type(42.0)
Out[5]:
float

Operações aritméticas

O interpretador Python permite operar valores numéricos usando um conjunto de operadores aritméticos pré-definidos. As operações usadas mais frequentemente são:

Operação Operador Exemplo Resultado
Adição + 9 + 7 16
Subtração - 9 - 7 2
Multiplicação * 9 * 7 63
Divisão / 5 / 2 2.5
Divisão inteira // 5 // 2 2
Exponenciação ** 9**2 81
Módulo % 9 % 2 1

Regra 1: Operações envolvendo apenas números inteiros resultam em números inteiros (com exceção da divisão)

In [6]:
42 + 5
Out[6]:
47
In [7]:
42 - 5
Out[7]:
37
In [8]:
42 * 5
Out[8]:
210
In [9]:
42 ** 2
Out[9]:
1764
In [10]:
42 % 4
Out[10]:
2

A operação de divisão é a exceção à regra:

In [11]:
42/2
Out[11]:
21.0
In [12]:
42/5
Out[12]:
8.4

Mas podemos utilizar o operador de divisão inteira (//), que descarta a parte decimal

In [13]:
42//2
Out[13]:
21
In [14]:
41//5
Out[14]:
8

Regra 2: Operações envolvendo pelo menos um número de ponto flutuante resultam em números de ponto flutuante:

In [15]:
42.0 + 5
Out[15]:
47.0
In [16]:
42 - 5.0
Out[16]:
37.0
In [17]:
42 * 5.0
Out[17]:
210.0
In [18]:
42 ** 2.0
Out[18]:
1764.0
In [19]:
42.0 % 2
Out[19]:
0.0
In [20]:
42 / 2
Out[20]:
21.0
In [21]:
42.0 // 2
Out[21]:
21.0
In [22]:
41.0 // 5
Out[22]:
8.0

Comparando valores numéricos

Podemos comparar valores numéricos usando os operadores de comparação. Estes operadores avaliam se a expressão é verdadeira, retornando valores lógicos True ou False (mais sobre valores lógicos a seguir).

Operador Descrição
== Retorna True se valores de dois operandos são iguais
!= Retorna True se valores de dois operandos são diferentes
< Retorna True se valor do operando à esquerda é menor que o da direita
<= Retorna True se valor do operando à esquerda é menor ou igual ao da direita
> Retorna True se valor do operando à esquerda é maior que o da direita
>= Retorna True se valor do operando à esquerda é maior ou igual ao da direita
In [23]:
5 == 4
Out[23]:
False
In [24]:
5 != 4
Out[24]:
True
In [25]:
5 < 4
Out[25]:
False
In [26]:
5 <= 4
Out[26]:
False
In [27]:
5 > 4
Out[27]:
True
In [28]:
5 >= 4
Out[28]:
True

Na comparação, o tipo utilizado para representar o valor não importa:

In [29]:
42.0 == 42
Out[29]:
True
In [30]:
42.01 == 42
Out[30]:
False
In [31]:
42.5
Out[31]:
42.5
In [32]:
42.0
Out[32]:
42.0

Representando informação textual com Strings

Strings podem ser codificadas usando aspas duplas ou simples, desde que usadas de forma coerente: aspas duplas devem ser fechadas com aspas duplas, assim como aspas simples devem ser fechadas com aspas simples

In [33]:
"String criada com aspas duplas"
Out[33]:
'String criada com aspas duplas'
In [34]:
'String criada com aspas simples'
Out[34]:
'String criada com aspas simples'

Usamos uma combinação de aspas simples e duplas quando queremos que elas sejam interpretadas como caracteres pertencentes à própria string

In [35]:
"String criada com aspas duplas, contendo texto com 'aspas simples'"
Out[35]:
"String criada com aspas duplas, contendo texto com 'aspas simples'"
In [36]:
'String criada com aspas simples, contendo texto com "aspas duplas"'
Out[36]:
'String criada com aspas simples, contendo texto com "aspas duplas"'

Podemos também usar triplas (3 aspas duplas ou 3 aspas simples) para criar strings com quebra de linha. Observe que a quebra de linha é codificada na string com um caractere especial, o newline ('\n'). Mas as linhas só são de fato quebradas quando imprimimos a string no console, usando a função print

In [37]:
"""Aspas triplas permitem criar strings com múltiplas linhas.
Cada quebra de linha é transformada no caractere especial 'newline'.
Quando a string é imprimida, o caractere especial 'newline' quebra a linha."""
Out[37]:
"Aspas triplas permitem criar strings com múltiplas linhas.\nCada quebra de linha é transformada no caractere especial 'newline'.\nQuando a string é imprimida, o caractere especial 'newline' quebra a linha."
In [38]:
print("""Aspas triplas permitem criar strings com múltiplas linhas.
Cada quebra de linha é transformada no caractere especial 'newline'.
Quando a string é imprimida, o caractere especial 'newline' quebra a linha.""")
Aspas triplas permitem criar strings com múltiplas linhas.
Cada quebra de linha é transformada no caractere especial 'newline'.
Quando a string é imprimida, o caractere especial 'newline' quebra a linha.

Mas e se quiséssemos de fato armazenar os caracteres '\' e 'n' na string, sem que fossem interpretados como um caractere especial? Neste desvemos usar um outro caractere especial, o escape character ('\'). Sempre que usamos este caractere dizemos que o caractere seguinte não deve ser interpretado como caractere especial

In [39]:
print("Se quisermos pular uma linha é só usar o caractere 'newline'.\nEle é considerado um caractere especial.")
Se quisermos pular uma linha é só usar o caractere 'newline'.
Ele é considerado um caractere especial.

E se quisermos incluir os dois caracteres \e n literalmente na string? Podemos usar um backslash (\) para escapar o caractere especial

In [40]:
print("Se quisermos pular uma linha é só usar o caractere 'newline' (\\n).\nEle é considerado um caractere especial.")
Se quisermos pular uma linha é só usar o caractere 'newline' (\n).
Ele é considerado um caractere especial.

Strings cruas (raw strings)

Se quisermos escapar todos os caracteres especiais, podemos dizer que uma string é "crua", (uma raw string). Para isso, basta adicionar o caractere 'r' imediatamente antes de abrir a string

In [41]:
r"Esta é uma string crua.\nCaracteres especiais são automaticamente escapados, e interpretados literalmente"
Out[41]:
'Esta é uma string crua.\\nCaracteres especiais são automaticamente escapados, e interpretados literalmente'
In [42]:
print(r"Esta é uma string crua.\nCaracteres especiais são automaticamente escapados, e interpretados literalmente")
Esta é uma string crua.\nCaracteres especiais são automaticamente escapados, e interpretados literalmente
In [43]:
print("Esta NÃO é uma string crua.\nCaracteres especiais NÃO são escapados, e exercem sua função esperada")
Esta NÃO é uma string crua.
Caracteres especiais NÃO são escapados, e exercem sua função esperada

Formatação de Strings

A linguagem oferece divermas maneiras para formatar strings, usando valores contidos em variáveis ou resultantes de expressões avaliadas em tempo de execução. Aqui vamos conhecer apenas os dois mais comumente usados

O método .format()

Este método realiza a formatação da string inserindo entre as chaves (também chamados "campos de substituição") os valores que são passados como argumento. Caso os campos de substituição estejam vazios, a substituição dos valores ocorre na mesma ordem em que são passados para a função format.

In [44]:
"My name is {}. I am {} years old".format("Luke Skywalker", 18 + 10)
Out[44]:
'My name is Luke Skywalker. I am 28 years old'

Campos de substituição podem incluir o índice (posição) do argumento que deve ser substituído

In [45]:
"My name is {0}. I am {0} years old".format("Luke Skywalker", 18 + 10)
Out[45]:
'My name is Luke Skywalker. I am Luke Skywalker years old'
In [46]:
"My name is {1}. I am {0} years old".format("Luke Skywalker", 18 + 10)
Out[46]:
'My name is 28. I am Luke Skywalker years old'

Caso a função format receba argumentos nomeados, podemos usar os nomes nos campos de substituição

In [47]:
"My name is {name}. I am {age} years old".format(name="Luke Skywalker", age=18 + 10)
Out[47]:
'My name is Luke Skywalker. I am 28 years old'

Mais detalhes sobre o método format estão documentados aqui

As f-strings

A partir do Python 3.6, a formatação de strings ficou ainda mais simples com as f-strings (ou "formatted string literals"). Para construir uma f-string, devemos (i) adicionar o caractere 'f' imediatamente antes de abrir a string; e (ii) usar chaves ({}) para conter expressões cujos valores deverão ser substituídos. Veja o exemplo abaixo

In [48]:
myName = "Luke Skywalker"
age = 18 + 10

f"My name is {myName}. I am {age} years old"
Out[48]:
'My name is Luke Skywalker. I am 28 years old'

Atualmente este é o método de formatação mais indicado pela comunidade Python. Veja mais detalhes na documentação


Representando valores lógicos com Boolean

Na álgebra booleana, variáveis podem assumir apenas dois valores distintos: "verdadeiro" (True) ou "falso" (True). Na linguagem Python, estes elementos lógicos são representados com o tipo Boolean (bool). Operadores de comparação envolvendo números resultam em valores lógicos. O uso de expressões lógicas é muito comum quando construímos estruturas de decisão no nosso programa, como veremos adiante

Na álgebra booleana, são definidas três operações fundamentais: a conjunção (and), a disjunção (or), e a negação (not). Podemos construir expressões a partir destes operadores, e avaliar seu valor lógico, usando como base uma tabela-verdade

X Y X and Y X or Y not X
False False False False True
False True False True True
True False False True False
True True True True False
In [49]:
True and True
Out[49]:
True
In [50]:
True and False
Out[50]:
False
In [51]:
True or False
Out[51]:
True
In [52]:
False or False
Out[52]:
False
In [53]:
True and not False
Out[53]:
True
In [54]:
True or not False
Out[54]:
True

Quando as expressões se tornam mais complexas, podemos indicar a ordem de avaliação das operações usando parênteses

In [55]:
(True and False) and (True and not False)
Out[55]:
False
In [56]:
(not False) and (not(not True))
Out[56]:
True

Valores lógicos também resultam de operações de comparação

In [57]:
5 + 10 == 15
Out[57]:
True
In [58]:
5 + 11 == 18
Out[58]:
False
In [59]:
20 >= 20
Out[59]:
True
In [60]:
10 >= 20
Out[60]:
False
In [61]:
5 + 10 == 15 or 5 + 11 ==18
Out[61]:
True
In [62]:
(5 > 10) or (5 <=5) and not(10==12)
Out[62]:
True

O interpretador Python considera que números diferentes do zero possuem valor lógico True, enquanto o zero possui valor lógico False. Podemos então operar valores numéricos com operadores lógicos

obs: A função bool converte valores de tipos não-booleanos para booleanos (coerção de tipos, ou type casting)

In [63]:
bool(1)
Out[63]:
True
In [64]:
bool(-1)
Out[64]:
True
In [65]:
bool(2)
Out[65]:
True
In [66]:
bool(0)
Out[66]:
False
In [67]:
bool(1 and 0)
Out[67]:
False
In [68]:
bool(2 and 0)
Out[68]:
False
In [69]:
bool(1 and 1)
Out[69]:
True
In [70]:
bool(1 or 0)
Out[70]:
True
In [71]:
bool(1 and not 0)
Out[71]:
True

Strings também podem assumir valores lógicos. Neste caso, strings de comprimento maior que 0 equivalem a True, enquanto strings de comprimento igual a zero equivalem a False.

In [72]:
# strings de comprimento > 0 equivalem a True
bool("texto")
Out[72]:
True
In [73]:
# strings de comprimento 0 equivalem a False
bool("")
Out[73]:
False
In [74]:
bool("Uma string" and 1)
Out[74]:
True
In [75]:
bool("Uma string" and "")
Out[75]:
False

2. Tipos compostos

Tipos compostos permitem armazenar conjuntos de valores em uma única estrutura.

Listas

Listas são representadas pelo tipo list. Cada elemento ocupando uma posição dentro da lista é chamado de item. O número de itens que uma lista armazena é referido como seu comprimento. A princípio não há limites para o comprimento de uma lista, embora listas grandes demais certamente exigem mais recursos computacionais, podendo se tornar intratáveis. Uma característica importante das listas é que elas permitem armazenar itens de tipos diferentes dentro de uma mesma estrutura (embora seja mais usual armazenar itens do mesmo tipo). Podemos por exemplo armazenar dentro de uma única lista strings, booleanos, inteiros, floats, e até outras listas!

Na ilustração acima, temos uma lista de comprimento 5, armazenando itens de tipos diferentes. Cada item da lista ocupa um espaço próprio, representado como um "quadradinho". Os números abaixo de cada quadrado representam os índices de cada item na lista. Os índices nada mais são que endereços numéricos, que nos permitem referenciar elementos em cada posição na lista. Por exemplo, se quisermos nos referir ao número 42, indicaremos o índice (ou endereço) 2.

Obs: Em Python, a numeração dos índices começa em zero, e portanto o primeiro elemento se encontra na posição 0. Da mesma forma, o último item de uma lista de comprimento $n$ se encontra na posição $n-1$.

Criando listas

Podemos criar uma lista vazia apenas abrindo e fechando colchetes

In [76]:
[]
Out[76]:
[]

ou usando a função list

In [77]:
list()
Out[77]:
[]

Podemos adicionar elementos a uma lista já existente usando o método append

In [78]:
l = list()
l.append('el1')
l.append('el2')
l
Out[78]:
['el1', 'el2']

e remover elementos usando o método remove

In [79]:
l.remove('el1')
l
Out[79]:
['el2']

Podemos criar uma lista pré-populada, inserindo os elementos nela contidos entre colchetes, separados por vírgulas. Note que a ordem dos itens permanece a mesma em que foram inseridos durante a criação da lista. Vamos criar a lista dad como exemplo na figura acima

In [80]:
[ "Olá", True, 42, 9.87, ['a','b','c']]
Out[80]:
['Olá', True, 42, 9.87, ['a', 'b', 'c']]

Podemos também criar uma lista contendo uma sequência de números inteiros (uma range), que é criada usando as funções list e range.

Obs 1: Perceba que para efetivamente criar a lista passamos o resultado da função range para a função list. Isso ocorre porque a função range cria um objeto que sabe como produzir a sequência de números, mas não o faz automaticamente.

Obs 2: A função range cria uma sequência de números de 0 a $n-1$, caso apenas um argumento, com valor $n$, seja fornecido. Consulte a documentação da função para ver outras formas de criar uma sequência.

In [81]:
range(10)
Out[81]:
range(0, 10)
In [82]:
list( range(10) )
Out[82]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Compreensão de Listas (List comprehension)

O método de compreensão de listas permite criar listas dinamicamente, usando uma expressão. É portanto uma forma mais eficiente e concisa de se criar listas em Python

Este método usa a seguinte sintaxe:

[ expressão(item) for item in lista if condição(item) ]

sendo

  • lista: uma lista pré-existente, ou objeto a ser iterado;
  • item: a variável de iteração;
  • expressão: uma expressão que pode usar o valor em item;
  • condição (opcional): o resultado da expressão só é incluído na lista final caso a avaliação da condição resulte em True.

Por exemplo, podemos criar uma lista contendo os quadrados dos números de 1 a 10

In [83]:
[ x**2 for x in range(1,11) ]
Out[83]:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

ou os quadrados dos números pares de 1 a 20

In [84]:
[ x**2 for x in range(1,21) if x%2==0 ]
Out[84]:
[4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

ou até mesmo uma lista contendo listas de comprimento 2. Cada lista interna armazena um número par entre 1 e 20 e seu quadrado

In [85]:
[ [x,x**2] for x in range(1,21) if x%2==0 ]
Out[85]:
[[2, 4],
 [4, 16],
 [6, 36],
 [8, 64],
 [10, 100],
 [12, 144],
 [14, 196],
 [16, 256],
 [18, 324],
 [20, 400]]

Usando o exemplo acima, podemos criar uma lista de strings formatadas com os números e seus quadrados

In [86]:
[ f"{x}^2 = {x**2}" for x in range(1,21) if x%2==0 ]
Out[86]:
['2^2 = 4',
 '4^2 = 16',
 '6^2 = 36',
 '8^2 = 64',
 '10^2 = 100',
 '12^2 = 144',
 '14^2 = 196',
 '16^2 = 256',
 '18^2 = 324',
 '20^2 = 400']

O método de compreensão de listas fornece à linguagem Python um grande ganho em expressividade. Podemos criar listas a partir de expressões relativamente complexas em apenas uma linha!

Acessando elementos em uma lista

Agora que temos uma lista, como acessar os elementos dentro dela? Existem basicamente duas formas: a indexação (indexing) e o recorte (slicing). Por fim, podemos usar também algumas funções aplicáveis a listas. Vejamos cada um deles.

Indexing

Usando a notação de indexação [i] podemos recuperar o elemento em determinada posição em uma lista. Para isso basta indicar o índice i do elemento que queremos entre colchetes, ao lado da nossa lista (ou da variável que a armazena)

In [87]:
myList = ['a','b','c','d','e','f','g','h','i','j']
In [88]:
myList[0]
Out[88]:
'a'
In [89]:
myList[9]
Out[89]:
'j'

Podemos usar números negativos para nos referir a elementos na lista seguindo uma lógica de ordem reversa. Por exemplo, podemos usar o índice -1 para nos referir ao último elemento, -2 ao penúltimo, e assim por diante.

In [90]:
myList[-1]
Out[90]:
'j'
In [91]:
myList[-2]
Out[91]:
'i'
In [92]:
myList[-10]
Out[92]:
'a'

Se tentarmos passar um índice que não exista na lista (por exemplo o índice 10 em uma lista de comprimento 10), o interpretador encerrará a execução do programa e nos mostrará uma mensagem de erro

In [94]:
myList[10]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-94-bb7b0cf4a394> in <module>()
----> 1 myList[10]

IndexError: list index out of range

Podemos também atualizar valores contidos na lista usando seu índice (posição na lista)

In [95]:
myList[0] = 'A'
In [96]:
myList[1] = 'B'
In [97]:
myList
Out[97]:
['A', 'B', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

Slicing

Usando a notação de recorte [i:j] podemos recuperar um conjunto de elementos de uma lista. A diferença para a indexação é que o recorte retorna uma sublista (um pedaço, ou slice da lista original), em vez de um único elemento. Para isso indicamos, entre colchetes e separados por dois pontos, dois números: o primeiro (i) é o índice do elemento no começo do recorte; o segundo (j) é o índice do elemento que delimita o fim do pedaço (não-inclusivo).

In [98]:
myList = ['a','b','c','d','e','f','g','h','i','j']
In [99]:
myList[1:6]
Out[99]:
['b', 'c', 'd', 'e', 'f']

Podemos omitir o número i ou j se quisermos que o pedaço comece do primeiro elemento da lista ou que termine no último elemento da lista, respectivamente.

In [100]:
myList[:6]
Out[100]:
['a', 'b', 'c', 'd', 'e', 'f']
In [101]:
myList[4:]
Out[101]:
['e', 'f', 'g', 'h', 'i', 'j']

Embora pouco usual, podemos definir o passo do recorte alterando a notação de recorte para [i:j:k]. Neste caso, k é o tamanho do passo, ou seja, o número de elementos que devem ser pulados.

In [102]:
myList[2:8:2]
Out[102]:
['c', 'e', 'g']
In [103]:
myList[:8:2]
Out[103]:
['a', 'c', 'e', 'g']
In [104]:
myList[2::2]
Out[104]:
['c', 'e', 'g', 'i']
In [105]:
myList[::2]
Out[105]:
['a', 'c', 'e', 'g', 'i']

Funções aplicáveis a listas

Existem algumas funções permitem obter elementos com características específicas de uma lista. Por exemplo, poderíamos querer consultar quais os elemento com maior e menor valor dentro da lista. Para isso, podemos usar as funções max e min, respectivamente

In [106]:
myList = [1,3,10,-4,12,7,42,-3,0,1]
In [107]:
max(myList)
Out[107]:
42
In [108]:
min(myList)
Out[108]:
-4

E se precisarmos usar a lista em ordem inversa? A função reversed espera receber uma lista e retorna um objeto iterador, que pode ser interpretado como uma cópia da lista original porém com os elementos em ordem inversa

In [109]:
reversed(myList)
Out[109]:
<list_reverseiterator at 0x7f4157779278>
In [110]:
list(reversed(myList))
Out[110]:
[1, 0, -3, 42, 7, 12, -4, 10, 3, 1]

Finalmente, podemos verificar se um elemento com determinado valor existe dentro de uma lista. Para isso, construímos uma expressão utilizando o operador in. Caso o elemento exista na lista, a expressão retornará o valor booleano True e, caso contrário, False

In [111]:
42 in myList
Out[111]:
True
In [112]:
-42 in myList
Out[112]:
False

Combinando listas

É possível construir listas facilmente a partir de outras. Para isso usamos o operador +, que pode ser usado para concatenar listas. obs: Listas só podem ser somadas a listas! Portanto é fundamental que ambos os operandos sejam listas

In [113]:
['a','b','c'] + [1, 2, 3] + [True, False]
Out[113]:
['a', 'b', 'c', 1, 2, 3, True, False]
In [114]:
lista1 = ['a','b']
lista2 = [42, 43]

lista1 + lista2
Out[114]:
['a', 'b', 42, 43]

Emparelhando listas com a função Zip

A função zip emparelha duas ou mais listas (ou outros elementos iteráveis), retornando um iterador (zip object) com o mesmo comprimento do menor dos iterávei usados para construí-lo. Porém, usualmente aplicamos a função a iteráveis de mesmo comprimento. Cada elemento dentro do iterador zip é uma n-tupla, sendo n o número de iteráveis passados para a função

Podemos emparelhar duas listas de mesmo comprimento. Como ela retorna um iterador, precisamos usar a função list caso queiramos que o resultado seja convertido em uma lista

In [1]:
l1 = [  1,    7,   4,   0,   3 ]
l2 = [ 'a', 'b', 'c', 'd', 'e' ]

list( zip(l1,l2) )
Out[1]:
[(1, 'a'), (7, 'b'), (4, 'c'), (0, 'd'), (3, 'e')]

ou de comprimentos diferentes

In [2]:
l1 = [  1,    7,   4,   0,   3 ]
l2 = [ 'a', 'b', 'c' ]

list( zip(l1,l2) )
Out[2]:
[(1, 'a'), (7, 'b'), (4, 'c')]

Também podemos emparelhar mais que apenas duas listas

In [3]:
l1 = [  1,    7,   4,   0,   3 ]
l2 = [ 'a', 'b', 'c', 'd', 'e' ]
l3 = [ 'v', 'w', 'x', 'y', 'z' ]

list( zip(l1,l2,l3) )
Out[3]:
[(1, 'a', 'v'), (7, 'b', 'w'), (4, 'c', 'x'), (0, 'd', 'y'), (3, 'e', 'z')]

Tuplas

Tuplas são representadas pelo tipo tuple, e armazenam conjuntos ordenados de valores. Portanto, assim como nas listas, a ordenação dos valores importa. A principal característica das tuplas que as diferencia das listas é que tuplas são imutáveis. Isso significa que não é possível adicionar ou remover elementos após sua criação

Criando tuplas

A criação de tuplas é bastante similar à criação de listas. Porém, todos os elementos que a compõem devem ser inseridos durante sua criação. Para isso, ordenamos os $n$ valores a compor a n-tupla entre parênteses

Tupla com apenas 1 elemento (1-tupla):

In [115]:
('el1',)
Out[115]:
('el1',)

Par ordenado (2-tupla):

In [116]:
('el1','el2')
Out[116]:
('el1', 'el2')

Tripla ordenada (3-tupla):

In [117]:
('el1','el2','el3')
Out[117]:
('el1', 'el2', 'el3')

e assim por diante...

Podemos também usar a função tuple para converter uma lista (ou objeto iterável) em uma tupla. Podemos por exemplo criar uma 5-tupla com números de 0 a 4 usando a função range

In [118]:
tuple(range(5))
Out[118]:
(0, 1, 2, 3, 4)

ou transformar uma lista em uma tupla

In [119]:
l = ["Luke", "Skywalker", 28, "Jedi"]
tuple( l )
Out[119]:
('Luke', 'Skywalker', 28, 'Jedi')

Acessando elementos em uma tupla

ou at

A notação usada pra acessar elementos em uma tupla é idêntica à de listas. Usamos um índice posicional, que varia de $0$ a $n-1$, para uma tupla de comprimento $n$

Combinando tuplas

Assim como em listas, podemos combinar tuplas usando o operador +

In [120]:
(0,1,2,3) + ('a','b','c','d')
Out[120]:
(0, 1, 2, 3, 'a', 'b', 'c', 'd')

Atualizando tuplas

Tuplas são imutáveis, portanto NÃO podem ser atualizadas. Mas em alguns casos podemos simular uma atualização criando novas tuplas tomando como base tuplas pré-existentes. Considere a tupla-base abaixo

In [121]:
tupla_base = (0,1,2,3)
"Adicionando" um novo elemento

Podemos criar uma nova tupla contendo todos os elementos da tupla-base mais um novo elemento. Neste caso fazemos uma combinação de tuplas

In [122]:
t1 = tupla_base + (4,)
t1
Out[122]:
(0, 1, 2, 3, 4)
"Removendo" o último elemento

Podemos criar uma nova tupla contendo todos os elementos da tupla-base exceto o último

In [123]:
t1 = tupla_base[:-1]
t1
Out[123]:
(0, 1, 2)

Dicionários

Dicionários são representados pelo tipo dict e, assim como listas e tuplas, são estruturas de dados que permitem armazenar conjuntos de valores. A principal diferença é que os valores são indexados explicitamente e não são ordenados. Dicionários possuem chaves (keys), que referenciam os valores (values) armazenados em memória. Dicionários são também conhecidos como mapas ou índices.

Obs 1: As chaves não precisam ser necessariamente numéricas, podendo ser usado qualquer objeto "hasheável" (hashable). Podem ser chaves, por exemplo, valores dos tipos int, float, string, boolean ou tuplas. Já listas e dicionários não podem ser usados como chaves. No entanto, normalmente criamos dicionários cujas chaves possuem o mesmo tipo.

Obs 2: Cada chave deve ser única no dicionário e referenciar um único valor. Entretanto, um determinado valor pode ser referenciado por múltiplas chaves.

Na ilustração acima, temos um dicionário composto por 5 chaves, que referenciam 4 valores distintos. Cada valor ocupa um espaço próprio na memória, representado como o retângulo que o contém. Diferentemente de listas, os índices não são posicionais, o que significa que é impossível resgatar elementos com base em sua posição no dicionário. Em vez disso usamos as chaves como índice para cada valor. Por exemplo, se quisermos resgatar o número 42, podemos indicar a chave 0 ou a chave 'k9'.

Criando dicionários

Podemos criar um dicionário vazio simplesmente abrindo e fechando chaves

In [124]:
{}
Out[124]:
{}

ou usando a função dict

In [125]:
dict()
Out[125]:
{}

Podemos adicionar elementos a um dicionário já existente usando o método update

In [126]:
d = dict()
d.update( key1='value1',key2='value2' )
d
Out[126]:
{'key1': 'value1', 'key2': 'value2'}

e remover chaves e seus valores associados usando o método pop

In [127]:
d.pop('key1')
d
Out[127]:
{'key2': 'value2'}

Entretanto, existe uma forma melhor de criar um dicionário pré-populado, caso já saibamos quais os elementos que o compõem. Para isso incluímos os pares chave-valor (key:value) entre chaves, separados de outros pares por vírgulas. Para indicar qual chave referencia cada valor, usamos dois pontos (:), seguindo a sintaxe abaixo

{ k1:v1 , k2:v2 , ... , kn:vn }

sendo cada k1,k2, ..., kn chaves; e v1, v2, ..., vn seus valores associados. Vamos criar o dicionário dado como exemplo na figura acima

In [128]:
{ 42: "Olá", 'k1': True, 0: 42, 'k9':42, -3: {'a':1, 'b':2} }
Out[128]:
{42: 'Olá', 'k1': True, 0: 42, 'k9': 42, -3: {'a': 1, 'b': 2}}

Para melhorar a legibilidade, podemos quebrar linhas enquanto criamos os dicionários, separando cada par chave-valor seguindo um nível de indentação

In [129]:
{ 42: "Olá", 
  'k1': True, 
  0: 42, 
  'k9':42, 
  -3: { 'a':1, 
        'b':2 } 
}
Out[129]:
{42: 'Olá', 'k1': True, 0: 42, 'k9': 42, -3: {'a': 1, 'b': 2}}

Podemos também converter uma lista de 2-tuplas em um dicionário, usando a função dict. Neste caso, o primeiro elemento em cada tupla é interpretado como uma chave; e o segundo como seu valor associado

In [130]:
tpls = [
    (42, "Olá"),
    ('k1', True),
    (0, 42),
    ('k9', 42),
    (-3, {'a':1, 'b':2 })
]

dict(tpls)
Out[130]:
{42: 'Olá', 'k1': True, 0: 42, 'k9': 42, -3: {'a': 1, 'b': 2}}

Finalmente, no caso de as chaves de um dicionário serem strings, podemos criar dicionários passando chaves e valores como argumentos nomeados para a função dict. Porém, tenha em mente que neste caso números não podem ser usados como chaves

In [131]:
dict( k1="Olá", k9=42 )
Out[131]:
{'k1': 'Olá', 'k9': 42}

Compreensão de Dicionários (Dict comprehension)

Assim como vimos para listas, o método de compreensão de dicionários, documentado aqui, permite a criação de dicionários usando uma expressão.

Este método usa a mesma sintaxe base da compreensão de listas, com duas distinções: (i) o uso de chaves no lugar de colchetes; e (ii) expressões distintas para o registro da chave e valor em cada iteração, separadas por dois pontos (:):

{ expressão_chave(item):expressão_valor(item) for item in lista if condição(item) }

sendo

  • lista: uma lista pré-existente, ou objeto a ser iterado;
  • item: a variável de iteração;
  • expressão_chave: uma expressão que pode usar o valor em item, a ser incluída como chave;
  • expressão_valor: uma expressão que pode usar o valor em item, a ser incluída como valor;
  • condição (opcional): o resultado da expressão só é incluído na lista final caso a avaliação da condição resulte em True.

Podemos criar, por exemplo, um dicionário cujas chaves são números pares de 1 a 20; e os valores são seus quadrados

In [132]:
{ x:x**2 for x in range(1,21) if x%2==0}
Out[132]:
{2: 4,
 4: 16,
 6: 36,
 8: 64,
 10: 100,
 12: 144,
 14: 196,
 16: 256,
 18: 324,
 20: 400}

De forma análoga, poderíamos criar um dicionário cujas chaves são os quadrados dos números pares de 1 a 20; e os valores são os próprios números

In [133]:
{ x**2:x for x in range(1,21) if x%2==0}
Out[133]:
{4: 2,
 16: 4,
 36: 6,
 64: 8,
 100: 10,
 144: 12,
 196: 14,
 256: 16,
 324: 18,
 400: 20}

Acessando elementos em um dicionário

Podemos acessar elementos de um dicionário usando a notação de indexação. Ela é bem parecida com a notação que usamos para listas e tuplas, com a única diferença de que agora o índice não é mais posicional. Recuperamos um determinado valor usando como índice sua chave

In [134]:
d = {'k1': "val1", 'k2': "val2", 'k3': "val3"}
In [135]:
d['k1']
Out[135]:
'val1'
In [136]:
d['k2']
Out[136]:
'val2'
In [137]:
d['k3']
Out[137]:
'val3'

Podemos também atualizar o valor associado a uma determinada chave usando a notação de indexação

In [138]:
d['k1'] = "val1.2"
In [139]:
d['k2'] = "val2.2"
In [140]:
d
Out[140]:
{'k1': 'val1.2', 'k2': 'val2.2', 'k3': 'val3'}

ou mesmo inserir chaves e valores novos

In [141]:
d['k4'] = "val4"
In [142]:
d['k5'] = "val5"
In [143]:
d
Out[143]:
{'k1': 'val1.2', 'k2': 'val2.2', 'k3': 'val3', 'k4': 'val4', 'k5': 'val5'}

Mas receberemos uma mensagem de erro se tentarmos resgatar um valor usando uma chave inexistente

In [144]:
d['k9']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-144-17f959e6f626> in <module>()
----> 1 d['k9']

KeyError: 'k9'

Para evitar que este erro quebre nosso programa, podemos usar o método get, que retorna um valor pré-definido caso a chave não exista no dicionário. Por default, o valor pré-definido é nulo (None), mas podemos especificar qualquer outro valor, passando como o segundo argumento para a função

In [145]:
d.get('k9')
In [146]:
d.get('k9',"Não encontrado!")
Out[146]:
'Não encontrado!'

Combinando dicionários

Diferentemente de listas, dicionários não podem ser combinados usando o operador +. Em vez disso, podemos atualizar um dicionário com o conteúdo de outro, usando o método update

In [147]:
d1 = {'k1':"val1", 'k2':"val2"}
d2 = {'k3':"val3"}
d3 = {'k4':"val4"}
In [148]:
d1.update(d2)
d1
Out[148]:
{'k1': 'val1', 'k2': 'val2', 'k3': 'val3'}
In [149]:
d1.update(d3)
d1
Out[149]:
{'k1': 'val1', 'k2': 'val2', 'k3': 'val3', 'k4': 'val4'}