Ordenação de uma lista

É comum termos uma lista toda bagunçada e querermos ordenar os elementos contidos nela. Para ordenar uma lista de valores, basta chamar o método sort da lista.

Vamos ver como isso funciona na prática. Primeiramente, vamos criar uma lista com 10 elementos e depois bagunçá-la usando a função shuffle, do módulo random.

>>> lista = range(10)
>>> lista
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> import random
>>> random.shuffle(lista)
>>> lista
[2, 5, 4, 1, 3, 6, 9, 7, 0, 8]

Tudo que precisamos fazer para ordenar uma lista desordenada é:

>>> lista.sort()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Barbada! Também podemos ordená-la de forma descendente:

>>> lista.sort(reverse=True)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

A ordenação de inteiros ou de valores de tipos de dados simples é bem trivial. Porém, se quisermos ordenar uma lista que contém instâncias de uma classe, precisaremos utilizar o parâmetro key do sort.

O parâmetro key

O parâmetro key do método sort espera uma função que será chamada uma vez para cada um dos elementos da lista e o retorno dessa função será utilizado na comparação com os outros elementos da lista.

Considere que temos uma classe Aluno, conforme o código abaixo:

class Aluno:
	def __init__(self, nome, matricula):
		self.nome = nome
		self.matricula = matricula

	def __str__(self):
		return "%s - %s" % (str(self.nome), str(self.matricula))

Dada uma lista chamada alunos contendo n objetos do tipo Aluno, como ordená-la? Se chamarmos alunos.sort(), sem especificar como queremos que ela seja ordenada, o sort irá ordená-la através de comparações dos endereços de memória dos objetos contidos na lista alunos. Se quisermos que a ordenação se dê por algum dos atributos da classe, devemos especificar isso através do parâmetro key.

Vamos primeiramente criar uma lista com objetos de conteúdo aleatório:

>>> alunos = [Aluno("".join(random.sample(string.ascii_letters, 5)), random.randint(0, 100)) for i in range(10)]
>>> for aluno in alunos:
		print aluno
zfbnu - 12
sxbIX - 77
vJCIN - 33
aBjZA - 70
fNLeS - 19

Bonitos os nomes deles, né? Agora, vamos ordená-los:

>>> alunos.sort(key=lambda a: a.nome)
>>> for aluno in alunos:
        print aluno
aBjZA - 70
fNLeS - 19
sxbIX - 77
vJCIN - 33
zfbnu - 12

O que fizemos foi especificar como queremos que os elementos sejam comparados.  Para isso, criamos uma função anônima que recebe como parâmetro um objeto e retorna o elemento a ser usado na comparação (o atributo nome). O mesmo poderia ser feito com uma função nomeada, como:

>>> def key_func(aluno):
...     return aluno.nome
>>> alunos.sort(key=key_func)
>>> for aluno in alunos:
...     print aluno

Porém, ter que criar uma função (anônima ou não) somente para indicar qual atributo deverá ser usado na ordenação é um pouco inconveniente. Por isso, vamos utilizar o outro mecanismo que permite que façamos a mesma coisa. Ao invés de criarmos uma função que recebe um objeto e retorna o atributo nome daquele objeto, vamos usar a função attrgetter do módulo operator, que retorna o valor do atributo solicitado no objeto em questão.

>>> from operator import attrgetter
>>> alunos.sort(key=attrgetter("nome"))

A função attrgetter irá retornar uma outra função que quando chamada sobre cada objeto x contido na lista alunos, irá retornar x.nome.

Ou seja, para cada objeto Aluno contido na lista, será chamado o método attrgetter, solicitando o atributo nome.

Ordenando uma lista de listas

Já vi muito código que utiliza lista ou tupla como mecanismo para agrupar dados. Ao invés de criar uma classe ou uma namedtuple, o cara vai lá e empacota os dados que deseja em uma tupla. Por exemplo, ao invés de criar uma classe Aluno, poderíamos ter empacotado os dados referentes a cada aluno em uma tupla. Veja:

>>> alunos = [("Jose", 12345), ("Maria", 28374), ("Joao", 11119), ("Joana", 12346)]

Para ordenar uma lista desse tipo, podemos continuar usando o método sort e o parâmetro key, e agora vamos especificar qual elemento das tuplas que compõem a lista será utilizado na comparação para definir qual elemento precede qual na ordem. No exemplo abaixo, estamos ordenando os alunos pelo número da matrícula.

>>> alunos.sort(key=lambda x: x[1])
>>> print alunos
[('Joao', 11119), ('Jose', 12345), ('Joana', 12346), ('Maria', 28374)]

A função anônima poderia ser evitada novamente usando a função itemgetter:

>>> from operator import itemgetter
>>> alunos.sort(key=itemgetter(1))

O itemgetter é bem parecido com o attrgetter, com a diferença de que passamos para ele o índice do elemento que queremos que seja usado na comparação que será feita ao ordenar a lista.

Mas fique atento, o método sort está presente somente nas listas. Para ordenar outros objetos iteráveis, dê uma olhada na função builtin sorted.

Anúncios

Baixar página de servidor com tolerância a falhas simples

Um dia desses precisei fazer um script Python que baixava uma cacetada de páginas HTML de um servidor, que às vezes respondia com um erro para algumas das requisições. As requisições estavam corretas, as páginas existiam, mas por alguma falha no servidor elas não respondiam no momento exato da execução do script.

A solução foi fazer uma função que tenta carregar a URL, e caso não consiga, espera alguns segundos e tenta de novo:

import time
import urllib2

def get_url(url, retry=3, timeout=3):
    try:
        return urllib2.urlopen(url).read()
    except:
        if retry > 0:
            time.sleep(timeout)
            return get_url(url, retry - 1, timeout)
        else:
            raise