range() vs xrange()

(válido somente para Python 2.x)

A função range()

Em Python, é muito comum usarmos a seguinte estrutura para realizar uma repetição baseada em um contador:

for i in range(0, 10):
    print i,

A função range(x, y) gera uma lista de números inteiros de x até y (sem incluir o segundo). Assim, range(0, 10), gera a seguinte lista:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Desse modo, a variável i é iterada sobre essa lista, através da estrutura de repetição for. O trecho de código:

for i in range(0, 10):
    print i,

pode ser lido como:

"Para cada elemento na lista [0,1,2,3,4,5,6,7,8,9], imprima tal elemento"

Usar range() ou xrange()? Eis a questão…

É comum ouvirmos ou lermos algum desenvolvedor Python aconselhando a utilização da função xrange() ao invés da função range(), por questões de desempenho. Mas o que é essa tal de xrange()?

A xrange() nada mais é do que uma função que pode, em muitos casos (não sempre), substituir o uso da função range(), fornecendo ainda melhor desempenho. Veja o código abaixo:

for i in xrange(0, 10):
    print i,

O resultado dessa execução é o mesmo de quando utilizamos a função range(), porém, por gerar um elemento de cada vez, o código que utiliza a função xrange() apresenta um desempenho superior.

Quando executamos:

for i in range(0, 1000000000):
    pass

A função range() irá imediatamente gerar uma lista contendo um bilhão de inteiros e alocar essa lista na memória. Uma lista contendo um bilhão de inteiros é capaz de encher a memória de um computador pessoal.

Já com a função xrange(), ao executarmos:

for i in xrange(0, 1000000000):
    pass

Cada um dos inteiros (dos 1 bilhão) será gerado de uma vez, economizando memória e tempo de startup.

Vamos então testar o desempenho usando o módulo timeit().

 

A hora da verdade

junior@qwerty:~ $ python -m timeit "for i in xrange(10000000): pass"
 10 loops, best of 3: 246 msec per loop
junior@qwerty:~-$ python -m timeit "for i in range(10000000): pass"
 10 loops, best of 3: 342 msec per loop

Como podemos ver, o loop que utiliza a função xrange() foi quase 100 milisegundos mais rápido do que o loop que utiliza a função range(). Além disso, se fizermos uma análise de consumo de memória, veremos que a o código que utiliza a função range() utiliza uma quantidade de memória muito maior, pois gera a lista inteira antes de executar a iteração do for.

Então nunca mais vou usar o range(), certo?

Errado! Existe uma diferença fundamental entre as duas funções: a função xrange() não gera uma lista. Isso torna inviável, por exemplo, o slicing e a gravação de seu resultado como uma lista em uma variável. Vejamos:

>>> x = range(0, 10)
>>> print x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> y = xrange(0, 10)
>>> print y
xrange(10)

Ou seja, apesar de nos fornecer um desempenho superior ao desempenho obtido com a range(), a função xrange() não substitui a anterior em todos os seus casos.

Medindo tempo de execução de código Python

Muitas vezes, é necessário que comparemos duas implementações diferentes de uma mesma função em nosso código para saber qual apresenta melhor performance, com relação ao tempo de resposta. Por exemplo, escrevemos duas funções que fazem a soma de todos os números inteiros entre x e y. Ambas funcionam corretamente, mas, gostaríamos de descobrir qual delas nos fornece o menor tempo de resposta. As duas implementações para a mesma função são apresentadas abaixo:

def soma1(x, y):
    return sum(range(x, y+1))

def soma2(x, y):
    soma = 0
    for i in range(x, y+1):
        soma += i
    return soma

Tanto a função soma1, quanto a função soma2 realizam a soma de todos os elementos entre x e y. Como descobrir qual delas nos fornece o menor tempo de resposta? A primeira solução que vem em mente é marcar o tempo antes de chamar a função e marcar o tempo depois de chamar a função. Assim o tempo de resposta seria a diferença entre os dois tempos marcados. Veja o exemplo:

import time

# verifica o tempo de resposta da função soma1
ini = time.time()
soma1(1, 1000000)
fim = time.time()
print "Função soma1: ", fim-ini

# verifica o tempo de resposta da função soma2
ini = time.time()
soma2(1, 1000000)
fim = time.time()
print "Função soma2: ", fim-ini

Assim, você obtém o tempo de execução de cada uma das funções. O problema dessa abordagem é a precisão do tempo medido. A função time.time() retorna o tempo atual em segundos passados desde 1/1/1970. Apesar de ser um número de ponto flutuante, a precisão desse número não é muito grande, além de a própria chamada da função time() já trazer consigo um overhead.

Uma alternativa mais precisa é a utilização do módulo timeit. Esse módulo é incluído com Python e foi projetado especificamente para fazer a medição de tempo de execução de programas/trechos de código Python. Por isso, é mais recomendado que utilizemos o timeit no lugar de uma solução “caseira”. Vejamos a seguir como utilizar o timeit.

Pela linha de comando

O timeit pode ser utilizado para medir o tempo de execução de código python diretamente na linha de comando. O exemplo abaixo ilustra isso:

$ python -m timeit "range(1,10000)"

A linha de comando acima executa o módulo timeit como um script e passa como entrada a expressão Python que está entre aspas. A execução do comando acima em meu sistema retornou o seguinte:

10000 loops, best of 3: 156 usec per loop

Ou seja, o timeit realizou 3 baterias compostas de um conjunto de 10000 repetições cada, executando o código range(1,10000) e, dessas 3 tentativas, a melhor delas obteve uma média de 156 microsegundos por execução do código. Através dele, podemos, por exemplo, verificar na prática que o uso do xrange() ao invés do range() nos dá um tempo de execução melhor. Veja e compare:

