Python3 05 – Sequências: Listas

Este artigo é a parte 5 de 10 na série Python3

Das estruturas de dados em Python, a mais empregada por novos programadores é, de longe, a lista. Isto se deve, principalmente, a sua semelhança funcional às estruturas de dados mais tradicionais encontradas em outras linguagens, como os vetores. No entanto, as listas em Python são bem mais versáteis que as tradicionais estruturas de vetores e matrizes, como será mostrado neste texto.

1. Listas

A lista é uma estrutura de dados sequencial bem parecida com a tupla, mas com um “pequena” diferença: seus elementos são mutáveis. O fato de serem mutáveis adiciona várias outras possibilidades à lista, daí o motivo do “pequena”. Listas podem ser iniciadas por colchetes ou pelo comando list(), passando um iterável qualquer para retornar os elementos para a lista:

[xterm color='true' py='true']
>>> A = [4, "pássaro", 2.14, 5j]
>>> B = list((4, "pássaro", 2.14, 5j))
>>> a = (4, "pássaro", 2.14, 5j)
>>> C = list(a)
>>> A == B == C
True
[/xterm]

 

1. Listas

A lista é uma estrutura de dados sequencial bem parecida com a tupla, mas com um “pequena” diferença: seus elementos são mutáveis. O fato de serem mutáveis adiciona várias outras possibilidades à lista, daí o motivo do “pequena”. Listas podem ser iniciadas por colchetes ou pelo comando list(), passando um iterável qualquer para retornar os elementos para a lista:

[xterm color=’true’ py=’true’]
>>> A = [4, “pássaro”, 2.14, 5j]
>>> B = list((4, “pássaro”, 2.14, 5j))
>>> a = (4, “pássaro”, 2.14, 5j)
>>> C = list(a)
>>> A == B == C
True
[/xterm]

É mais comum o uso dos colchetes para criar uma lista (conforme a definição de A na primeira linha de comando), pela sua simplicidade. No entanto, todas as três variáveis A, B e C são definições de listas idênticas, ou seja, possuem os mesmos elementos na mesma ordem.

Na definição da lista C, observe que foi empregada uma tupla, predefinida, a. Embora tenha definido uma lista com o comando list() passando uma tupla como argumento, o mesmo seria possível passando qualquer iterável como argumento. Veja as próximas instruções:

[xterm color=’true’ py=’true’]
>>> D = list(range(5))
>>> D
[0, 1, 2, 3, 4]
>>> F = list(A)
>>> G = tuple(A)
>>> A == B == C == F
True
>>> a == G
True
>>> G == A
False
>>> h = iter(A)
>>> h
<listiterator object at 0x7f07bd62a7d0>
>>> H = list(h)
>>> H
[4, “pássaro”, 2.14, 5j]
[/xterm]

A variável D é iniciada com o iterável gerado pelo comando range(5). Vou explorar iteráveis mais adiante, mas por agora considere que o comando range(5) apenas gera uma sequência imutável de números com os 5 inteiros de 0 a 4.

Na sequência, F é uma lista criada passando a lista A como argumento ao comando list(), enquanto G é uma tupla criada passando a lista A como argumento ao comando tuple().

Para terminar, é criado um iterável, h, com o comando iter(), utilizando como argumento a lista A. Este iterável é empregado para criar a lista H.

Para completar, observe que todas as listas criadas são iguais:

[xterm color=’true’ py=’true’]
>>> A == B == C == F == H
True
>>> A is B
False
>>> A is H
False
>>> J = A
>>> A is J
True
[/xterm]

Mas não são as mesmas listas, ou seja, possuem os mesmo elementos mas são alocações independentes na memória. O comparador is retorna verdadeiro caso os identificadores apontem para o mesmo objeto na memória. Isto ocorre na declaração da variável J, cujo identificador simplesmente aponta para a mesma lista apontada por A.

Como dito anteriormente, a grande diferença entre listas e tuplas é que os elementos de uma lista são mutáveis:

[xterm color=’true’ py=’true’]
>>> A[3] = ‘albert’
>>> A
[4, “pássaro”, 2.14, ‘albert’]
>>> A == B
False
[/xterm]

Outro fato curioso é que a lista J permanece idêntica a A:

[xterm color=’true’ py=’true’]
>>> A[3] = 2 + 5j
>>> A
[4, “pássaro”, 2.14, (2+5j)]
>>> A == B
False
>>> J
[4, “pássaro”, 2.14, (2+5j)]
[/xterm]

