3. Listas e dicionários#
3.1. Revisão sobre coleções#
No capítulo anterior, definiu-se o conceito de coleção e mostrou-se 4 dos tipos fundamentais de coleções na linguagem Python: as listas, as strings, os dicionários e os conjuntos.
A função len()
, para calcular o número de elementos de uma coleção, e o
operador in
, para verificar se um elemento está contido numa coleção, podem
ser usados em qualquer coleção.
Nota
Recorde que o operador in
, ao ser usado com dicionários, verifica
se um elemento está entre as chaves do dicionário.
Recorde que o operador in
, ao sr usado com strings, pode verificar se uma substrings
está presente e não apenas uma letra:
if 'AUG' in 'UCCAUGGCCAA':
print('AUG existe na sequência')
Outra característica em comum a todas as coleções é a possibilidade de serem “iteradas”
usando o comando for
.
Nota
Recorde que, usando o comando for
:
Passamos por todos os elementos de uma lista (pela sua ordem)
Passamos pelas chaves de um dicionário.
Passamos pelos caracteres de uma string (pela sua ordem)
Passamos pelos elementos de um conjunto.
Vale a pena relembrar que as listas e os dicionários podem ser criados “em compreensão”:
quads = [n**2 for n in range(1, 11)]
n_letras = {p: len(p) for p in ['isto', 'são', 'várias', 'palavras']}
print(quads)
print(n_letras)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
{'isto': 4, 'são': 3, 'várias': 6, 'palavras': 8}
3.2. Listas#
Neste capítulo mostra-se algumas funções que são específicas de cada coleção, começando pelas listas e dicionários. As funções características das strings serão o tema do próximo capítulo.
Vejamos algumas funções que são específicas das listas.
A referência oficial destas funções pode ser consultada na documentação da linguagem Python.
De uma forma esquemática, algumas das funções membro de listas mais usadas são as seguintes
Note-se que a função .remove()
apenas remove a primeira ocorrência de um elemento numa lista e não todas as ocorrências.
Por outro lado, estas outras funções, já vistas anteriormente, são também muito usadas com listas:
Um primeiro exemplo, ilustrando o uso das funções .count()
e .remove()
:
# Remover todas as ocorrências de um elemento
a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
print(a)
for i in range(a.count(1)):
a.remove(1)
print('\ndepois de remover todos os 1:')
print(a)
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
depois de remover todos os 1:
[2, 3, 4, 2, 3, 4, 2, 3, 4]
Um segundo exemplo, ilustrando o uso da função .count()
(e list()
):
# Contar K e L na sequência de uma proteína
seq = 'ADKHLILTAVGGCWFHVAFWEVEKAGAHKWE'
seqlista = list(seq)
nK = seqlista.count('K')
nL = seqlista.count('L')
print(f'Existem {nK} lisinas e {nL} leucinas na sequência')
print(seq)
Existem 3 lisinas e 2 leucinas na sequência
ADKHLILTAVGGCWFHVAFWEVEKAGAHKWE
No entanto, uma vez que a função .count()
também funciona com strings, o programa anterior pode ser escrito exclusivamente com base em strings:
# Contar K e L na sequência de uma proteína
seq = 'ADKHLILTAVGGCWFHVAFWEVEKAGAHKWE'
nK = seq.count('K')
nL = seq.count('L')
print(f'Existem {nK} lisinas e {nL} leucinas na sequência')
print(seq)
Existem 3 lisinas e 2 leucinas na sequência
ADKHLILTAVGGCWFHVAFWEVEKAGAHKWE
3.3. .append()
, como função geradora de listas#
A função .append()
é uma das mais usadas, já que permite a construção de listas novas num programa, muits vezes com base em iteração com for
, começando a partir de uma lista vazia.
Alguns exemplos:
# Gerar uma lista com os 40 primeiros quadrados perfeitos
a = []
for i in range(1, 41):
a.append(i**2)
print(a)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600]
# Gerar uma lista com os 40 primeiros quadrados perfeitos
# que estejam entre 400 e 800
a = []
for i in range(1, 41):
q = i**2
if q >= 400 and q <= 800:
a.append(q)
print(a)
[400, 441, 484, 529, 576, 625, 676, 729, 784]
# Somar os 10 primeiros números ímpares
ímpares = []
for i in range(10):
ímpares.append(2*i + 1)
print(ímpares)
resultado = sum(ímpares)
print('A soma dos 10 primeiros ímpares é', resultado)
print('Verificação:')
print('Pela soma de prog. aritm.',(1+19)/2*10)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
A soma dos 10 primeiros ímpares é 100
Verificação:
Pela soma de prog. aritm. 100.0
3.3.1. Formas equivalentes de usar .append()
e listas em compreensão#
A utilização da função .append()
para criar uma lista nova é muitas vezes equivalente a usar uma lista em compreensão.
Ilustrando com os dois últimos exemplos:
a = []
for i in range(1, 41):
q = i**2
if q >= 400 and q <= 800:
a.append(q)
print(a)
a = [i**2 for i in range(1, 41) if 400 <= i**2 <= 800]
print(a)
[400, 441, 484, 529, 576, 625, 676, 729, 784]
[400, 441, 484, 529, 576, 625, 676, 729, 784]
Repare-se que os dois print()
dão o mesmo resultado.
ímpares = []
for i in range(10):
ímpares.append(2*i + 1)
resultado = sum(ímpares)
# é equivalente a
resultado = sum([2*i + 1 for i in range(10)])
print('A soma dos 10 primeiros ímpares é', resultado)
print('Verificação, pela soma de prog. aritm.',(1+19)/2*10)
A soma dos 10 primeiros ímpares é 100
Verificação, pela soma de prog. aritm. 100.0
3.4. Indexação com números negativos#
As listas e as string têm uma numeração implícita, (a contar do zero), e podemos
indexar uma lista usando lista[posição]
. Obtemos o elemento que está
numa posição.
Mostrando, com um comentário as posições implícitas dos elementos:
enzimas = ['HK', 'G6PDH', 'TPI', 'Ald', 'PFK', 'PK']
# 0 1 2 3 4 5 len()
print(enzimas[0])
print(enzimas[3])
# para obter o último elemento...
print(enzimas[len(enzimas) -1])
HK
Ald
PK
As listas e as strings têm também uma numeração implícita com números negativos: o último elemento é -1, o penúltimo -2 e assim sucessivamente.
enzimas = ['HK', 'G6PDH', 'TPI', 'Ald', 'PFK', 'PK']
# -4 -3 -2 -1
print(enzimas[-4])
print(enzimas[-6])
print(enzimas[-1])
TPI
HK
PK
Dica
para obter o último elemento de uma lista podemos simplesmente indexar com -1
último = a[-1]
A indexação permite também modificar um elemento que está numa
posição, usando =
como se fossemos atribuír um nome.
No seguinte exemplo modificamos o elemento que está na posição 2:
a = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
print(a)
a[2] = 4 * 2**10 + a[-1]
print(a)
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
[1, 1, 4100, 2, 2, 2, 3, 3, 3, 4, 4, 4]
Podemos aplicar um comando for
não diretamente aos elementos de uma lista
mas sim aos números inteiros , geralmente gerados com range()
, que podem funcionar como posições para indexar a lista.
Vejamos estas duas alternativas:
# Iteração direta dos elementos da lista
a = [1, 2, 3, 2, 1]
for e in a:
print(e)
1
2
3
2
1
Esta é a maneira simples e direta de aplicar comandos (neste caso print()
) a todos os
elementos da lista.
Mas o mesmo pode ser conseguido, de uma forma indireta, passando pelas posições, não pelos próprios elementos, e usando indexação para obter os elementos:
# iteração através das posições dos elementos
a = [1, 2, 3, 2, 1]
for i in range(len(a)):
print(a[i])
1
2
3
2
1
Claro que a forma indireta é mais complicada e são raras as ocasiões onde teremos de a usar.
No entanto, um exemplo:
Problema
Calcular as diferenças sucessivas entre os elementos de uma lista, pondo o resultado numa lista
a = [1, 1, 1, 2, 2, 2, 3, 3, 3, 5, 5, 7]
difs = []
for i in range(1, len(a)):
d = a[i] - a[i-1]
difs.append(d)
print('Lista')
print(a)
print('\nDiferenças sucessivas')
print(difs)
Lista
[1, 1, 1, 2, 2, 2, 3, 3, 3, 5, 5, 7]
Diferenças sucessivas
[0, 0, 1, 0, 0, 1, 0, 0, 2, 0, 2]
Neste exemplo tivemos de usar posições, os índices dos elementos, porque as diferenças sucessivas têm de ser calculadas por \(d_i = a_i - a_{i-1}\) ou, em Python,
d = a[i] - a[i-1]
Repare-se que a range()
tem de começar em 1, uma vez que, se começasse em 0, a primeira
diferença calculada seria d = a[0] - a[-1]
que não faria sentido, uma vez que a[-1]
representa o último elemento da lista.
Exemplo
Mostrar que as diferenças sucessivas entre os quadrados perfeitos, são os números ímpares (usar os 20 primeiros)
#calcular os quadrados perfeitos
quads = []
for i in range(20):
quads.append(i**2)
#calcular as diferenças sucessivas
difs = []
for i in range(1, len(quads)):
d = quads[i] - quads[i-1]
difs.append(d)
print('quadrados perfeitos', quads)
print('\ndiferenças sucessivas', difs)
quadrados perfeitos [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
diferenças sucessivas [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37]
A propriedade matemática anterior foi usada por Galileu no estudo da queda livre dos corpos.
As distâncias sucessivas percorridas durante a queda para a mesma unidade de tempo estão entre si como a sucessão dos números ímpares, o que implica que a distância acumulada cresce segundo o quadrado do tempo decorrido: o movimento de queda livre é uniformemente acelerado.
As listas em compreensão podem ser usadas para abreviar o programa, sem recorrer a listas vazias e .append()
:
quads = [n**2 for n in range(20)]
difs = [quads[i] - quads[i-1] for i in range(1, len(quads))]
print('Quadrados: ', quads)
print('\nDiferenças:', difs)
Quadrados: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
Diferenças: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37]
Uma pequena nota final sobre a utilização de indexação para passar por todos os elementos de uma lista:
Nota
A maneira “indireta” de percorrer uma lista com for
for i in range(len(lista)):
"operações com lista[i]"
é muito típica de outras linguagens de programação. Não na linguagem Python, onde é encorajado o uso da forma direta:
for e in lista:
"operações com e"
Na linguagem Python é quase sempre possível reescrever a forma indireta na forma direta.
Quando os índices dos elementos são mesmo necessários a função enumerate()
,
vista no capítulo anterior, pode muitas vezes substituír a indexação.
Num dos próximos capítulos abordaremos ainda o uso de módulos de computação científica, em
particular os módulos numpy
e pandas
, em que a iteração é substituída por operações “vetoriais”, que, efetivamente, prescindem o uso de comandos for
, quer na sua forma direta quer na sua forma indireta.
3.5. Mais algumas funções de listas#
3.5.1. .reverse()
, .sort()
#
As funções .reverse()
, .sort()
também modificam uma lista, tal como, por exemplo, a função .append()
.
.reverse()
coloca os elementos da lista por uma ordem contrária aquela que é dada.
.sort()
ordena os elemento da lista por ordem alfabética ou por ordem numérica.
a = ['seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom']
print('lista original')
print(a)
a.reverse()
print('\nDepois de a.reverse()')
print(a)
a.sort()
print('\nDepois de a.sort()')
print(a)
lista original
['seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom']
Depois de a.reverse()
['dom', 'sab', 'sex', 'qui', 'qua', 'ter', 'seg']
Depois de a.sort()
['dom', 'qua', 'qui', 'sab', 'seg', 'sex', 'ter']
3.6. Dicionários#
Os dicionários são associações (não ordenadas) entre chaves e valores.
Nota
Num dicionário cada chave é única. Não pode haver chaves repetidas.
3.6.1. Acrescentar e modificar elementos (chave:valor)#
Importante
A maneira de obter, inserir e modificar elementos de um dicionário é através das suas chaves, usando indexação
De certa forma é mais fácil inserir e modificar elementos (pares chave: valor) num dicionário do que numa lista. Basta usar indexação pela chave. O melhor será com um exemplo.
d = {'H':1, 'Li':3, 'Na':11, 'K':19}
print(d)
# inserir um elemento novo (par chave:valor)
d['O'] = 16
print(d)
# modificar um elemento já existente
d['O'] = 18
print(d)
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19}
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19, 'O': 16}
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19, 'O': 18}
Não esquecer que o operador in
procura se existe ou não uma chave
d = {'H':1, 'Li':3, 'Na':11, 'K':19}
if 'N' in d:
print('Existe info sobre o azoto')
else:
print('Não existe info sobre o azoto')
Não existe info sobre o azoto
Não esquecer que o comando for
aplicado a dicionários passa pelas chaves.
Um exemplo, com uma mini tabela periódica:
# elementos cujo símbolo tem 2 letras
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for k in tperiodica:
if len(k) == 2:
print(k)
Li
Na
Mas, percorrendo as chaves, podemos obter os valores correspondentes por indexação:
# elementos com o número atómico superior a 10
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for k in tperiodica:
if tperiodica[k] > 10:
print(k, '--->', tperiodica[k])
Na ---> 11
K ---> 19
O ---> 18
3.6.2. .update()
#
.update()
faz várias modificações de uma só vez: modifica um dicionário
com outro dicionário. Se uma chave já existir, o seu valor é modificado. Se não existir,
é inserido um novo elemento. Vejamos um exemplo:
d = {'a': 1, 'c': 3, 'b': 2}
print('original')
print(d)
novos = {'p': 10, 'q': 15, 'c': 20}
d.update(novos)
print('após update')
print(d)
original
{'a': 1, 'c': 3, 'b': 2}
após update
{'a': 1, 'c': 20, 'b': 2, 'p': 10, 'q': 15}
3.6.3. .clear()
#
Uma função óbvia. (Note-se que já a vimos em listas).
d = {'a': 1, 'c': 3, 'b': 2}
print(d)
d.clear()
print(d)
{'a': 1, 'c': 3, 'b': 2}
{}
3.6.4. Iterações usando .values()
,.items()
, .keys()
#
Apesar da iteração de dicionários ser feita pelas chaves k
quando escrevemos
for k in dicionário:
podemos usar as funções .values()
e .items()
para iterar por um dicionário de outra forma.
A função .values()
faz com que a iteração seja pelos valores e não pelas chaves:
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for v in tperiodica.values():
print(v)
1
3
11
19
18
Mais interessante ainda é a função .items()
. O melhor será ver um exemplo, usando sempre a mini tabela periódica:
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for x in tperiodica.items():
print(x)
('H', 1)
('Li', 3)
('Na', 11)
('K', 19)
('O', 18)
Isto é, o efeito da função .items()
é fazer com que a iteração passe por pares do tipo (chave, valor).
Quando usamos .items()
podemos dar dois nomes à frente do comando for
(e antes do in
).
O resultado é semelhante ao que foi visto no capítulo anterior com a função enumerate()
:
os pares resultantes de .items()
são desdobrados pelos dois nomes, o que significa que
o primeiro nome é atribuído a cada chave e o segundo nome atribuído a cada valor durante a
iteração do dicionário.
Vejamos com exemplo, repare-se no uso de dois nomes, e
e n
.
# elementos com o número atómico superior a 10
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for e, n in tperiodica.items():
if n > 10:
print (e, '--->', n)
Na ---> 11
K ---> 19
O ---> 18
A última versão deste programa (sem .items()
) usava indexação para obter os valores:
# elementos com o número atómico superior a 10
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
for k in tperiodica:
if tperiodica[k] > 10:
print(k, '--->', tperiodica[k])
Na ---> 11
K ---> 19
O ---> 18
Problema
Virar um dicionário “do avesso”, isto é, criar um novo dicionário em que as chaves “trocam” com os respetivos valores.
Nota: é possível repetir valores num dicionário, mas não as chaves. O que significa que num problema destes podem-se “perder” chaves quando se trocam as chaves por valores.
Tentando resolver este problema usando indexação:
# Trocar os elementos da T.P. pelos n. atómicos
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
num2simbs = {}
for k in tperiodica:
num2simbs[tperiodica[k]] = k
print(tperiodica)
print(num2simbs)
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19, 'O': 18}
{1: 'H', 3: 'Li', 11: 'Na', 19: 'K', 18: 'O'}
Confuso? Um pouco.
Usando .items()
tudo fica mais claro:
# Trocar os elementos da T.P. pelos n. atómicos
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
num2simbs = {}
for elem, na in tperiodica.items():
num2simbs[na] = elem
print(tperiodica)
print(num2simbs)
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19, 'O': 18}
{1: 'H', 3: 'Li', 11: 'Na', 19: 'K', 18: 'O'}
Problema
Contar os diferentes valores de um dicionário.
Mais uma vez: as chaves são únicas, os valores não. Por isso faz sentido conta-los
fellowship = {'Aragorn':'Humano',
'Frodo':'Hobbit',
'Sam':'Hobbit',
'Boromir':'Humano',
'Merry':'Hobbit',
'Took':'Hobbit',
'Gandalf':'Feiticeiro',
'Gimli':'Anão',
'Legolas':'Elfo'}
# resultado das contagens num dicionário
# que associa espécie: nº personagens dessa espécie
contagens = {}
for espécie in fellowship.values():
if espécie in contagens:
contagens[espécie] = contagens[espécie] + 1
else:
contagens[espécie] = 1
# apresentação das contagens
for e, c in contagens.items():
print(e, c)
Humano 2
Hobbit 4
Feiticeiro 1
Anão 1
Elfo 1
Confuso? Talvez o uso de um if...else
não seja claro.
O dicionário contagens
, inicialmente vazio, tem como objetivo conter as contagens
na forma de pares espécie:contagem.
A cada iteração dos valores do dicionário fellowship
é obtida a “espécie” que terá de contar mais um
para essa espécie no dicionário contagens
. Mas só pode ser adicionado +1
à chave correspondente a essa espécie
se ela já existir no dicionário. Se não existir, será a primeira contagem e terá o valor 1 pela primeira vez.
O teste if espécie in contagens:
trata deste problema de averiguar se podemos somar ou não a um elemento
que pode existir ou não.
A solução deste problema inclui
a iteração dos valores de um dicionário com
.values()
um teste de inclusão num dicionário com
in
a atribuição de valores novos ou a modificação de um já existente com indexação
a iteração das chaves e valores de um dicionário com desdobramento de nomes com
.items()
Um exemplo a estudar…
Finalmente, algo quase inútil, a função .keys()
pode ser usada para passar pelas chaves de um dicionário.
Inútil porque, se não usarmos a função o for
, por convenção passa pelas chaves de um dicionário:
d = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
# isto poderia ser
# for i in d:
for i in d.keys():
print(i)
H
Li
Na
K
O
# Virar um dicionário "do avesso"
# usando um dicionário em compreensão
tperiodica = {'H':1, 'Li':3, 'Na':11, 'K':19, 'O':18}
num2simbs = {na: elem for elem, na in tperiodica.items()}
print(tperiodica)
print(num2simbs)
{'H': 1, 'Li': 3, 'Na': 11, 'K': 19, 'O': 18}
{1: 'H', 3: 'Li', 11: 'Na', 19: 'K', 18: 'O'}
3.6.5. dict()
#
A função dict()
tenta transformar o seu argumento num dicionário. É análoga à função
list()
que tenta transformar o seu argumento numa lista.
Em particular, a função dict()
pode aceitar uma lista de pares de objetos, interpretando-os como
associações de chaves a valores.
pares = [('Li', 3), ('K', 19), ('O',18)]
d = dict(pares)
print(d)
{'Li': 3, 'K': 19, 'O': 18}
3.7. Função zip()
#
Embora formalmente esta função não seja nem de dicionários nem de listas, o seu uso é frequente na linguagem Python e faz muito sentido introduzi-la aqui.
nomes = ['Enolase (S.cerevisiae)',
'Enolase (S.pombe)',
'Enolase (K.lactis)']
ids = ['P00924', 'P40370', 'Q70CP7']
for x in zip(ids, nomes):
print(x)
('P00924', 'Enolase (S.cerevisiae)')
('P40370', 'Enolase (S.pombe)')
('Q70CP7', 'Enolase (K.lactis)')
Como se pode ver por este exemplo, a função zip()
parece ter gerado pares de objetos a partir de duas
listas. O primeiro par é constituídos pelos dois elementos na posição 0, o segundo pelos dois elementos na
posição 1 e assim sucessivamente até esgotar a lista mais curta.
Como o resultado são pares de valores, podemos desdobrar nomes, tal como fizemos com a função enumerate()
ou com a função .items()
:
nomes = ['Enolase (S.cerevisiae)',
'Enolase (S.pombe)',
'Enolase (K.lactis)']
ids = ['P00924', 'P40370', 'Q70CP7']
for n, i in zip(nomes, ids):
print(i, ':', n)
P00924 : Enolase (S.cerevisiae)
P40370 : Enolase (S.pombe)
Q70CP7 : Enolase (K.lactis)
Com este exemplo (nomes de proteínas e seus Uniprot accessions que estão em listas diferentes) podemos criar uma tabela a partir das duas listas, representada como um dicionário.
Começando por usar um dicionário em compreensão combinado com zip()
:
nomes = ['Enolase (S.cerevisiae)',
'Enolase (S.pombe)',
'Enolase (K.lactis)']
ids = ['P00924', 'P40370', 'Q70CP7']
d = {n: i for i, n in zip(nomes, ids)}
print(d)
{'P00924': 'Enolase (S.cerevisiae)', 'P40370': 'Enolase (S.pombe)', 'Q70CP7': 'Enolase (K.lactis)'}
Combinando a função zip()
com a função dict()
, a criação do
dicionário fica ainda mais sucinta:
nomes = ['Enolase (S.cerevisiae)',
'Enolase (S.pombe)',
'Enolase (K.lactis)']
ids = ['P00924', 'P40370', 'Q70CP7']
d = dict(zip(nomes, ids))
print(d)
{'Enolase (S.cerevisiae)': 'P00924', 'Enolase (S.pombe)': 'P40370', 'Enolase (K.lactis)': 'Q70CP7'}
Como é que isto resultou?
zip()
gera pares de objetos vindos das duas listas. dict()
aceita uma lista de pares de valores como argumento e tenta interpreta-los como chave:valor. E é tudo muito sucinto:
dict(zip(nomes, ids))
juntem-se ordenadamente os elementos de cada lista aos pares e construa-se um dicionário a partir desses pares