$ python -m timeit "xrange(1,10000)"
1000000 loops, best of 3: 0.357 usec per loop

Além do recurso de teste pela linha de comando, também podemos executar o timeit dentro de scripts Python.

Importando o módulo e usando dentro do código Python

Voltando ao exemplo das funções soma1 e soma2, no qual queremos descobrir qual delas nos dá melhor tempo de resposta, podemos fazer o seguinte, no mesmo arquivo onde estão definidas as funções:

import timeit

def soma1(x, y):
    return sum(range(x, y+1))

def soma2(x, y):
    soma = 0
    for i in range(x, y+1):
        soma += i
    return soma

t = timeit.Timer("soma1(1,1000)", "from __main__ import soma1")
print t.repeat()

t = timeit.Timer("soma2(1,1000)", "from __main__ import soma2")
print t.repeat()

O programa, quando executado, irá fazer o teste de ambas as funções. Vamos analisar as duas chamadas a funções do módulo timeit:

t = timeit.Timer("soma1(1,1000)", "from __main__ import soma1")
print t.repeat()

Na primeira linha, passamos duas entradas para o método Timer(). A primeira string indica o código a ser testado, ou seja, o código do qual estamos interessados em saber o tempo de resposta. A segunda string indica um código de inicialização, necessário para que o timeit consiga executar o código que queremos. Chamando a função repeat() sem parâmetros, são executadas 3 baterias de 1000000 execuções e, como retorno, recebemos uma lista contendo os tempos médios de execução das 3 baterias de testes. Poderíamos especificar, como no exemplo abaixo, a quantidade de baterias (2) e o número de execuções por bateria (1000).

t = timeit.Timer("soma1(1,1000)", "from __main__ import soma1")
print t.repeat(2, 1000)

Assim, podemos utilizar o módulo timeit para descobrir qual o tempo de resposta de nosso código, sem a necessidade de construir o nosso próprio módulo de testes de tempo de execução. Uma das vantagens do timeit é que, por exemplo, ele desabilita o Garbage Collector, para evitar que este interfira nos resultados.

Curioso em saber qual das duas funções (soma1 e soma2) apresentou melhor tempo de execução? Faça o teste. 🙂

Servidor Web em Python pela Linha de Comando

O post da vez não é muito relacionado à programação em Python, mas poderá ajudar bastante no nosso dia-a-dia. Quantas vezes precisamos compartilhar determinado arquivo com um colega, que muitas vezes está na mesma rede que a gente? Qual a solução? Realizar o compartilhamento via Samba? Copiar o arquivo com scp? Existe um jeito bem mais simples, usando um módulo que vem instalado junto com o Python, chamado SimpleHTTPServer. Nesse módulo, há uma classe chamada SimpleHTTPRequestHandler, que é responsável por servir, via HTTP, os arquivos e sub-diretórios do diretório de trabalho atual.

Na prática, significa que podemos “levantar” um servidor web que irá listar os arquivos presentes em um diretório, para que outras pessoas possam copiar arquivos. Por exemplo, queremos compartilhar os arquivos e sub-diretórios presentes no diretório /home/user/files, então podemos proceder da seguinte forma:

cd /home/user/files
python -m SimpleHTTPServer

Após isso, basta acessar, usando um navegador Web, o endereço http://localhost:8000 , pois quando chamado sem parâmetros, o serviço é iniciado na porta 8000. É óbvio que para compartilharmos arquivos com amigos, devemos passar o endereço IP de nossa máquina, e não localhost. 😛

Pydev = Python + Eclipse

Pydev é um plugin para a IDE Eclipse que permite criação de projetos Python usando esta IDE. Além do tradicional syntax highlighting, o Pydev oferece completação de código, operações de refatoração, verificação de erros e indicação destes no código, execução e depuração de código Python, autoimport de módulos, dentre outros recursos interessantes. Para usar o Pydev, é necessário ter o Eclipse instalado (é óbvio, né?). Para isso, vá até a página de downloads do Eclipse ou instale este via apt (sistemas Debian based):

sudo apt-get install eclipse

Após isso, a instalação do Pydev se dá utilizando a ferramenta de instalação de software do Eclipse. Os passos para instalação são os seguintes:

  1. Abra o Eclipse e vá ao menu “Help“, opção “Install new software”;
     

    Figura 1. Eclipse

  2. Adicione a URL “http://pydev.org/updates” e selecione o pacote Pydev, conforme mostra a Figura 2;

    Figura 2. Adicionando a URL do Pydev ao Eclipse

  3. Clique em Next até finalizar a instalação;
  4. Reinicie o Eclipse;
  5. Agora é necessário informar ao Pydev onde está nosso interpretador Python;
  6. Vá ao menu Window -> Preferences (Figura 3);

    Figura 3. Selecionando Interpretador

  7. Vá na opção Pydev->Intepreter – Python e clique em “New“;

    Figura 4. Selecionando o Interpretador

  8. No diálogo que abrir, preencha com os dados do seu interpretador e confirme;
  9. Após isso, aplique as alterações (botão Apply na janela mostrada na Figura 3);
  10. Pronto. O Pydev está configurado em seu Eclipse.

Agora você pode criar seu projeto Python. Vá em File -> New -> Other -> Pydev -> Pydev Project. Pronto, seu projeto está criado. Agora basta criar os arquivos .py dentro da pasta src presente no projeto.