26 dic. 2012

Pruebas Unitarias en Python con unittest

Continuando con las herramientas que permiten el aseguramiento de la calidad, ahora se realizará pruebas unitarias a la función que cálcula la raíz cuadrada del artículo anterior (Probar código con doctest).

El desarrollo guiado por pruebas ó Test driven development (TDD), es una práctica de la programación que involucra dos prácticas: Escribir las pruebas primero y refactorizar continuamente el código.

Para escribir primero las pruebas se usa generalmente las pruebas unitarias. esto es una forma de probar el correcto funcionamiento de un módulo de código. Permite asegurar el correcto funcionamiento de cada módulo por separado, luego con las pruebas de integración, se podrá asegurar el correcto funcionamiento del sistema.

Ciclo de desarrollo guidado por pruebas (TDD).
En primer lugar se debe definir una lista de requisitos, después se ejecuta el siguiente ciclo:

  1. Elegir un requisito: Se elige de una lista el requerimiento que se cree dará mayor conocimiento del problema y que a la vez sea facilmente implementable.
  2. Escribir una prueba.
  3. Verificar que la prueba falle: Si la prueba no falla es por que el requerimiento ya estaba implementado o por que la prueba es erronea. 
  4. Ejecutar las pruebas automatizadas: Verificar que todo el conjunto de pruebas funciona correctamente.
  5. Eliminación de duplicación: Se eliminará código duplicado. 
  6. Actualización de la lista de requisitos: Se actualiza la lista de requisitos tachando el requisito implementado.

Para que una prueba unitaria sea buena tiene que cumplir los siguientes requisitos:

  • Automatizable: No debe ejecutarse manualmente (útil para integración continua).
  • Completas: Deben cubrir la mayor cantidad de código.
  • Reutilizables: No se deben crear pruebas que sólo puedan ser ejecutadas una vez (útil para integración continua).
  • Independientes: La ejecución de una prueba no debe afectar la ejecución de otra prueba.
  • Profesionales: Las pruebas deben ser consideradas igual que el código, con la misma profesionalidad, documentación, etc. 


Las pruebas unitarias son pruebas automatizadas que prueban pequeñas piezas de código, usualmente una función o un método.

Python tiene el API de PyUnit para pruebas unitarias. El módulo se llama unittest, se basa en el framework XUnit diseñado por Ken Beck y Erich Gamma.

El código de ejemplo muestra por un lado la función Raiz y por el otro la clase RaizTest que hereda de unittest.TestCase. Esta clase tendrá 3 métodos, el primero que cálcula la raiz de 9 y devuelve 3, el segundo que cálcula la raiz de 0 y devuelve 0 y por último el que cálcula la raiz de un valor negativo y devuelve una excepción o mensaje de error.

El código se muestra  a continuación:


#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa el módulo unittest y math

import unittest

import math



#Función raiz cuadrada.

def Raiz(a):

    #Si a es mayor o igual a cero se calcula la raiz cuadrada

    if a >= 0:

        return math.sqrt(a)

    #Si es menor a cero se genera una excepción donde se informa que a debe ser mayor o igual a cero.

    else:

        raise ValueError,"a debe ser >= 0"



class RaizTest(unittest.TestCase):

    def test_Raiz(self):

        #Test para la raiz de nueve que devuelve 3 que debe pasar.

        self.assertEqual(3, Raiz(9))

    

    def test_zero(self):

        #Test para la raiz de 0 que devuelve 0, que debe pasar.

        self.assertEqual(0, Raiz(0))

        

    def test_negative(self):

        #Test para la raiz de un número negativo, que debe fallar.

        # Este debe devolver un ValueError, pero se espera un IndexError.

        self.assertRaises(IndexError, Raiz(-10))

        

if __name__ == '__main__':

    #Se ejecuta la prueba unitaria

    unittest.main()

Al ejecutar el código se tiene lo siguiente:
ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/timeit$ python test-raiz.py 
.E.
======================================================================
ERROR: test_negative (__main__.RaizTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test-raiz.py", line 40, in test_negative
    self.assertRaises(IndexError, Raiz(-10))
  File "test-raiz.py", line 24, in Raiz
    raise ValueError,"a debe ser >= 0"
ValueError: a debe ser >= 0

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=1)



Como se puede ver, la prueba de un valor negativo falla. 
Si se comenta el método de prueba de valor negativo la ejecución devuelve que no hay errores:
ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/timeit$ python test-raiz.py 
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK


Las funciones de unittest utilizadas son:
  • assertEqual: Prueba donde dos valores son iguales.
  • assertRaises: Prueba donde una excepción es devuelta.
Para más métodos del módulo unittest se puede revisar la documentación.

24 dic. 2012

Pyproceessing: Un ambiente para crear gráficos con Python

Pyprocessing es un paquete python que permite crear gráficos que se basa en las librerías  OpenGL y Pyglet. El proyecto se aloja en google code.

En la documentación encontrarán la guía de referencia rápida, un tutorial básico, un tutorial más completo y las instrucciones de uso.

Para instalarlo en linux se usa easy_install o pip:
easy_install pyprocessing



pip install pyprocessing

El ejemplo que se hará es el de crear con rectangulo, líneas y elipses una especie de muñeco sin brazos junto a una pequeña esfera. Este ejemplo se basa en el pequeño ejemplo que tiene el proyecto en la página de entrada.

El código se muestra a continuación:

