Python3 05 – Sequências: Listas

porRudson Alves

Python3 05 – Sequências: Listas

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:

É 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:

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:

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:

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

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:

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:

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:

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

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:

o que não é muito prático e

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:

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;
  • clear() – limpa todo o conteúdo da lista;
  • copy() – método padrão para retornar uma cópia da lista;
  • observe que é o mesmo que passar o slice [:], feito anteriormente.

  • count(Valor) – retorna o número de ocorrências de Valor na lista;
  • extend(Iterável) – estende a lista com os elementos do Iterável.

    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;
  • insert(índice, objeto) – insere um objeto na posição de índice passado;
  • 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;
  • remove(Valor) – remove a primeira ocorrência de Valor da lista;
  • reverse() – reverte a ordem dos elementos da lista;
  • sort() – ordena os elementos da lista.

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():

    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:

    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:

    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:

  • __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:

    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).

    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:

    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.
  • __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.
  • __sizeof__ – retorna o tamanho em bytes do objeto na memória.

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:

[  for  in  ]
[  for  in  if  ]

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:

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

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:

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

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

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

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

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

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

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.

Sobre o Autor

Rudson Alves author

Deixe uma resposta

This site uses Akismet to reduce spam. Learn how your comment data is processed.