Isto se deve ao fato que J é apenas um apontamento para o objeto lista apontado por A, enquanto que as demais listas são apontamentos para locações diferentes na memória. Lembre-se também de que A, uma lista, é um mutável, daí o comportamento diferente da alocação de variáveis imutáveis como inteiros, complexos, strings e tuplas discutidas no texto Python3 03 – Outras Estruturas de Dados, seção “Imutável e Referência”.

Para passar uma cópia de uma lista para outra, deve ser usado o comando list(), como feito para as variáveis B, C, E, … ou empregar slices como abaixo:

[xterm color=’true’ py=’true’]
>>> J = A[:]
>>> A[2] = ‘santos’
>>> A
[4, ‘pássaro’, ‘santos’, (2+5j)]
>>> A == J
False
>>> J
[4, ‘pássaro’, 2.14, (2+5j)]
[/xterm]

Slices é a forma “Pythônica” de se passar uma cópia de uma lista.

1.1. Uma Lista de Listas é uma Matriz

O Python não possui uma estrutura específica para matrizes, mas estas são facilmente construídas como uma lista cujo seus elementos são listas, como no exemplo abaixo:

[xterm color=’true’ py=’true’]
>>> Mat = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> Mat[0]
[1, 2, 3]
>>> Mat[0][2]
3
>>> Mat[1][0]
4
>>> Mat[2][0]
7
[/xterm]

O que temos aqui é uma lista Mat com três elementos, sendo o primeiro a lista [1, 2, 3], o segundo a lista [4, 5, 6] e o terceiro a lista [7, 8, 9]. Portanto, quando é acessado o elemento zero de Mat (Mat[0]), estamos acessando a primeira lista [1, 2, 3] e a consulta ao elemento Mat[0][2] é uma consulta ao elemento [0] da lista Mat, seguido de uma consulta ao elementos [2] da lista [1, 2, 3]

Considere a lista [1, 2, 3] sendo triplicada para criar uma matriz 3×3:

[xterm color=’true’ py=’true’]
>>> MatB = [[1,2,3]]*3
>>> MatB
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
[/xterm]

Agora tente alterar alguns elementos desta matriz e observe o resultado:

[xterm color=’true’ py=’true’]
>>> MatB[0][2] = 8
>>> MatB
[[1, 2, 8], [1, 2, 8], [1, 2, 8]]
>>> MatB[1][1] = 5
>>> MatB
[[1, 5, 8], [1, 5, 8], [1, 5, 8]]
[/xterm]

Aparentemente, a matriz não está funcionando como esperado. Mas porque isto está acontecendo? O problema vem da forma com a matriz foi gerada. Ao se multiplicar por 3, cada um dos três elementos da lista MatB recebeu a mesma lista [1, 2, 3], e não uma cópia dela. Existem várias formas de contornar isso, veja algumas abaixo:

[xterm color=’true’ py=’true’]
>>> L = [1,2,3]
>>> MatC = [L[:], L[:], L[:]]
>>> MatC
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> MatC[1][1] = 8
>>> MatC
[[1, 2, 3], [1, 8, 3], [1, 2, 3]]
[/xterm]

o que não é muito prático e

[xterm color=’true’ py=’true’]
>>> MatC = [L[:] for i in range(3)]
>>> MatC
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> MatC[1][1] = 8
>>> MatC
[[1, 2, 3], [1, 8, 3], [1, 2, 3]]
[/xterm]

Usando List Comprehensions, um gerador de listas. List Comprehensions será apresentado na seção 1.3., logo adiante. Por agora, uma discussão sobre alguns métodos e atributos das listas.

1.2. Métodos de Lista

Listas aceitam alguns métodos a mais que as tuplas. Novamente, o comando dir() é um bom ponto de partida para conhecer os atributos e métodos de um objeto:

[xterm color=’true’ py=’true’]
>>> dir(A)
[‘__add__’, … , ‘append’, ‘clear’, ‘copy’, ‘count’,
‘extend’, ‘index’, ‘insert’, ‘pop’, ‘remove’, ‘reverse’,
‘sort’]
[/xterm]

Intencionalmente, omiti alguns operadores, atributos e métodos internos da classe lista, fazendo comentários sobre alguns mais adiante. Segue uma breve descrição dos métodos da classe lista:

    • append(Valor) – adiciona um elemento (Valor) ao final da lista;

[xterm color=’true’ py=’true’]
>>> A = [2, 9]
>>> A.append(3)
>>> A
[2, 9, 3]
[/xterm]

    • clear() – limpa todo o conteúdo da lista;

[xterm color=’true’ py=’true’]
>>> A.clear()
>>> A
[]
[/xterm]

    • copy() – método padrão para retornar uma cópia da lista;