#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa pyprocessing

from pyprocessing import *

#define el tamaño de la ventana.

size(200,200)

#Define un rectangulo en el centro de la ventana

rectMode(CENTER)

#Se crea el rectangulo(x,y,ancho,alto)

rect(100,100,20,100)

#Se crean 3 elipses(x,y,ancho,alto)

ellipse(100,70,60,60)

ellipse(81,70,16,32) 

ellipse(119,70,16,32)

#Se crean 2 lineas(x1,y1,x2,y2)

line(90,150,80,160)

line(110,150,120,160)

#No se crea bordes en la figura

noStroke();

#Define que tendrá luz la esfera

lights();

#Define la cantidad de desplazamiento con respecto a la ventana.

#(derecha/izquierda,arriba/abajo,delante/detrás)

translate(28, 48, 0);

#Se crea una esfera con radio 15

sphere(15)

#Se muestra en la ventana.

run()

La siguiente figura muestra el resultado del código al ser ejecutado:

14 dic. 2012

Separar código de pruebas de la documentación (doctest, 2da parte)

En el artículo anterior se explico como utilizar doctest dentro de un código para realizar pruebas sobre la documentación de cada función.

Ahora se explicará como realizar dichas pruebas de la documentación en un archivo aparte del código del programa. Se usará el mismo ejemplo del artículo anterior pero adaptandolo a tener la documentación aparte en un archivo de texto.

El código se muestra a continuación:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

#Se importa el módulo math para calcular la raiz cuadrada.

import math



#Función raiz cuadrada.

def Raiz(a):

    #Si a es mayor o igual a cero se calcula la raiz cuadrada

    if a >= 0:

        return math.sqrt(a)

    #Si es menor a cero se genera una excepción donde se informa que a debe ser mayor o igual a cero.

    else:

        raise ValueError("a debe ser >= 0")



if __name__ == '__main__':

    #Se importa el módulo doctest

    import doctest

    #Se realiza la prueba al archivo raizcuadra.txt

    doctest.testfile("raizcuadrada.txt")

Como se ve se realiza la prueba al archivo raizcuadrada.txt. El archivo contiene lo siguiente:


Modulo raiz cuadrada

=====================

Usando  'raizcuadrada'

------------------------------

Primero se importa la función:



>>> from raizcuadrada import Raiz



Ejemplos de calculo de raiz cuadrada:



>>> Raiz(4)

2.0

>>> Raiz(9)

3.0

>>> Raiz(25)

5.0

>>> Raiz(0)

0.0

>>> Raiz(-1)

Traceback (most recent call last):

...

ValueError: a debe ser >= 0

Ahora lo que viene es ejecutar el comando que devuelve el resultado de las pruebas:
python -m doctest -v raizcuadrada.txt



Trying:

    from raizcuadrada import Raiz

Expecting nothing

ok

Trying:

    Raiz(4)

Expecting:

    2.0

ok

Trying:

    Raiz(9)

Expecting:

    3.0

ok

Trying:

    Raiz(25)

Expecting:

    5.0

ok

Trying:

    Raiz(0)

Expecting:

    0.0

ok

Trying:

    Raiz(-1)

Expecting:

    Traceback (most recent call last):

    ...

    ValueError: a debe ser >= 0

ok

1 items passed all tests:

   6 tests in raizcuadrada.txt

6 tests in 1 items.

6 passed and 0 failed.

Test passed.
Como se puede observar en este caso se realiza la importación del programa y luego se empieza las pruebas.


13 dic. 2012

Probar código con doctest

Doctest es un framework que viene en Python el cual permite desarrollar aplicaciones utilizando TDD (Desarrollo guiado por pruebas).

El TDD exige escribir las pruebas primero y la refactorización del código para llegar al resultado deseado.

En este caso se usará doctest el cual permite realizar pruebas según la documentación que se tenga escrita en el código. Significa que es necesario tener una documentación clara para cada función antes de desarrollarla, de esta forma se tiene claro los casos de funcionamiento correcto de la función y los casos en los cuales puede fallar.

El código de ejemplo es el de una función que permite calcular la raíz cuadrada de un número.

A continuación el código:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

#Se importa el módulo math para calcular la raiz cuadrada.

import math



#Función raiz cuadrada.

def RaizCuadrada(a):

    """

    >>> RaizCuadrada(4)

    2.0

    >>> RaizCuadrada(9)

    3.0

    >>> RaizCuadrada(25)

    5.0

    >>> RaizCuadrada(0)

    0.0

    >>> RaizCuadrada(-1)

    Traceback (most recent call last):

        ...

    ValueError: a debe ser >= 0

    """

    #Si a es mayor o igual a cero se calcula la raiz cuadrada

    if a >= 0:

        return math.sqrt(a)

    #Si es menor a cero se genera una excepción donde se informa que a debe ser mayor o igual a cero.

    else:

        raise ValueError("a debe ser >= 0")

Como se muestra se genera en la documentación una serie de casos de ejecución de la función donde debe devolver un valor correcto y si se pasa un valor que no puede ser calculado devuelve la excepción.

Para realizar la prueba se ejecuta el código realizando una llamada al módulo doctest y modo verbose:
python -m doctest -v prueba-raizcuadrada.py

El resultado es el siguiente:

Trying:

    RaizCuadrada(4)

Expecting:

    2.0

ok

Trying:

    RaizCuadrada(9)

Expecting:

    3.0

