Módulo timeit

Como já escrevi em um post anterior, podemos utilizar o módulo timeit (Time it!) para medir o tempo de execução de nossos programas em Python. Porém, às vezes queremos apenas verificar qual trecho de código obtém menor tempo de execução, para escolher qual abordagem seguir. Por exemplo, quero saber o que acarreta em um menor tempo de execução: fazer um for com a função range() ou com a função xrange()?

Nada melhor do que testar na prática para descobrir qual nos dá o menor tempo de resposta. Para isso, podemos usar a interface de linha de comando que o módulo timeit fornece, quando executado como módulo.

python -m timeit

Assim, podemos testar:

python -m timeit "for i in range(1, 10000): pass"
1000 loops, best of 3: 303 usec per loop
python -m timeit "for i in xrange(1, 10000): pass"
1000 loops, best of 3: 206 usec per loop

Como podemos ver, o uso da função xrange() nos deu um tempo de execução de 97 milisegundos a menos do que utilizando a função range(), considerando o melhor dos casos para ambos. No exemplo acima, o timeit executou 3 baterias de testes compostas por 1000 execuções do código passado como argumento.

Em muitos casos iremos precisar testar um código que possui mais de uma linha. Isso pode ser feito passando cada uma das linhas como um argumento separado para o programa. Por exemplo:

x = 0
for i in range(0, 1000):
    x = i * 2

O código acima poderia ser testado pela linha de comando da seguinte forma:

python -m timeit "x=0" "for i in range(1, 10000):" "    x = i * 2"

Ou seja, cada linha é uma string separada e a indentação é feita linha por linha.

Por que python -m?

Se você está se perguntando o que significa python -m timeit, eis a resposta: ao chamar o interpretador python com a opção -m seguida pelo nome de um módulo existente no ambiente, o interpretador irá buscar o arquivo .py que representa tal módulo e executá-lo como se fosse um programa. O módulo timeit, em meu sistema, fica localizado em /usr/lib/python2.7/timeit.py (como eu sei isso? leia aqui). O que é feito quando executamos python -m timeit é o mesmo que:

python /usr/lib/python2.7/timeit.py "for i in xrange(1, 10000): pass"

Vá em frente e teste.

 

O timeit pode ser usado também dentro de programs Python. Veja mais exemplos: http://docs.python.org/library/timeit.html#examples

Anúncios

Problemas importando módulos em Python

Sendo professor de disciplinas de introdução à programação com Python, percebi que muitos alunos cometem um erro muito comum e bastante difícil de ser descoberto quando estamos começando.

De vez em quando, apresento módulos Python aos alunos e peço que eles façam um pequeno exemplo utilizando aquele módulo. Ao dar nome ao arquivo-fonte que está digitando, o aluno acaba nomeando o seu arquivo com o mesmo nome do módulo a ser usado.

Por exemplo, peço aos alunos para escrever um programinha simples para conhecer melhor o módulo math. Então, o aluno cria um arquivo chamado math.py, onde digita seu código. Entre as linhas de código inseridas no arquivo, estão:

import math
print math.pi

Ao tentar executar o programa, o usuário receberá uma mensagem dizendo que não existe um atributo pi no módulo math. Daí, o aluno vai lá e pesquisa na documentação do módulo math e vê que o atributo pi de fato existe naquele módulo. O que há de errado?

Simples. Quando executa o programa recém escrito (math.py), a primeira linha faz import de um módulo chamado math(.py). Como o diretório atual faz parte dos caminhos onde o interpretador python busca os módulos que o programador importa, o interpretador acaba, inadvertidamente, importando o arquivo que o usuário criou, ao invés de importar o módulo math(.py) original. Para resolver esse problema, renomeie seu arquivo.

🙂

Quer descobrir em quais diretórios Python busca os módulos importados pelos programas? Execute o código abaixo:

import sys
print sys.path

Onde está localizado um módulo Python no disco?

Às vezes, quando importamos um módulo em Python, surge a curiosidade para ler o código-fonte desse módulo, para descobrir o que aquele código faz realmente. Existe uma forma bem simples de descobrir a localização de um módulo no disco. Após importar um módulo, basta verificar o atributo __file__ do módulo.

>>> import os
>>> print os.__file__
/usr/lib/python2.7/os.pyc

Esse arquivo, cujo caminho é impresso na tela é, na realidade, o arquivo binário contendo o bytecode compilado. Porém, se retirar o c do final do nome do arquivo, teremos o arquivo-fonte (.py). No exemplo acima, podemos ver o código-fonte do módulo em:

/usr/lib/python2.7/os.py

Outro exemplo, se quisermos ver o código-fonte do módulo timeit, fazemos o seguinte:

>>> import timeit
>>> print timeit.__file__
/usr/lib/python2.7/timeit.pyc

O arquivo-fonte então está em:

/usr/lib/python2.7/timeit.py

Também poderíamos procurar pelo módulo dentro da lista sys.path, que contém os caminhos onde o interpretador busca pelos módulos a serem importados.

map(), reduce(), filter() e lambda

map()

map() é uma função builtin de Python, isto é, uma função que é implementada diretamente no interpretador Python, podendo ser utilizada sem a importação de um módulo específico. Essa função, em Python, serve para aplicarmos uma função a cada elemento de uma lista, retornando uma nova lista contendo os elementos resultantes da aplicação da função. Considere o exemplo abaixo:

>>> import math
>>> lista1 = [1, 4, 9, 16, 25]
>>> lista2 = map(math.sqrt, lista1)
>>> print lista2
[1.0, 2.0, 3.0, 4.0, 5.0]

Ao chamar a função map(math.sqrt, lista1), estamos solicitando ao interpretador para que execute a função math.sqrt (raiz quadrada – square root) usando como entrada cada um dos elementos de lista1, e inserindo o resultado na lista retornada como resultado da função map().

É uma forma bem interessante e expressiva de denotar a aplicação de uma função a cada elemento de uma lista (ou sequência). Mas, podemos facilmente substituir uma chamada a map() por list comprehensions. O código recém listado poderia ser substituído por:

>>> lista2 = [math.sqrt(x) for x in lista1]
>>> print lista2
[1.0, 2.0, 3.0, 4.0, 5.0]

O código acima produz o mesmo resultado que map(), pois, para cada elemento de lista1, executa a função math.sqrt e inclui o resultado dessa execução na lista de retorno.

O fato de a função map() ser tão facilmente substituída pelo uso de comprehensions, já criou até mesmo algumas discussões sobre manter ou não map() entre as funções builtin do Python 3000 [1].

reduce()

reduce() é outra função builtin do Python (deixou de ser builtin e passou a estar disponível no módulo functools a partir da versão 3000). Sua utilidade está na aplicação de uma função a todos os valores do conjunto, de forma a agregá-los todos em um único valor. Por exemplo, para aplicar a operação de soma a todos os elementos de uma sequência, de forma que o resultado final seja a soma de todos esses elementos, poderíamos fazer o seguinte:

>>> import operator #necessário para obter a função de soma
>>> valores = [1, 2, 3, 4, 5]
>>> soma = reduce(operator.add, valores)
>>> print soma
15

É claro que, para realizar a soma de todos os elementos de uma sequência, é muito mais claro utilizarmos a função sum():

>>> print sum(valores)
15

Como falei anteriormente, reduce() foi retirada do conjunto de builtins de Python, em parte devido à obscuridade que pode acabar gerando [1].

filter()

Como o próprio nome já deixa claro, filter() filtra os elementos de uma sequência. O processo de filtragem é definido a partir de uma função que o programador passa como primeiro argumento da função. Assim, filter() só “deixa passar” para a sequência resultante aqueles elementos para os quais a chamada da função que o usuário passou retornar True. Vejamos um exemplo:

>>> def maior_que_zero(x):
...     return x > 0
...
>>> valores = [10, 4, -1, 3, 5, -9, -11]
>>> print filter(maior_que_zero, valores)
[10, 4, 3, 5]

No exemplo acima, filter() chamou a função maior_que_zero para cada um dos elementos contidos em valores. Se a função retornar True, o elemento é inserido na lista de resultado. Caso contrário, não o é. Ou seja, é feita a filtragem e só passam aqueles elementos que são maiores que zero.

Assim, como no exemplo da builtin map(), aqui também podemos escrever com facilidade uma comprehension com a mesma funcionalidade:

>>> print [x for x in valores if x > 0]
[10, 4, 3, 5]

Devido a essa fácil substituição, filter() também já esteve na mira para ser retirada do conjunto de builtins, embora tenha acabado permanecendo.

lambda

No exemplo da função filter(), tivemos que definir uma nova função (chamada maior_que_zero) para usar somente dentro da função filter(), sendo chamada uma vez para cada elemento. Ao invés de definir uma nova função dessa forma, poderíamos definir uma função válida somente enquanto durar a execução do filter. Não é necessário nem dar um nome a tal função, sendo portanto chamada de função anônima ou função lambda. Considere o exemplo abaixo:

>>> valores = [10, 4, -1, 3, 5, -9, -11]
>>> print filter(lambda x: x > 0, valores)
[10, 4, 3, 5]

Definimos uma função anônima (portanto, não tem nome), que recebe uma entrada (a variável x) e retorna o resultado da operação x > 0, True ou False.

Poderíamos também usar uma função lambda no exemplo da função reduce():

>>> valores = [1, 2, 3, 4, 5]
>>> soma = reduce(lambda x, y: x + y, valores)
>>> print soma
15

No código acima, definimos uma função anônima que recebe duas entradas e retorna a soma dessas entradas.

[1] Guido Van Rossum. The fate of reduce() in Python 3000

Supresas divertidas em Python – Easter Eggs

Existem algumas brincadeiras que os desenvolvedores do interpretador Python “esconderam” como surpresas engraçadas para o usuário. Por exemplo, abra um shell Python e digite:

>>> import antigravity

Após digitar tal comando, será aberta uma janela do navegador com uma tirinha do xkcd, que brinca com Python: http://xkcd.com/353/

Outra brincadeira bem engraçada é a seguinte:

>>> from __future__ import braces
File "<stdin>", line 1
SyntaxError: not a chance

Repare na mensagem de erro. Ela faz uma brincadeira com o fato de programas em Python não precisarem e não utilizarem { e } para delimitar os blocos de código, isso porque Python define os blocos através da indentação do código.

O easter egg que é provavelmente o mais famoso é o seguinte:

>>> import this

Ao digitar esse comando, será impresso na tela um texto conhecido como “The Zen of Python”, que apresenta em alguns versos a filosofia empregada no desenvolvimento da linguagem.

Faça os testes você mesmo. Se você conhece algum outro easter egg em Python, poste aqui nos comentários.