[xterm color=’true’ py=’true’]
>>> A = [2, 9, 3]
>>> B = A.copy()
>>> B
[2, 9, 3]
>>> B[1] = ‘a’
>>> A
[2, 9, 3]
>>> B
[2, ‘a’, 3]
[/xterm]

observe que é o mesmo que passar o slice [:], feito anteriormente.

    • count(Valor) – retorna o número de ocorrências de Valor na lista;

[xterm color=’true’ py=’true’]
>>> A += B
>>> A
[2, 9, 3, 2, ‘a’, 3]
>>> A.count(3)
2
>>> A.count(9)
1
[/xterm]

    • extend(Iterável) – estende a lista com os elementos do Iterável.

      [xterm color=’true’ py=’true’]
      >>> C = [‘u’, ‘i’, ‘e’, ‘o’]
      >>> A.extend(C)
      >>> A
      [2, 9, 3, 2, ‘a’, 3, ‘u’, ‘i’, ‘e’, ‘o’]
      [/xterm]

      Observe que foi o mesmo efeito do operador adição empregado no quadro anterior, com a linha A += B;

    • index(Valor, [Início, [Fim]]) – retorna o índice do elemento Valor na lista. Início e Fim podem ser usados para fatiar o intervalo de pesquisa, como em uma tupla;

[xterm color=’true’ py=’true’]
>>> A.index(3)
2
>>> A.index(3, 3)
5
[/xterm]

    • insert(índice, objeto) – insere um objeto na posição de índice passado;

[xterm color=’true’ py=’true’]
>>> A.insert(5, ‘f’)
>>> A
[2, 9, 3, 2, ‘a’, ‘f’, 3, ‘u’, ‘i’, ‘e’, ‘o’]
>>> A.index(3, 3)
6
[/xterm]

    • pop([índice]) – retorna o último elemento da lista, se o índice não for passado, e o remove da lista. O pop é o equivalente ao pop usado em estruturas de pilhas. O push seria o append, descrito acima;

[xterm color=’true’ py=’true’]
>>> A.pop()
‘o’
>>> A.pop(5)
‘f’
>>> A
[2, 9, 3, 2, ‘a’, 3, ‘u’, ‘i’, ‘e’]
[/xterm]

    • remove(Valor) – remove a primeira ocorrência de Valor da lista;

[xterm color=’true’ py=’true’]
>>> A.remove(3)
>>> A
[2, 9, 2, ‘a’, 3, ‘u’, ‘i’, ‘e’]
[/xterm]

    • reverse() – reverte a ordem dos elementos da lista;

[xterm color=’true’ py=’true’]
>>> A.reverse()
>>> A
[‘e’, ‘i’, ‘u’, 3, ‘a’, 2, 9, 2]
[/xterm]

    • sort() – ordena os elementos da lista.

[xterm color=’true’ py=’true’]
>>> A.pop(3)
‘a’
>>> A
[2, 9, 2, 3, ‘u’, ‘i’, ‘e’]
>>> C = A[:4]
>>> C
[2, 9, 2, 3]
>>> C.sort()
>>> C
[2, 2, 3, 9]
[/xterm]

Observe que um sort() somente funciona em uma lista de elementos do mesmo tipo, uma vez que o operador ‘<‘ somente funciona entre elementos de mesmo tipo. Por isto, um sort() em A retornaria um TypeError. Para poder usar a ordenação, foi necessário isolar os inteiros da lista em C.

Agora, um breve passeio por alguns operadores:

    • __add__ – O operador adição (+) essencialmente implementa o método extent():

      [xterm color=’true’ py=’true’]
      >>> A = [1, 2, 3]
      >>> B = [4, 5, 6]
      >>> C = A + B
      >>> C
      [1, 2, 3, 4, 5, 6]
      >>> D = A[:]
      >>> D.extend(B)
      >>> D
      [1, 2, 3, 4, 5, 6]
      >>> A += [8]
      >>> A += [5, 6, 7]
      >>> A
      [1, 2, 3, 8, 5, 6, 7]
      [/xterm]

      Observe que o operador adição somente funciona entre listas. Portanto, uma operação do tipo A += 8 irá gerar um TypeError, já que 8 é um inteiro, daí a necessidade dos colchetes, [8].

    • __mul__/__rmul__ – Já o operador multiplicação clona a lista um inteiro de vezes:

      [xterm color=’true’ py=’true’]
      >>> D = [‘-‘]*3
      >>> D
      [‘-‘, ‘-‘, ‘-‘]
      >>> D *= 2
      >>> D
      [‘-‘, ‘-‘, ‘-‘, ‘-‘, ‘-‘, ‘-‘]
      [/xterm]

      Listas suportam apenas a multiplicação por inteiros.

    • __contains__ – Tem ainda o operador in, que é implementado pelo método __contains__, que é bem útil para saber se um elemento, ou uma lista, está contida em outra:
      [xterm color=’true’ py=’true’]
      >>> B
      [4, 5, 6]
      >>> 5 in B
      True
      >>> C
      [1, 2, 3, 4, 5, 6]
      >>> B in C
      False
      [/xterm]

       

      Embora os elementos de B estejam contidos em C, a lista B não está contida como um elemento de C. O operador in verifica se o elemento de B é igual a algum dos elementos de C. Para retornar True, C deve conter algum elemento igual a B, como segue:

[xterm color=’true’ py=’true’]
>>> C.insert(4, B)
>>> C
[1, 2, 3, 4, [4, 5, 6], 5, 6]
>>> B in C
True
[/xterm]

    • __eq__ (==),__ge__ (≥), __gt__ (>), __le__ (≤), __lt__ (<), __ne__ (!=) – comparadores. Comparação entre listas é feita termo a termo, respeitando o fato de que os elementos comparados devem ser do mesmo tipo, ou suportem a comparação:

      [xterm color=’true’ py=’true’]
      >>> A = [2, 9, 3, 4]
      >>> B = [1, 8, 2, 0]
      >>> A > B
      True
      >>> B.sort()
      >>> B.reverse()
      >>> A > B
      False
      >>> B
      [8, 2, 1, 0]
      [/xterm]

      Embora seja possível implementar a comparação entre tipos diferentes, esta não é a regra. A primeira comparação entre A e B retorna True, pois o primeiro elemento de A é maior que o primeiro elemento de B (A[0]=2 > B[0]=1). Ao ordenar B e inverter seus elementos, a mesma comparação retorna False, pois desta vez o primeiro elemento de A é menor que o primeiro elemento de B (A[0]=2 < B[0]=8).

      [xterm color=’true’ py=’true’]
      >>> C = A[:] + [6]
      >>> C
      [2, 9, 3, 4, 6]
      >>> A <= C True >>> A > C
      False
      >>> A == C
      False
      >>> A != C
      True
      >>> A == C[:-1]
      True
      >>> D = A[:]
      >>> A.reverse()
      >>> A
      [4, 12, 9, 2]
      >>> D
      [2, 9, 12, 4]
      >>> A == D
      False
      [/xterm]

      Na comparação A == C[:-1], foi removido o último elemento de C da comparação, através do slice C[:-1] e, portanto, as duas se tornaram iguais, elemento a elemento.

      Apenas para enfatizar, embora A e D possuam os mesmos elementos em ordem diferente, uma comparação de igualdade entre eles retorna False, uma vez que a comparação é feita elemento a elemento.

    • __getitem__/__setitem__ – estas são as duas implementações para retornar um item da lista e alterar um item da lista:

      [xterm color=’true’ py=’true’]
      >>> A[2]
      9
      >>> A[2] = 12
      >>> A
      [4, 12, 12, 2]
      [/xterm]

      os dois comandos acima ilustram o __getitem__, também existente em tuplas, e o __setitem__, respectivamente.

    • __len__ – esta implementação é para retornar o comprimento da lista.

[xterm color=’true’ py=’true’]
>>> len(A)
4
>>> A.pop()
2
>>> A.__len__()
3
[/xterm]

    • __repr__ e __str__ – o __str__ serve para exibir o objeto para usuário final, essencialmente o resultado obtido com o comando print() e pela função str(). Já o __repr__ serve para exibir o objeto para o programador, usada pelo console do Python e pela função repr(). Essencialmente, ambos são bem parecidos e não é pouco comum a definição de um ser apenas um link para a definição do outro.

[xterm color=’true’ py=’true’]
>>> str(A)
‘[4, 12, 12]’
>>> A.__str__()
‘[4, 12, 12]’
>>> z = 2 + 5j
>>> str(z)
‘(2+5j)’
>>> repr(z)
‘(2+5j)’
[/xterm]

    • __sizeof__ – retorna o tamanho em bytes do objeto na memória.

[xterm color=’true’ py=’true’]
>>> A.__sizeof__()
72
>>> z.__sizeof__()
32
[/xterm]

Outros atributos como ‘__class__’, ‘__delattr__’, ‘__delitem__’, ‘__format__’, ‘__getattribute__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’ e ‘__subclasshook__’, específicos de orientação a objetos, pretendo abordar em um texto sobre o assunto mais adiante.