ok

Trying:

    RaizCuadrada(25)

Expecting:

    5.0

ok

Trying:

    RaizCuadrada(0)

Expecting:

    0.0

ok

Trying:

    RaizCuadrada(-1)

Expecting:

    Traceback (most recent call last):

        ...

    ValueError: a debe ser >= 0

ok

1 items had no tests:

    prueba-raizcuadrada

1 items passed all tests:

   5 tests in prueba-raizcuadrada.RaizCuadrada

5 tests in 2 items.

5 passed and 0 failed.

Test passed.

Como se muestra todos los casos pasaron el test.
Pueden revisar la siguiente documentación y tutoriales:
En otro artículo se explicará como separar los diferentes casos en un archivo de texto y el programa en un script aparte.


12 dic. 2012

Configurar pantalla touch Bematech en Debian

Se realiza la instalación de Debian Squeeze con la pantacha táctil conectada. en este caso la pantalla es una pantalla táctil por medio de una conexión USB.

Al terminar la instalación se ejecuta el comando lsusb para ver como se detecta la pantalla:
lsusb
Bus 004 Device 002: ID 0eef:0001 D-WAV Scientific Co., Ltd eGalax TouchScreen

Es un dispositivo de la Empresa eGalax.
Se visita la página de eGalax para bakar los drivers para Linux,el driver es eGTouch_v2.5.2107.L-x, este driver es el que tiene soporte para kernel superior a 2.6.24.

Instalar paquetes necesarios para la configuración del touch:
Es necesario instalar librerías de xorg y los headers del kernel que tiene Squeeze.

apt-get install xserver-xorg-input-evtouch xinput xserver-xorg-dev xserver-xorg-input-evdev-dev xserver-xorg-input-evdev xserver-xorg-input-synaptics-dev  linux-headers-2.6.32-5-686

Al tener el archivo comprimido en el equipo se descomprime:
tar -xvzf eGTouch_v2.5.2107.L-x.tar.gz

Se entra en el directorio del driver:
cd eGTouch_v2.5.2107.L-x/

Ahora se ejecuta el script setup.sh el cual es una ayuda para el proceso de Instalación del driver:

sudo sh setup.sh

A continuación se sigue las instrucciones del instalador:

Se resaltará la parte de las preguntas del instalador.

(*) Driver installer for touch controller

(*) Script Version = 1.04.2013

(I) Check user permission: root, you are the supervisor.

(I) Platform application binary interface = i686
(W) X server detected.

Declaration and Disclaimer

