Desvendando as Listas em Python
Seu Alicerce para Análise e Ciência de Dados
Acessar itens de uma lista
Olá caro amigo, no primeiro artigo a respeito de listas vimos diferentes métodos para criar listas, atribuição, método construtor e à partir de outras estruturas de dados. Neste artigo pretendo discorrer a respeito de algumas características das listas como acessibilidade de seus elementos e a mutabilidade das listas.
Elementos Acessíveis
A acessibilidade dos elementos das listas se deve à sua propriedade de serem indexáveis. Isso significa que cada elemento possui uma posição única, chamada índice, que se inicia em 0 para o primeiro elemento, permitindo o acesso direto a eles.
Sintaxe:
nome_da_lista[índice]
Exemplo:
idades = [15, 25, 18, 35, 12]
Acessar o 3 item:
print(idades[2])
# Saída: 18
Calma, mencionei o terceiro elemento, mas o índice entre colchetes foi 2. Por quê?
Como expliquei ao falar a respeito de indexação das listas, os índices começam em 0, assim é o índice do primeiro elemento. assim, para acessar o quarto elemento, o índice será sempre 4 - 1. Portanto, para acessar o elemento na posição n de uma lista, o índice correspondente será sempre n - 1.
Vamos usar o conceito matemático de PA (Progressão Aritmética) para ilustrar a relação entre posição e índice. Em uma PA o n-ésimo termo é o de posição exatamente igual a n. Veja por exemplo a PA dos quadrados:
PA(quadrados) = {0, 1, 4, 9, 16, 25, 36, 49...}
Se perguntarmos qual é o 4º termo da PA, qual termo você destacaria? O elemento 9, não é isso? Pois em PA n representa "posição ordinal" (1º, 2º, 3º , etc.). Em Python e diversas outras linguagens de programação, o 1º elemento possui índice 0, o 3º elemento índice 2, seguindo sempre a lógica: posição (n) - 1. Veja o código abaixo relacionando PA e listas.
pa_lista = [1, 3, 5, 7, 9]
Saída:
# PA dos 5 primeiros números ímpares
# O 3º termo da PA é 5, sua posição é 3.
# para acessar o 3º termo da lista, que é 5, na pa_lista fazemos:
terceiro_termo_pa = pa_lista[2] # O índice para acessar o 3º termo é 2 (3 - 1)
print(f"O 3º termo da PA tem posição (índice 2) na lista, que é: {terceiro_termo_pa}")
A compreensão da indexação de listas é fundamental, pois esse conceito de o primeiro elemento ter índice 0 é comum a diversas estruturas de dados.
Acesso a elementos da lista a partir de seu final
Em Python existe a possibilidade de acessar seu último elemento sem a necessidade de saber seu índice exato. Essa funcionalidade é extremamente útil, especialmente ao trabalhar com strings (que também são indexáveis). Veja como fazer isso na lista de idades que já estávamos trabalhando. Ao olhar para a lista é fácil saber que o último elemento tem valor: 12, como se trata de uma lista pequena, apenas com 5 elementos fica simples entender que o último elemento tem índice 4. Mas imagina que você tem uma planilha e queria pegar o último item de uma coluna. Você nem sabe quantas linhas tem essa planilha. O que fazer?
No primeiro artigo falamos sobre o método len() que retorna o tamanho (quantidade de elementos) de uma lista, Basta então utilizar len(lista) - 1 para obter o índice do último elemento.
indice_ultimo_elemento = len(idades) - 1
print(f"Último elemento lista idades: {idades[indice_ultimo_elemento]}")
Que maravilha, acessamos o último elemento e só precisamos de duas linhas de código para isso. Mas nós já sabemos que python é a linguagem que visa facilitar a vida dos analistas de dados, então com certeza temos uma forma mais simples de acessar o último item de uma lista. Este método é usar índices negativos para acessar os elementos da lista de trás para frente.
print(f"Último elemento da lista: {idade[-1]}")
Essa é uma forma simples e sucinta, que dispensa o conhecimento do tamanho da lista. O índice -1 sempre se refere ao último elemento, -2 ao penúltimo, e assim por diante. Então com isso pode-se acessar o último, o penúltimo, o antepenúltimo e assim por diante, para isso basta usar os índices negativas: -1, -2, -3...
Qual é o problema de acessar elementos de uma lista utilizando seus índices, seja positivo, seja negativo? Ao tentar acessar um índice fora dos limites da lista, o Python levanta um erro do tipo IndexError: list index out of range, interrompendo a execução do script. Por exemplo, imagina que você tente acessar o elementos idades[10] ou o elemento idades[-6], estes elementos não existem na lista e o IndexError será levantado, seu script será interrompido. Como evitar esse problema? A solução é garantir que o índice a ser acessado esteja dentro dos limites válidos da lista. Lembre-se: o maior índice acessível é sempre o tamanho da lista menos 1 (tamanho - 1). Em uma lista de 5 elementos, o maior índice é 4. Então garantiremos o funcionamento do script com a verificação do índice menor que len(lista).
indice_para_acessar = 7
if -len(idades) <= indice_para_acessar < len(idades):
elemento = idades[indice_para_acessar]
print(f"O elemento no indide: {indice_para_acessar} é: {elemento}")
else:
print(f"Erro: índice {indice_para_acessar} está fora dos limites da lista (tamano: {len(idades)})")
O intervalo de índices válidos é −tamanho_da_lista ≤ índice < tamanho_da_lista. Como isso funciona? Para acessar elementos com índices positivos, os valores válidos são 0,1,2,3,…,tamanho_da_lista−1 Para índices negativos, os valores válidos são −tamanho_da_lista,…,−4,−3,−2,−1. Note que não existe o índice -0; nos índices negativos, começamos de -1 (o último elemento). Além disso, os índices representam deslocamentos a partir do início ou do fim da lista, e o índice 0 já está definido para o primeiro elemento.
| 0 1 2 3 4 5 |
+-------------------+
| 5 7 9 11 13 15 |
+-------------------+
|-6 -5 -4 -3 -2 -1 |
Assim estão ordenos os índices para a lista em python.
observação: Também serve para strings.
Sublistas em listas.
O acesso a subconjuntos de listas é conhecido como fatiamento (slicing). Em Python, podemos acessar partes de uma lista utilizando a notação de dois pontos (:).
Para obter os 3 primeiros elementos da lista idades, usamos: idades[0:3].
Para obter os 2 últimos elementos da lista idades (considerando o exemplo atual), usaríamos: idades[3:5]
Como funciona isso?
Para fatiar a lista nós temos 2 parâmetros o s (start) e o e(end), assim acessar pedaço da lista fica: lista[s:e].
Python itera pela lista a partir do índice start até que o índice seja menor que end. Como seria isso na prática?
start = 1
end = 5
numeros = range(start, end)
for i in numeros:
print(i)
# Saída: 1, 2, 3, 4
Percebeu que o número 5 não foi incluído? Isso ocorre por que o loop for itera sobre os valores gerados por range(start, end), onde a variável icada valor sequenciadamente até o limite definido. O comportamento da função range(start, end) é o seguinte:
| item | i | valor de i | Teste Lógico | Resultado do Teste Lógico |valor retornado (índice)
| 0 | i=s | 1 | i = start | True | 1
| 2 | i+1 | 2 | i < end? | True | 2
| 3 | i+1 | 3 | i < end? | True | 3
| 4 | i+1 | 4 | i < end? | True | 4
| 5 | i+1 | 5 | i < end? | False | Sai do Loop pois i é igual a end.
Portanto, o valor especificado como end no fatiamento (e na função range) representa o limite superior não inclusivo. A iteração para antes de alcançar esse índice. Assim, para acessar todos os elementos até o final da lista usando o fatiamento, podemos omitir o valor de end ou usar o tamanho da lista como valor.
Agora que já entendemos que no método de acesso por fatiamento o primeiro valor é o start e o segundo valor é o end, valor que a iteração para antes dele (lembrando que start e end são indices), podemos refinar ainda mais o fatiamento, especificando um terceiro parâmetro: o passo (step), que define o intervalo entre os elementos selecionados.
Por exemplo:
Vamos supor que queremos pegar o 1º, 3º e o 5º elemento da lista idades, então determinamos um passo de iteração, lembra na tabela anterior estava fazendo i + 1? Então, o passo da iteração era 1, agora queremos fazer a iteração com passos mais largos, passo 2, isto é: i + 2.
sintaxe:
lista[start: end : passo]
no primeiro exemplo o passo era 1, por isso não temos a necessidade de colocar, mas se queremos pular os elementos de 2 em 2
Como ficaria a tabela?
| item | i | valor de i | Teste Lógico | Resultado do Teste Lógico |valor retornado (índice)
| 0 | i=s | 1 | i = start | True | 1
| 2 | i+2 | 3 | i < end? | True | 3
| 3 | i+2 | 5 | i < end? | False | Sai do Loop pois i é igual a end ou fora dos limites da lista
Estranho, não era para começarmos pelo primeiro elemento? A saída mostra apenas os índices 1 e 3. Exatamente Para incluir o primeiro elemento, precisamos definir start = 0. A tabela correspondente seria:
| item | i | valor de i | Teste Lógico | Resultado |valor retornado (índice)
| 0 | i=s | 0 | i = start | True | 0
| 2 | i+2 | 2 | i < end? | True | 2
| 3 | i+2 | 4 | i < end? | True | 4
| 4 | i+2 | 6 | i < end? | False | Sai do Loop pois i é maior a end ou fora dos limites da lista
Agora o loop retorna os índices: 0, 2, 4. Se acessarmos esses índices em nossa lista idades teremos:
idades[0] # 1º Elemento
idades[2] # 3º Elemento
idades[4] # 5º Elemento
A sintaxe para obter os elementos nos índices 0, 2 e 4 da lista idades utilizando o fatiamento seria: idades[0 : 5 : 2].
start é 0 e end é 5.
E se especificarmos um valor de end muito maior que o tamanho da lista?
start = 0
end = 15
idades[0 : 15: 2]
A saída permanecerá a mesma e não ocorrerá erro. Isso porque, ao encontrar um índice maior que o limite da lista, o fatiamento simplesmente para, ignorando o restante do intervalo especificado.
Obtendo índices a partir de valores.
Agora, vamos explorar como obter o índice de um elemento em uma lista a partir do seu valor. Suponha que temos uma idade específica e queremos descobrir sua posição (índice) na lista idades.
Uma forma de fazer isso é iterando pela lista.
idade_procurada = 12
indice = -100
for i, idade in enumerate(idades):
if idade == idade_procurada:
indice = i
if indice >= 0:
print(indice)
else:
print('Idade não está na lista.')
Este exemplo não é eficiente e também não é a melhor abordagem para obter o índice de um elemento da lista. A classe list tem um método específico para realizar este trabalho, o método .index(). Este método recebe um argumento que é o valor procurado. Caso o valor procurado não exista na lista, então um Value Error é levantado e o script é interrompido.
indice_idade = idades.index(12) # 2
novo_indice = idades.index(2) # ValueError: 2 is not in list
Existem duas abordagens comuns para lidar com essa potencial falha:
1 - Verificar se a idade está na lista idades.
idade = 12
if idade in idades:
indice = idades.index(idade)
print(f"{idade} está na posicao {indice} na lista idades.")
else:
print(f"A idade buscada não está na lista idades")
Esta abordagem resolve o problema do elemento não existir na lista, e é simples de entender. No entanto, em listas muito grandes, essa verificação prévia (in) pode ter um impacto no desempenho. Isso nos leva a outra solução para lidar com a possível ausência do elemento, considerada mais "pythônica
try:
indice_idade = idades.index(idade)
print(f"A idade {idade} está no índice {indice_idade} da lista.")
except ValueError:
print(f"Erro: A idade {idade} não está na lista")
A vantagem deste método é que ele se mostra potencialmente mais eficiente. Entretanto não é tão intuitivo, pois a lógica de o valor não existir na lista está no except.
Agora que já sabemos como acessar valores por meio de índices, podemos começar a editar as listas, alterando seus valores. Para isso, utilizamos a atribuição direta de um valor ao índice desejado.
idade[2] = 18
Neste caso, estamos alterando o item que está no índice 2 da lista. Se tentarmos adicionar o valor 17 em um índice fora do limite da lista (por exemplo, índice 5 em uma lista de 5 elementos), um IndexError será levantado, pois os índices válidos vão de 0 até 4.
Para adicionar um elemento ao final da lista, utilizamos o método .append()
idades.append(17). Este método retorna None, mas modifica a lista idades adicionando o valor 17 ao final. Ao executar print(idades), a alteração será visível.
O método .append() também permite adicionar outras estruturas de dados (como outra lista) como um único elemento ao final da lista.
novas_idades = [65, 70, 75]
idades.append(novas_idades)
print(idades)
# Saída:
# [15, 30, 12, 25, 28, 17, [65, 70, 75]]
Portanto, o método .append() adiciona a estrutura de dados fornecida como um único elemento ao final da lista. No entanto, para adicionar os elementos de uma estrutura de dados (como uma lista) individualmente ao final da lista original, utilizamos o método .extend(). Este método itera sobre a estrutura de dados fornecida e adiciona cada um de seus elementos individualmente ao final da lista. Vamos adicionar os elementos da lista novas_idades à lista idades usando .extend()
idades.extend(novas_idades)
print(idades)
# Saída: [15, 30, 12, 25, 28, 17, [65, 70, 75], 65, 70, 75]
Nos exemplos de .append() e .extend(), os novos elementos são adicionados ao final da lista original. Entretanto, podemos inserir elementos em uma posição específica da lista, utilizando o método .insert().
idades.insert(1, novas_idades)
# Saída: [15, [65, 70, 75], 30, 12, 25, 28, 17, [65, 70, 75], 65, 70, 75]
De fato, as listas são mutáveis: podemos alterar valores existentes, inserir novos elementos em posições específicas (sejam eles individuais ou estruturas de dados). A remoção de elementos da lista será o tema da nossa próxima seção.