1.3. List Comprehensions

Tentei encontrar uma tradução sensata para Comprehensions, mas confesso que não encontrei. Muito provavelmente não procurei nos lugares corretos, mas vou usar o termo inglês aqui. List/Set Comprehensions é uma forma curta e divertida de se criar lista simples e mesmo algumas bem elaboradas de índices. Sua sintaxe é simples, mas as possibilidades são enormes.

List Comprehensions é uma expressão em um loop e, eventualmente, com alguma condição, tudo fechado entre colchetes. Suas sintaxes segue o padrões são:

[ &lt;expressão&gt; for  in &lt;iterável&gt; ]
[ &lt;expressão&gt; for  in &lt;iterável&gt; if &lt;condição&gt; ]

A primeira sintaxe é a forma mais simples, apenas um laço de repetição, enquanto que a segunda adiciona um condicional if. Segue alguns exemplos simples:

[xterm color=’true’ py=’true’]
>>> [ x**2 for x in range(10) ]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[/xterm]

Gera uma lista com os quadrados dos primeiros 10 inteiros, incluindo o zero.

[xterm color=’true’ py=’true’]
>>> [ x for x in range(10) if x % 2 == 0 ]
[0, 2, 4, 6, 8]
>>> [ x for x in range(10) if x % 2 == 1 ]
[1, 3, 5, 7, 9]
[/xterm]

Retorna os pares e os ímpares de 0 a 9. O operador % retorna o resto da divisão. Estas duas últimas listas poderiam ser criadas com um comando list(range(0,10,2)) e list(range(1,10,2)), mas veja uns exemplos mais elaborados:

[xterm color=’true’ py=’true’]
>>> N = 12
>>> [ x for x in range(2, N) if N % x == 0 ]
[2, 3, 4, 6]
[/xterm]

Esta lista retorna os divisores inteiros de 12, passado pelo identificador N. O código a seguir ilustra a geração da lista acima.

[xterm color=’true’ py=’true’]
>>> N = 12
>>> ans = []
>>> for x in range(2, N):
… if N % x == 0:
… ans.append(x)
>>> ans
[2, 3, 4, 6]
[/xterm]

A função a seguir emprega a lista acima para verificar se um número passado é primo:

[xterm color=’true’ py=’true’]
>>> def eh_primo(N):
… return True if [ x for x in range(2, N) if N % x == 0 ] == [] else False

>>> eh_primo(13)
True
>>> eh_primo(12)
False
[/xterm]

A próxima list comprehensions emprega a função acima para gerar a lista de primos, inferiores a 100.

[xterm color=’true’ py=’true’]
>>> [x for x in range(2, 100) if eh_primo(x)]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
[/xterm]

A list comprehensions a seguir utiliza dois for para gerar uma tupla com os elementos da tabuada de 2 e 3.

[xterm color=’true’ py=’true’]
>>> tabuada = [(i, j, i*j) for i in range(2, 4) for j in range(1, 11)]
>>> tabuada
[(2, 1, 2), (2, 2, 4), (2, 3, 6), (2, 4, 8), (2, 5, 10), (2, 6, 12), (2, 7, 14), (2, 8, 16), (2, 9, 18), (2, 10, 20), (3, 1, 3), (3, 2, 6), (3, 3, 9), (3, 4, 12), (3, 5, 15), (3, 6, 18), (3, 7, 21), (3, 8, 24), (3, 9, 27), (3, 10, 30)]
>>> for a, b, c in tabuada:
… print(‘{0} x {1} = {2}’.format(a, b, c))

2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12

3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30
[/xterm]

A lista acima utiliza dois loops embutidos e pode ser reproduzida pelo código abaixo:

[xterm color=’true’ py=’true’]
>>> tab = []
>>> for i in range(2, 4):
… for j in range(1, 11):
… tab.append((i, j, i*j))

>>> tab == tabuada
True
[/xterm]

Para terminar, uma list comprehensions para gerar as matrizes Mat e MatA da seção 1.1, com linhas independentes:

[xterm color=’true’ py=’true’]
>>> [[x for x in range(1,4)] for z in range(3)]
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> [[(1 + i + j*3) for i in range(3)] for j in range(3)]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[/xterm]

2. Considerações

Este é o último texto da série Python 3 sobre estruturas de dados sequenciais. O próximo texto será sobre conjuntos (Set) que, embora não seja uma estrutura sequencial, pois não suporta fatiamento, tem algumas similaridades com as sequências e é a chave para tratar de dicionários.

Deixe um comentário

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.