The programs, including but not limited to software and/or firmware
(hereinafter referred to "Programs" or "PROGRAMS", are owned by
eGalax_eMPIA Technology Inc. (hereinafter referred to EETI) and are
compiled from EETI Source code. EETI hereby grants to licensee a
personal, non-exclusive, non-transferable license to copy, use and
create derivative works of Programs for the sole purpose in
conjunction with an EETI Product, including but not limited to
integrated circuit and/or controller. Any reproduction, copies,
modification, translation, compilation, application, or representation
of Programs except as specified above is prohibited without the
express written permission by EETI.

Disclaimer: EETI MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,

WITH REGARD TO PROGRAMS, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
EETI reserves the right to make changes without further notice to the
Programs described herein. Licensee agrees that EETI does not assume
any liability, damages or costs, including but not limited to attorney
fees, arising out from Programs themselves or those arising out from
the application or combination Programs into other products or
circuit. The use or the inclusion of EETI's Programs implies that the
Licensee assumes all risk of such use and in doing so indemnifies EETI
against all charges, including but not limited to any claims to
infringement of any third party's intellectual property right.

Do you agree with above patent declaration?
 [Y] Yes, I agree.  [N] No, I don't agree.
Y

(Q) Which interface controller do you use?
(I) [1] RS232 [2] USB [3] PS2 : 2
(I) Please confirm the touch controller is linked with your device.
Press [Enter] key to continue..........

(I) Found /etc/rc.local file.
(I) Found a HID compliant touch controller.
(I) X.Org X server 1.7.7
(I) X verion is 1.7.6 upwards
(I) Found uinput.ko in modules.
(I) Attach uinput at boot.
(I) Add uinput module into /etc/modules file.
(I) Place eGTouch driver archive to /usr/local/eGTouch32withX.
(I) Create eGTouch daemon shortcut in /usr/bin.
(I) Create eGTouchU tool shortcut in /usr/bin.
(I) Create eCalib tool shortcut in /usr/bin.
(I) Append eGTouch daemon execution into /etc/rc.local.

(Q) How many controllers do you want to plug-in to system? [1-10]
(I) Default [1]:

(I) Device Nums is set to 1
(I) Copy udev rule: 52-egalax-virtual.conf to /usr/share/X11/xorg.conf.d.
(I) Create eGTouchU shortcut in application list.

(I) Driver installation completed. Setup version 1.04.2013.

(I) Please reboot the system.
--------------------------------------------------------------------
Lo que queda es reiniciar el equipo.

Ahora viene el proceso de configuración del touch ejecutando el comando eGTouchU desde la consola: eGTouchU 
Este despliega una herramienta gráfica que permite configurar y calibrar el touch:
La figura muestra la pestaña general donde debe aparecer el controlador USB.



La pestaña Setting se deja tal cual viene pre configurada:




La pestaña Tool es la que permite calibrar la pantalla toch, se selecciona la Función de Linearización con 25 puntos de calibración:





En la siguiente figura se muestra la pantalla en el momento de calibrarla:


Ahora se selecciona la prueba de dibujo para verificar el correcto funcionamiento de la pantalla táctil:



En la figura se muestra el funcionamiento de la pantalla desde la prueba de dibujo:

En la pestaña Display se selecciona modo full pantalla:

En la pestaña Edge no se realizan cambios:

En la pestaña Misc sólo se cambia el Modo del Mouse a Click on Touch:



Luego de esto se pueden aplicar los cambios y reiniciar el equipo.

Con esto ya se tiene la pantalla táctil funcionando.



11 dic. 2012

Analizando código Python con Pylint

Pylint es una herramienta de análisis de código creada por LogiLab.  Es más complejo que Pyflakes y permite más personalización. Para más información de pylint puede revisar el manual.

Se puede instalar pylint por medio de easy_install o pip y si es una distribución de Linux basada en Debian se puede instalar vía apt-get:

easy_install  pylint

pip install pylint

apt-get install pylint

La salida de pylint puede ser texto crudo o puede ser en formato html. Los mensajes tienen el siguiente formato:
TIPO_MENSAJE: LINEA:[OBJECTO:] MENSAJE

El tipo de mensaje puede ser de la siguiente forma:

  • [R]: Significa que se recomienda la refactorización del código.
  • [C]: Significa que en el código hubo violación de estilos.
  • [W]: Es una alarma por un problema menor.
  • [E]: Significa mensaje de error o un potencial bug.
  • [F]: Indica un error grave, bloqueo para análisis futuros.
Se usará el mismo código del artículo anterior:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Se importa numpy como np
import numpy as np
from pudb import set_trace; set_trace()
import string
a = np.arange(10)
print a
print a[8]
print a[9]
for i in a:
    if i == plataforma:
        print i
Este script se salva con el nombre de arreglo.py.
Ahora se ejecutará pylint y se le pasa como argumento el archivo arreglo.py:
La salida de la ejecución de pylint se muestra a continuación:

************* Module arreglo
C:  1,0: Missing docstring
C:  7,28: More than one statement on a single line
W:  8,0: Uses of a deprecated module 'string'
C: 12,0: Invalid name "a" (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)
E: 18,12: Undefined variable 'plataforma'
W:  8,0: Unused import string
Report
======
12 statements analysed.
Duplication
-----------
+-------------------------+------+---------+-----------+
|                         |now   |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines      |0     |0        |=          |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |0.000    |=          |
+-------------------------+------+---------+-----------+
Statistics by type
------------------
+---------+-------+-----------+-----------+------------+---------+
|type     |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module   |1      |1          |=          |0.00        |0.00     |
+---------+-------+-----------+-----------+------------+---------+
|class    |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|method   |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
|function |0      |0          |=          |0           |0        |
+---------+-------+-----------+-----------+------------+---------+
External dependencies
---------------------
::
    numpy (arreglo)
    pudb 
      \-set_trace (arreglo)
Raw metrics
-----------
+----------+-------+------+---------+-----------+
|type      |number |%     |previous |difference |
+==========+=======+======+=========+===========+
|code      |11     |64.71 |11       |=          |
+----------+-------+------+---------+-----------+
|docstring |0      |0.00  |0        |=          |
+----------+-------+------+---------+-----------+
|comment   |3      |17.65 |3        |=          |
+----------+-------+------+---------+-----------+
|empty     |3      |17.65 |3        |=          |
+----------+-------+------+---------+-----------+
Messages by category
--------------------
+-----------+-------+---------+-----------+
|type       |number |previous |difference |
+===========+=======+=========+===========+
|convention |3      |3        |=          |
+-----------+-------+---------+-----------+
|refactor   |0      |0        |=          |
+-----------+-------+---------+-----------+
|warning    |2      |2        |=          |
+-----------+-------+---------+-----------+
|error      |1      |1        |=          |
+-----------+-------+---------+-----------+
Messages
--------
+-----------+------------+
|message id |occurrences |
+===========+============+
|W0611      |1           |
+-----------+------------+
|W0402      |1           |
+-----------+------------+
|E0602      |1           |
+-----------+------------+
|C0321      |1           |
+-----------+------------+
|C0111      |1           |
+-----------+------------+
|C0103      |1           |
+-----------+------------+
Global evaluation
-----------------
Your code has been rated at 1.67/10 (previous run: 1.67/10)

Si se desea más información de la información generada en el reporte de pylint se puede revisar la documentación antes mencionada.

10 dic. 2012

Analizar código Python con Pyflakes

Pyflakes es una herramienta de análisis de código Python.

Pyflakes puede detectar potenciales problemas como:

  • Módulos importados sin usar.
  • Variables sin usar.

Para instalarlo se puede bajar desde PyPI ó desde el sitio de Launchpad; también se puede instalar con easy_install ó pip y para las distribuciones basadas en Debian se instala vía apt-get o aptitude.

easy_install pyflakes



pip install pyflakes



apt-get install pyflakes

Para probar el uso de pyflakes se usará el código del tutorial de debugging, se agregará la importación de string y una instrucción if donde se compara el valor de i con una variable llamada plataforma.

A continuación el código:

#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa numpy como np

import numpy as np



from pudb import set_trace; set_trace()

import string



a = np.arange(10)

print a

print a[8]

print a[9]



for i in a:

    if i == plataforma:

        print i

Este script se salvará con el nombre de arreglo.py.

Ahora se ejecutará pyflakes:

pyflakes arreglo.py 

arreglo.py:8: 'string' imported but unused

arreglo.py:18: undefined name 'plataforma'

La ejecución devuelve la línea donde hay definiciones sin usar (import string) y una variable sin definir (plataforma).

Al eliminar la importación de la línea 8 y areglar la definición de la variable plataforma o cambiar dicha parte del código se eliminará los mensajes que devuelve pyflakes. Esto permite prevenir errores en el código.

7 dic. 2012

Depurar código python con pudb

Pudb es una herramienta de depuración full pantalla para la consola. Soporta teclas de cursor y comandos del editor vi. Se puede integrar con ipython si se requiere.

Para instalarlo en distribuciones basadas en Debian se ejecuta el siguiente comando:

apt-get install python-pudb

Para instalarlo con easy_install o pip se ejecuta lo siguiente:

easy_install pudb

pip install pudb

Se depurará el mismo código del artículo anterior. Se agregará al código para la depuración:
from pudb import set_trace; set_trace()

El código completo se muestra a continuación:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#Se importa numpy como np
import numpy as np

from pudb import set_trace; set_trace()


a = np.arange(10)
print a
print a[8]
print a[9]

for i in a:
    print i
    
print a[10]

Para iniciar la depuración se ejecuta python haciendo una llamada al módulo de pudb.run y luego se pasa el script a depurar:
python -m pudb.run arreglo.py

En la siguiente figura se muestra la interfaz de pudb.
En la ventana principal se muestra el código del script, en la primera ventana lateral superior se muestra las variables, la siguiente ventana inferior se muestra el stack y por último los breakpoints. 

Para ver las opciones existentes de la interfaz se presiona a la tecla "?". En la siguiente figura se muestra las opciones.
Opciones:
  • Control+p: Permite editar las preferencias de la interfaz.
  • n (next): Permite saltar a la siguiente instrucción del código.
  • c (continue):  Continua a la siguiente instrucción.
  • s (step into): Salta a la siguiente instrucción.
  • r/f:: Finaliza la función actual.
  • t: Corre al cursor.
  • e: Muestra el traceback (postmorten o en el estado de una excepción).
  • H: Mueve a la línea actual (al final del stack). 
  • u: Mueve hacia arriba en el stack
  • d: Mueve hacia abajo en el stack
  • !: Invoca un interprete de comandos python en el actual ambiente.
  • o: Muestra la consola o la salida de la pantalla.
  • b: Conmuta los breakpoints.
  • q: Salir/reiniciar el script.
En la siguiente figura se muestra las preferencias:


La siguiente figura muestra a el depurador en funcionamiento.


6 dic. 2012

Depurar código python con ipython.

En los 3 artículos anteriores se explicó como realizar profiling con 3 herramientas (timeit, line_profiler y cProfile). Ahora se explicará como depurar código python.

En este caso se explicará la depuración de código utilizando ipython.
ipython aparte de permitir hacer profiling también permite realizar depuración de código.

Se mostrará el código de la generación de un arreglo, luego se muestra en pantalla el arreglo, el valor de un elemento de ese arreglo y el recorrido de los elementos del arreglo:


#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa numpy como np

import numpy as np







a = np.arange(10)

print a

print a[8]

print a[9]



for i in a:

    print i

print a[10]

El resultado de ejecutar el script es el siguiente:


ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/debugipython$ python arreglo.py 

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

8

9

0

1

2

3

4

5

6

7

8

9

Traceback (most recent call last):

  File "arreglo.py", line 17, in <module>

    print a[10]

IndexError: index out of bounds

Se muestra el mensaje de error de tratar de presentar en pantalla el valor del elemento 10 del arreglo a (que no existe).


Se ejecuta a continuación ipython desde la consola Linux, ipython entra en un interprete interactivo, ahí se ejecuta el comando %run script.py. Esto hace que se inicie el modo depuración con el script que se pasa como argumento:




ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/debugipython$ ipython

Python 2.7.3rc2 (default, Apr 22 2012, 22:35:38) 

Type "copyright", "credits" or "license" for more information.



IPython 0.13.1 -- An enhanced Interactive Python.

?         -> Introduction and overview of IPython's features.

%quickref -> Quick reference.

help      -> Python's own help system.

object?   -> Details about 'object', use 'object??' for extra details.



In [1]: %run arreglo.py

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

8

9

0

1

2

3

4

5

6

7

8

9

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
/usr/lib/python2.7/dist-packages/IPython/utils/py3compat.pyc in execfile(fname, *where)
    176             else:
    177                 filename = fname
--> 178             __builtin__.execfile(filename, *where)
/home/ecrespo/proyectos/ernesto-ecrespo.blogspot/debugipython/arreglo.py in <module>()
     15     print i
     16 
---> 17 print a[10]
IndexError: index out of bounds




El resultado es la ejecución del script y señala la sección donde se tiene el error.

Ahora se ejecuta el comando %debug:
In [2]: %debug
> /home/ecrespo/proyectos/ernesto-ecrespo.blogspot/debugipython/arreglo.py(17)<module>()
     15     print i
     16 
---> 17 print a[10]

Se ve claramente que el error se encuentra en la línea 17 del script, donde se trata de mostrar en pantalla el elemento 10 del arreglo el cual no existe.

Se puede listar el código al ejecutar el comando list:
ipdb> list
     12 print a[9]
     13 
     14 for i in a:
     15     print i
     16 
---> 17 print a[10]

Evaluar código. Se puede ejecutar una instrucción python para evaluar el tamaño del arreglo.
ipdb> len(a)
10

Se muestra en pantalla el valor de a:
ipdb> print a
[0 1 2 3 4 5 6 7 8 9]

Ver las llamadas del stack:Es un stack que contiene información acerca de las funciones activas de un programa en ejecución. El comando para ver el stack es bt:
ipdb> bt
  /usr/lib/python2.7/dist-packages/IPython/utils/py3compat.py(178)execfile()
    174             if isinstance(fname, unicode):
    175                 filename = fname.encode(sys.getfilesystemencoding())
    176             else:
    177                 filename = fname
--> 178             __builtin__.execfile(filename, *where)
> /home/ecrespo/proyectos/ernesto-ecrespo.blogspot/debugipython/arreglo.py(17)<module>()
     13 
     14 for i in a:
     15     print i
     16 
---> 17 print a[10]

Moverse hacia arriba en el stack se logra al ejecutar el comando u:
ipdb> u
> /usr/lib/python2.7/dist-packages/IPython/utils/py3compat.py(178)execfile()
    176             else:
    177                 filename = fname
--> 178             __builtin__.execfile(filename, *where)

Para moverse hacia abajo en el stack se logra al ejecutar el comando d:
ipdb> d
> /home/ecrespo/proyectos/ernesto-ecrespo.blogspot/debugipython/arreglo.py(17)<module>()
     15     print i
     16 
---> 17 print a[10]



Para salirse del modo depuración se ejecuta el comando exit, para salirse del interprete de comandos ipython se vuelve a ejectuar exit.

Se tiene claro que el error en el script se encuentra en la línea 17, donde se trata de imprimir en pantalla un elemento del arreglo que no existe.

De esta forma se puede realizar depuración de código python con ipython. 

5 dic. 2012

Profiling de un script python con cProfile

Continuando con los artículos sobre profiling, otra herramienta para llevar adelante el profiling se llama cProfile, esta es una extensión en C que se introdujo en Python 2.5. Se usa para determinar Profiling deterministico (se mide el tiempo de manera precisa en vez de muestreo).

El ejemplo que se desarrollará es el mismo de la generación de la matriz inversa, la diferencia será que la definición del valor de n de la matriz y su generación estará en una función llamada main. Está función luego se llamará desde cProfile.

A continuación el código:

#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa numpy como np

import numpy as np



import cProfile

import sys





#funcion que genera matrices inversas.

def Inversa(n):

    a = np.matrix(np.random.rand(n, n))

    return a.I



    

def main():

    

    #Se define una lista de  tamaños de la matriz

    tamagno = 2**np.arange(0,12)

    #Se recorre la lista de tamaños y se invierte cada matriz con la

    #funcion.

    for n in tamagno:

        Inversa(n)



#Se ejecuta la función main desde la llamada run de cProfile.    

cProfile.run('main()')

Al ejecutar el script se tiene como resultado el profiling de la generación de la matriz inversa:

ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/cprofile$ python matrizinversa.py 

         809 function calls in 5.764 seconds



   Ordered by: standard name



   ncalls  tottime  percall  cumtime  percall filename:lineno(function)

        1    0.000    0.000    5.764    5.764 <string>:1(<module>)

       24    0.000    0.000    0.058    0.002 defmatrix.py:233(__new__)

       36    0.000    0.000    0.000    0.000 defmatrix.py:279(__array_finalize__)

       12    0.000    0.000    0.000    0.000 defmatrix.py:55(asmatrix)

       12    0.015    0.001    5.467    0.456 defmatrix.py:808(getI)

        1    0.000    0.000    0.000    0.000 dual.py:12(<module>)

       12    0.000    0.000    0.000    0.000 linalg.py:127(_to_native_byte_order)

       12    0.000    0.000    0.257    0.021 linalg.py:139(_fastCopyAndTranspose)

       12    0.000    0.000    0.000    0.000 linalg.py:151(_assertRank2)

       12    0.000    0.000    0.000    0.000 linalg.py:157(_assertSquareness)

       12    0.000    0.000    5.396    0.450 linalg.py:244(solve)

       12    0.020    0.002    5.452    0.454 linalg.py:404(inv)

       36    0.000    0.000    0.000    0.000 linalg.py:66(_makearray)

       36    0.000    0.000    0.000    0.000 linalg.py:71(isComplexType)

       24    0.000    0.000    0.000    0.000 linalg.py:84(_realType)

       12    0.000    0.000    0.000    0.000 linalg.py:99(_commonType)

       12    0.007    0.001    5.751    0.479 matrizinversa.py:12(Inversa)

        1    0.013    0.013    5.764    5.764 matrizinversa.py:17(main)

       36    0.000    0.000    0.000    0.000 numeric.py:167(asarray)

       12    0.000    0.000    0.035    0.003 numeric.py:1830(identity)

       36    0.000    0.000    0.000    0.000 {getattr}

       72    0.000    0.000    0.000    0.000 {isinstance}

       60    0.000    0.000    0.000    0.000 {issubclass}

       60    0.000    0.000    0.000    0.000 {len}

       12    0.000    0.000    0.000    0.000 {max}

       24    0.000    0.000    0.000    0.000 {method '__array_prepare__' of 'numpy.ndarray' objects}

       24    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}

       12    0.059    0.005    0.059    0.005 {method 'astype' of 'numpy.ndarray' objects}

       12    0.058    0.005    0.058    0.005 {method 'copy' of 'numpy.ndarray' objects}

        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

       24    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}

       12    0.220    0.018    0.220    0.018 {method 'rand' of 'mtrand.RandomState' objects}

       12    0.000    0.000    0.000    0.000 {method 'transpose' of 'numpy.ndarray' objects}

       12    0.000    0.000    0.000    0.000 {method 'view' of 'numpy.ndarray' objects}

       12    0.000    0.000    0.000    0.000 {min}

       24    0.257    0.011    0.257    0.011 {numpy.core.multiarray._fastCopyAndTranspose}

        1    0.000    0.000    0.000    0.000 {numpy.core.multiarray.arange}

       36    0.000    0.000    0.000    0.000 {numpy.core.multiarray.array}

       24    0.035    0.001    0.035    0.001 {numpy.core.multiarray.zeros}

       12    5.078    0.423    5.078    0.423 {numpy.linalg.lapack_lite.dgesv}

La salida es la misma que se mostró en el artículo de timeit con ipython.  Como adicional al artículo de timeit se muestra a continuación el significado de cada columna:

  • ncalls: Número de llamadas.
  • tottime: Tiempo total gastado en una función.
  • percall: Tiempo por llamada, calculado el tiempo total la cantidad de llamadas.
  • cumtime: Tiempo acumulado gastado en una función y llamadas a funciones por la función, incluyendo llamadas recursivas.
El código del script lo pueden bajar desde bitbucket.


4 dic. 2012

Profiling de un script python con line_profiler

Ahora se mostrará el uso de la herramienta line_profiler para hacer profiling de programas Python.

Lo primero que se tiene que hacer es instalar line_profiler con el comando easy_install o pip:
easy_install line_profiler



pip install line_profiler





El código que se va a revisar es el mismo del artículo anterior (matriz inversa). La diferencia es que se define en la función que genera la matriz inversa el uso del decorador profile.

El código es el siguiente:

#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa numpy como np

import numpy as np



#Se define el uso del decorador profile. En la funcion que genera matrices inversas.

@profile

def Inversa(n):

    a = np.matrix(np.random.rand(n, n))

    return a.I



#Se define una lista de  tamaños de la matriz

tamagno = 2 ** np.arange(0, 12)





#Se recorre la lista de tamaños y se invierte cada matriz con la

#funcion.

for n in tamagno:

    Inversa(n)

Para realizar el profiling se ejecuta el siguiente comando:


ecrespo@jewel:~/proyectos/ernesto-ecrespo.blogspot/line_profiler$ kernprof.py -l -v  matrizinversa.py 

Wrote profile results to matrizinversa.py.lprof

Timer unit: 1e-06 s



File: matrizinversa.py

Function: Inversa at line 10

Total time: 6.05993 s



Line #      Hits         Time  Per Hit   % Time  Line Contents

==============================================================

    10                                           @profile

    11                                           def Inversa(n):

    12        12       260506  21708.8      4.3      a = np.matrix(np.random.rand(n, n))

    13        12      5799424 483285.3     95.7      return a.I

El resultado muestra el tiempo total de ejecución, Luego muestra cada línea de ejecución, y el porcentaje de tiempo que se ha ejecutado. Se nota que la generación de la matriz no tarda mucho, pero al invertirla si un 95,7% del tiempo de ejecución.
El significado de cada parámetro es:
Line: Es el número de línea en el archivo.
Hits: Es el número de veces que la línea se ejecuta.
Time: Tiempo que gasta al ejecutar cada línea.
Per Hit: Tiempo promedio que se gasta al ejecutar cada línea.
% Time: Porcentaje de tiempo que se gasta al ejecutar la línea relativo al tiempo que se gasta en ejecutar todas las líneas.

El código del script se encuentra en bitbucket.

3 dic. 2012

Profiling de un script python con timeit

El profiling permite conocer el tiempo que consume un programa en ejecutarse e incluso conocer cuanto tarda cada llamada de funciones de distintos módulos utilizados.

El ejemplo que se hará es un script que tiene una función donde se le pasa el tamaño de la matriz NxN  generada de forma aleatoria y devuelve la matriz inversa. Luego se genera una lista de tamaños de forma aletoria. Se crea un ciclo recorriendo la lista donde dentro del ciclo se crea la matriz inversa.

El script se guarda con nombre ej4.py .
El código del script se muestra a continuación:

#!/usr/bin/env python

# -*- coding: utf-8 -*-



#Se importa numpy como np

import numpy as np





#Se crea la función que invierte una matriz de valores aleatorios

def Inversa(n):

    a = np.matrix(np.random.rand(n, n))

    return a.I



#Se define una lista de  tamaños de la matriz

tamagno = 2 ** np.arange(0, 12)





#Se recorre la lista de tamaños y se invierte cada matriz con la

#funcion.

for n in tamagno:

    Inversa(n)



Desde la consola se ejecuta  ipython --pylab . Luego se ejecuta el comando %run -t ej4.py el cual devuelve el tiempo de ejecución en la capa de usuario, capa de sistema. Luego se ejecuta %run -p ej4.py el cual devuelve los tiempos de ejecución de cada función.


ecrespo@jewel:~/bin/python/matplotlib$ ipython --pylab

Python 2.7.3rc2 (default, Apr 22 2012, 22:35:38) 

Type "copyright", "credits" or "license" for more information.



IPython 0.13.1 -- An enhanced Interactive Python.

?         -> Introduction and overview of IPython's features.

%quickref -> Quick reference.

help      -> Python's own help system.

object?   -> Details about 'object', use 'object??' for extra details.



Welcome to pylab, a matplotlib-based Python environment [backend: TkAgg].

For more information, type 'help(pylab)'.



In [1]:  %run -t ej4.py



IPython CPU timings (estimated):

  User   :       8.79 s.

  System :       0.13 s.

Wall time:       6.17 s.

In [2]:  850 function calls in 6.201 seconds
   Ordered by: internal time
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       12    5.556    0.463    5.556    0.463 {numpy.linalg.lapack_lite.dgesv}
       24    0.236    0.010    0.236    0.010 {numpy.core.multiarray._fastCopyAndTranspose}
       12    0.229    0.019    0.229    0.019 {method 'rand' of 'mtrand.RandomState' objects}
       12    0.058    0.005    0.058    0.005 {method 'astype' of 'numpy.ndarray' objects}
       12    0.042    0.004    0.042    0.004 {method 'copy' of 'numpy.ndarray' objects}
       24    0.036    0.001    0.036    0.001 {numpy.core.multiarray.zeros}
       12    0.022    0.002    5.909    0.492 linalg.py:404(inv)
        1    0.012    0.012    6.200    6.200 ej4.py:5(<module>)
       12    0.006    0.001    6.188    0.516 ej4.py:9(Inversa)
        1    0.001    0.001    6.201    6.201 {execfile}
       12    0.000    0.000    5.851    0.488 linalg.py:244(solve)
        1    0.000    0.000    6.201    6.201 interactiveshell.py:2390(safe_execfile)
       12    0.000    0.000    0.036    0.003 numeric.py:1830(identity)
       12    0.000    0.000    5.909    0.492 defmatrix.py:808(getI)
       12    0.000    0.000    0.000    0.000 linalg.py:99(_commonType)
        1    0.000    0.000    0.000    0.000 {open}
       24    0.000    0.000    0.043    0.002 defmatrix.py:233(__new__)
       36    0.000    0.000    0.000    0.000 defmatrix.py:279(__array_finalize__)
       36    0.000    0.000    0.000    0.000 linalg.py:66(_makearray)
       36    0.000    0.000    0.000    0.000 {numpy.core.multiarray.array}
       12    0.000    0.000    0.236    0.020 linalg.py:139(_fastCopyAndTranspose)
       24    0.000    0.000    0.000    0.000 {method '__array_prepare__' of 'numpy.ndarray' objects}
       12    0.000    0.000    0.000    0.000 {method 'view' of 'numpy.ndarray' objects}
       12    0.000    0.000    0.000    0.000 linalg.py:127(_to_native_byte_order)
       75    0.000    0.000    0.000    0.000 {isinstance}
       36    0.000    0.000    0.000    0.000 numeric.py:167(asarray)
       12    0.000    0.000    0.000    0.000 defmatrix.py:55(asmatrix)
       12    0.000    0.000    0.000    0.000 {method 'transpose' of 'numpy.ndarray' objects}
 60    0.000    0.000    0.000    0.000 {issubclass}
       12    0.000    0.000    0.000    0.000 linalg.py:151(_assertRank2)
       36    0.000    0.000    0.000    0.000 linalg.py:71(isComplexType)
       12    0.000    0.000    0.000    0.000 linalg.py:157(_assertSquareness)
       24    0.000    0.000    0.000    0.000 linalg.py:84(_realType)
       36    0.000    0.000    0.000    0.000 {getattr}
       61    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 posixpath.py:312(normpath)
        1    0.000    0.000    0.000    0.000 posixpath.py:118(dirname)
       30    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 syspathcontext.py:55(__enter__)
        1    0.000    0.000    0.000    0.000 {posix.getcwdu}
       12    0.000    0.000    0.000    0.000 {max}
       24    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {numpy.core.multiarray.arange}
        1    0.000    0.000    6.201    6.201 py3compat.py:173(execfile)
        1    0.000    0.000    0.000    0.000 posixpath.py:341(abspath)
        1    0.000    0.000    0.000    0.000 syspathcontext.py:62(__exit__)
       12    0.000    0.000    0.000    0.000 {min}
        1    0.000    0.000    0.000    0.000 posixpath.py:60(join)
        5    0.000    0.000    0.000    0.000 {method 'startswith' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        1    0.000    0.000    6.201    6.201 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'encode' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 {method 'split' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 {method 'rfind' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 posixpath.py:249(expanduser)
        1    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        1    0.000    0.000    0.000    0.000 {method 'remove' of 'list' objects}
        1    0.000    0.000    0.000    0.000 posixpath.py:51(isabs)
        1    0.000    0.000    0.000    0.000 {method 'insert' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'rstrip' of 'unicode' objects}
        2    0.000    0.000    0.000    0.000 {method 'setdefault' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 syspathcontext.py:52(__init__)
        1    0.000    0.000    0.000    0.000 {method 'join' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 {method 'endswith' of 'unicode' objects}
        1    0.000    0.000    0.000    0.000 {sys.getfilesystemencoding}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
El código lo pueden encontrar en bitbucket.