Tabla de Contenidos

Ejemplos de referencia (módulos y tareas)



Python para gente que ya conoce la sintaxis de python

Este documento está pensado para aquellos programadores con conocimientos de programación en múltiples lenguajes que se han acercado a python y, rápidamente, han aprendido todos los rasgos generales de la sintaxis y son capaces de escribir programas sencillos en este lenguaje.

Si este es tu caso (como lo fue el mío) es posible que te hayas entusiasmado con Python y, rápidamente, hayas comenzado a escribir programas en este lenguaje. Y cada vez que lees tutoriales, artículos o libros nuevos sobre python, descubres que estás haciendo las cosas tal y como se harían en otros lenguajes pero no tal y como se hacen en python (de forma "pythónica"). En mi caso, me he visto forzado a reescribir varias veces porciones de código de programas ya acabados cada vez que descubría que ya existía un módulo o método para hacer algo y que yo lo estaba reinventando vía "código propio".

También me he encontrado, a mitad del desarrollo, con la necesidad de retocar los comentarios o la documentación porque no se adaptaban a los estándares de las docstrings, los cuales, si hubiera seguido desde el principio, me habrían permitido generar la documentación del programa de forma automática. Y eso sin hablar de cuando descubro, a posteriori, utilísimos métodos o propiedades de los objetos que desconocía y que simplificaban mucho el desarrollo.

No habría tenido que reescribir código ni documentación si desde el principio hubiera tenido una guía "básica" de "Python para alguien que ya sabe la sintaxis básica de python" y que me dijera cómo se han de escribir las docstrings, cómo se recogen y parsean los argumentos de línea de comandos, qué métodos me permiten hacer una determinada tarea, cómo hacer depuración de mis programas con el debugger de python, etc.

Y para eso es este pequeño documento: no permite aprender python a quien no sabe python, porque no habla de su sintaxis y asume que el lector ya la conoce, pero sí puede permitir descubrir a quien sabe python cómo hacer ciertas tareas de la forma correcta.

<!-- - re.findall, re.sub - datetime.* - zlib.* - timeit.Timer - xml.dom y xml.sax - textwrap.fill, textwrap.wrap - threading - logging - from collections import deque   -->






Formateo de cadenas de texto

En Python 3.x (y de 2.6 en adelante), print ha dejado de ser una sentencia del lenguaje para pasar a ser una función, por lo que deberemos utilizar los correspondientes parámetros alrededor del texto al imprimir, para convertirlo en un argumento:

print "Hola"    --->    print("Hola")

Además de este cambio, ha variado la sustitución de parámetros dentro de las cadenas. El método antiguo seguirá estando soportado pero se recomienda migrar a los métodos nuevos:


>>> print('El valor de %s es aproximadamente %2.5f.' % ("PI", math.pi) )


>>> print( 'Mas vale {} que {}.'.format('prevenir', 'lamentar') )
Mas vale prevenir que lamentar.
 
>>> print('{0} y {1}'.format('esto', 'aquello'))
esto y aquello
 
>>> print('{1} y {0}'.format('esto', 'aquello'))
aquello y esto


>>> print('Esta {comida} es {adjetivo}.'.format(
          comida='manzana', adjetivo='deliciosa'))
Esta manzana es deliciosa.
 
>>> print('Me gusta {0}, {1}, y {otro}.'.format(
          'python', 'C', otro='PHP'))
Me gusta python, C y PHP.


>>> agenda = {'Juan': 12345, 'Jose': 22222}
>>> for nombre, telefono in agenda.items():
       print('{0:10} ==> {1:10d}'.format(nombre, telefono))
Juan       ==>       12345
Jose       ==>       22222


>>> agenda = {'Juan': 12345, 'Jose': 22222}
 
>>> print('Juan: {0[Juan]:d}; Jose: {0[Jose]:d}; '.format(agenda))
Juan: 12345; Jose: 22222;
 
# Nota: {0 hace referencia al primer parámetro en format(), es decir, "agenda",
# y entre corchetes se coloca la clave a imprimir, seguida de :d (el tipo).
 
>>> print('Juan: {Juan:d}; Jose: {Jose:d};'.format(**agenda))
Juan: 12345; Jose: 22222;
 
# Nota: Directamente usamos la clave para obtener el valor desde "agenda".


Características y sintaxis del lenguaje


Comprensión de listas (List comprehensions)

En Python 3.x se han eliminado las sentencias map, filter y reduce como "sentencias" del lenguaje y pasarán a ser parte del módulo functools. Python 3 entiendo como más correcta la utilización de Comprensión de listas (list comprehensions) como las siguientes:

Para map(), cambiamos:

>>> def cuadrado(n): return n** 2
>>> lista = [ 1, 2, 3, 4, 5 ]
>>> lista2 = map( cuadrado, lista )

por:

>>> lista2 = [ n**2 for n in lista ]

Para filter(), cambiamos:

>>> def es_par(n): return (n % 2.0 == 0)
>>> lista = [ 1, 2, 3, 4, 5 ]
>>> lista2 = filter( es_par, lista )

Por:

>>> lista2 = [ n for n in lista if n % 2.0 == 0 ]


Expresiones y Funciones Generadoras

Las expresiones generadoras son muy útiles: son similares las list comprehensions pero en lugar de devolver una lista devuelven un objeto sobre el que se puede iterar. La sintaxis de los generadores es la misma que la de la Comprensión de listas pero usando paréntesis en vez de corchetes:

>>> lista = [ 1, 2, 3, 4, 5 ]
>>> listac = [ n**2 for n in lista ]
>>> listac
[ 1, 4, 9, 16, 25 ]
 
>>> lista2 = ( n**2 for n in lista )
>>> lista2
<generator object at 0×00E33210>

En el anterior ejemplo, podemos ahora iterar "lista2" con una construcción del tipo "for … in".

Por otra parte, las funciones generadoras son funciones que generan valores sobre los que iterar, utilizando la palabra clave yield en vez de return:

def mi_generador(n, m, s):
   while(n <= m):
      yield n
      n += s
 
# Generador de numeros pares:
 
>>> for numero in mi_generador(0, 100, 2): 
       print numero

La principal ventaja de este sistema es que no tenemos una lista completa en memoria sino sólo un mecanismo para continuar generando valores de la misma, ocupando sólo la memoria necesaria para la generación / iteración de cada elemento. De todas formas, podemos convertir un generador en lista mediante list():


Iteradores

Muchos de los objetos en python pueden ser iterados en un bucle de tipo for … in. Para que un objeto sea iterable debe implementar dos métodos:



Así pues, para que nuestros objetos sean iterables con for/in, basta con que implementemos iter() y next(). El control de la posición de la iteración la podemos realizar mediante índices "privados" o bien volviendo de next() con un yield.

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
 
    def __iter__(self):
        return self
 
    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

Mediante los generadores podremos crear iteradores fácilmente:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

Los generadores implementan (al usar yield) los métodos iter() y next() de forma automática. Además nos evitarn la utilización de índices como "self.index" en el ejemplo anterior, ya que yield mantiene los valores locales de las variables y la ejecución entre iteraciones, por lo que las funciones resultantes son más sencillas y cortas.


Decoradores

Un decorador es una función que "decora" a otra. Técnicamente, recibe como parámetro una función, y devuelve como parámetro otra: Por ejemplo, el siguiente decorador permite imprimir por pantalla el nombre de las funciones que son llamadas:

>>> def mi_decorador(funcion):
       def nueva(*args):
          print "Llamada a la funcion", funcion.__name__
          retorno = funcion(*args)
          return retorno
       return nueva
 
>>> def hola(x):
       print "hola", x
 
>>> p = mi_decorador(hola)
 
>>> p("PRUEBA")
Llamada a la funcion hola
hola PRUEBA

Desde la versión 2.4 de python, se puede automatizar la asignación del decorador con una línea @nombre_del_decorador antes de la función:

>>> @mi_decorador
    def hola(x):
       print "hola", x
 
>>> hola("prueba")
Llamada a la funcion hola
hola prueba

Se pueden especificar múltiples decoradores, especificando uno en cada línea antes del nombre de la función, y se ejecutarían en el orden en que son listados.


Partir líneas largas en el código fuente

En python se recomienda limitar el ancho de línea a un máximo de 79 caracteres, para mantener los listados legibles y hacerlos compatibles con determinados editores y terminales. Cuando tenemos una sentencia más larga de 79 caracteres, hay varias maneras de "partirla".

La forma preferida según el PEP 8 – Style Guide for Python Code es aprovechar que python asume continuación de líneas automática en expresiones entre paréntesis, llaves y corchetes:

a = do_stuff( argument1, argument2, argument3, argument4,
              argument5, argument6 )
 
(...)
 
a = ('1' + '2' + '3' +
     '4' + '5')
 
 
(...)
if b = ((i1 < 20) and
        (i2 < 30) and
        (i3 < 40)):
   blah()
 
(...)
    class Rectangle(Blob):
 
        def __init__(self, width, height,
                     color='black', emphasis=None, highlight=0):
 
            if (width == 0 and height == 0 and
                color == 'red' and emphasis == 'strong' or
                highlight > 100):
                raise ValueError("sorry, you lose")
            (...)

Otra opción es la de utilizar la barra invertida para finalizar la línea, pero se recomienda la opción anterior. La barra invertida puede dar lugar a errores cuando se inserta un espacio tras la misma, rompiendo la continuación:

   if a = True and \
      b = False:
      blah()         

Finalmente, muchas veces nos encontramos con la necesidad de partir cadenas de texto muy largas para hacer el programa más legible y no exceder el total de 79 caracteres por línea. En ese caso podemos aprovechar la unión implícita que pytho hace en tiempo de compilación de 2 cadenas consecutivas:

# Usando la barra invertida y contatenación de cadenas:
def fun():
    return '{0} Here is a really long ' \
           'sentence with {1}'.format(3, 5)
 
# Aprovechando los paréntesis para evitar la barra invertida          
def fun():
    return ( '{0} Here is a really long'
             ' sentence with {1}'.format(3, 5) )
 
texto = ('Se pueden crear cadenas mas largas a partir '
        'de varias cadenas cortas y los parentesis.')

Al respecto de la concatenación automática de cadenas, la documentación oficial dice esto:

2.4.2. String literal concatenation

Multiple adjacent string literals (delimited by whitespace), possibly using
different quoting conventions, are allowed, and their meaning is the same
as their concatenation. Thus, "hello" 'world' is equivalent to "helloworld".
This feature can be used to reduce the number of backslashes needed, to
split long strings conveniently across long lines, or even to add comments
to parts of strings, for example:

re.compile("[A-Za-z_]"       # letter or underscore
           "[A-Za-z0-9_]*"   # letter, digit or underscore
          )
          
Note that this feature is defined at the syntactical level, but implemented
at compile time. The ‘+’ operator must be used to concatenate string expressions
at run time. Also note that literal concatenation can use different quoting
styles for each component (even mixing raw strings and triple quoted strings).


Número de argumentos variables en funciones

Podemos pasar argumentos variables a funciones python de 2 formas:



Por ejemplo:

>>> def func( *var ):
      for v in var:
         print v
 
>>> func( 1 )
1
 
>>> func( 1, "prueba", 4, 5 )
1
prueba
4
5
 
>>> def func( **var ):
       for k, v in var.items():
          print "%s=%s" % (k,v)
 
>>> func( valor1="prueba", valor2=23, exitcode=0 )
valor1=prueba
valor2=23
exitcode=0

Además de los argumentos variables, podemos pasar argumentos "estándar" antes de los variables:


Getters, Setters y Deleters (property) en las clases de nuevo estilo

Cuando creamos clases en Python, solemos tener que definir métodos del tipo "get_" y "set_" para poder modificar los atributos del objeto que requieren lógica adicional (comprobaciones de rangos, acciones adicionales a una simple asignación, etc).

fget is a function for getting an attribute value, likewise fset is a function for setting, and fdel a function for del’ing, an attribute. Typical use is to define a managed attribute x:

En las últimas versiones de Python, se pueden crear clases "de nuevo estilo", haciéndolas heredar del objeto "object". Esto nos permite, por ejemplo, utilizar la nueva función property() para definir getters, setters y deleters y poder usar los atributos directamente con el objeto:



Por ejemplo:

class CoordenadaX(object):
   def __init__(self):
      self._x = None
 
   def getx(self):
      return self._x
 
   def setx(self, value):
      if value <= 100:
         self._x = value
      else:
         self._x = 100
 
   def delx(self):
      del self._x
 
   x = property(getx, setx, delx, "I'm the 'x' property.")
 
(...)
 
 
>>> mi_X = CoordenadaX()
 
>>> mi_X.x = 25
>>> print mi_X.x
25
 
>>> mi_X.x = 1000
>>> print mi_X.x
100

Cuando hacemos uso de estas asignaciones o lecturas (set y get), no lo hacemos directamente sobre _x sino sobre las funciones get y set, por lo que si estas tienen que tener lógica adicional, se ejecuta (como la limitación de X⇐100 de nuestro ejemplo).

No estamos obligados a declarar setter, deleter y docstring. Si no se especifica, el docstring se hereda del docstring del getter, y si no hay setter y deleter se considera que la propiedad es sólo lectura y no puede ser modificada, sólo leída.

Por otra parte, y para simplificar, si sólo necesitamos getters, podemos utilizar el decorador "@property":

class CoordenadaX(object):
    def __init__(self):
        self._x = None
 
    @property
    def getx(self):
       """I'm the 'x' property."""
       return self._x

Desde la versión 2.6, se pueden utilizar decoradores para definir los getters, setters y deleters sin usar "property":

class CoordenadaX(object):
    def __init__(self):
        self._x = None
 
    @property
    def getx(self):
        """I'm the 'x' property."""
       return self._x
 
    @x.setter
    def setx(self, value):
       self._x = value
 
    @x.deleter
    def delx(self):
       del self._x


Serializar uno o más objetos

Python permite serializar objetos, es decir, almacenar objetos "python" de memoria en disco, y recuperarlos, mediante los módulos pickle y cPickle (en C, más rápido y eficiente). cPickle es más rápido que pickle pero no podemos asumir que esté presente en todos los sistemas, por lo que podemos importar el módulo correcto de la siguiente forma:

try:
   import cPickle as pickle
except ImportError:
   import pickle

Una vez hecho esto, podemos hacer uso de:



Hay que recordar el hacer el open() del fichero y su correspondiente close() antes y después de usar estas funciones.

El módulo shelve permite extender las funciones de pickle para facilitar el uso de pickle cuando leemos y escribimos gran cantidad de objetos y queremos acceder sólo a algunos de ellos o direccionarlos fácilmente. La función shelve.open() devuelve un objeto Shelf que podemos usar para almacenar y recuperar objetos (soportando los mismos métodos que un diccionario, pero usando como claves sólo cadenas). De esta forma tenemos facilidad para acceder a elementos concretos. Debemos recordar ejecutar .close() en el objeto al acabar de acceder a los datos.

import shelve
   lista1 = ["uno", "dos", "tres"]
   lista2 = ["rojo", "verde", "amarillo"]
   shelf = shelve.open(“datos.dat)
   shelf["numeros"] = lista1
   shelf["colores"] = lista2
   print shelf["numeros"]
   shelf.close()



Acceso a ficheros y directorios


Existencia / tipos de ficheros y su tamaño

El módulo os.path proporciona funciones para comprobar el tipo de fichero o la existencia de una ruta concreta.




El módulo os proporciona funciones para crear directorios, cambiar permisos de ficheros, etc:



Para las funciones que requieren indicar uids y gids (chmod, chown, etc), puede que necesitemos obtener de /etc/passwd el uid/gid numérico que se corresponden con un usuario/grupo especificado como cadena. En ese caso debemos usar los módulos pwd y grp, los cuales nos permiten consultar la BBDD de usuarios y obtener una tupla de 7 elementos con los siguientes datos:



Las funciones son:




Generar / extraer componentes de una ruta (path)

El módulo os.path proporciona funciones para crear y partir rutas a ficheros y directorios:



Ejemplos:

>>> import os, os.path
 
>>> path="/home/sromero/prueba.txt"
 
>>> os.path.basename(path)
'prueba.txt'
 
>>> os.path.dirname(path)
'/home/sromero'
 
>>> os.path.abspath(path)
'/home/sromero/prueba.txt'
 
>>> os.path.abspath("/home/../home/sromero/prueba.txt")
'/home/sromero/prueba.txt'
 
>>> os.path.join("/home/../home/sromero/", "/home/sromero/directorio/")
'/home/sromero/directorio/'
 
>>> os.path.split(path)
('/home/sromero', 'prueba.txt')
 
>>> os.path.splitdrive(path)
('', '/home/sromero/prueba.txt')
 
>>> os.path.splitext(path)
('/home/sromero/prueba', '.txt')


Obtener el listado de ficheros de un directorio (módulo glob)

El módulo glob permite obtener fácilmente el listado de ficheros del directorio actual, soportando patrones estilo shell. Llamando a glob.glob obtenemos una lista de los ficheros del directorio actual que cumplen con el patrón dado. Usando glob.iglob, obtenemos un objeto iterador (para recorrer la lista sin mantener toda la lista de ficheros en memoria):

>>> import glob
 
>>> glob.glob('*.gif')
['1.gif', 'card.gif']
 
>>> glob.glob('./[0-9].*')
['./1.gif', './2.txt']
 
>>> glob.glob('?.gif')
['1.gif']


Recorrer todos los ficheros y directorios a partir de una ruta

Podemos utilizar la función os.walk() para recorrer recursivamente una ruta del disco:



Ejemplo:

>>> import os
>>> path = "/home/sromero/prog/code"
>>> 
>>> for (path, dirs, files) in os.walk(path):
        print("----")
        print(path)
        print(dirs)
        print(files)
 
----
/home/sromero/prog/code
['pcmaziacs-0.8.7', 'mansion']
['pcmaziacs-0.8.7.tar.gz']
----
/home/sromero/prog/code/pcmaziacs-0.8.7
['data']
['main.c', 'primitives.c', 'TODO', 'main.h', 'LICENSE', (...etc...)]
----
/home/sromero/prog/code/pcmaziacs-0.8.7/data
[]
['sword.wav', 'helvet.def', 'game.mod', (...etc...)]

Se le puede indicar un parámetro booleano topdown que indica cuándo obtenemos la 3-tupla del directorio padre de cada subprocesamiento, si antes que los hijos (True, por defecto) o después de los hijos (False). Este valor a False puede servir para procesados como, por ejemplo, un borrado recursivo ya que no podemos borrar el padre hasta haber borrado los directorios hijos.

Un tercer parámetro booleano opcional, followlinks que indique a os.walk si debe o no entrar en los directorios apuntados por enlaces simbólicos en los sistemas de ficheros que lo soporten (hay que tener en cuenta que esta opción podría dar lugar a recursión infinita si hay enlaces cruzados).

Si necesitamos obtener sólo los datos de un nivel de directorios, sin recursividad, tenemos varias opciones. La primera es utilizar os.listdir():

>>> import os
 
>>> root = "/home/sromero/"
 
>>> def walk_one_step( root ):
      dirs = []
      files = []
      for item in os.listdir(root):
         if os.path.isfile(os.path.join(root, item)):
            files.append(item)
         elif os.path.isdir(os.path.join(root, item)):
            dirs.append(item)
      return ( root, dirs, files )
 
>>> walk_one_step("/home/sromero/prog/code")
('/home/sromero/prog/code', ['pcmaziacs-0.8.7', 'mansion'], ['pcmaziacs-0.8.7.tar.gz'])

La segunda es utilizar el iterador os.walk().next() para hacer un sólo paso:

>>> path, dirs, files = os.walk( "/home/sromero/prog/code" ).next()
 
>>> path, dirs, files
('/home/sromero/prog/code', ['pcmaziacs-0.8.7', 'mansion'], ['pcmaziacs-0.8.7.tar.gz'])

Finalmente, la siguiente función manipula la salida de os.walk() al vuelo para permitir recorrer sólo hasta un nivel de profundidad deseado:

import os 
def walklevel(some_dir, level=1): 
    some_dir = some_dir.rstrip(os.path.sep) 
    assert os.path.isdir(some_dir) 
    num_sep = len([x for x in some_dir if x == os.path.sep]) 
    for root, dirs, files in os.walk(some_dir): 
        yield root, dirs, files 
        num_sep_this = len([x for x in root if x == os.path.sep]) 
        if num_sep + level <= num_sep_this: 
            del dirs[:] 
 
>>> for i in walklevel("/home/sromero/prog/", level=0): 
       print(i)
('/home/sromero/prog', ['spectrum', 'code'], [])
 
>>> for i in walklevel("/home/sromero/prog/", level=1):
       print(i)
('/home/sromero/prog', ['spectrum', 'code'], [])
('/home/sromero/prog/spectrum', ['c', 'z88dk', 'utils', 'juegos'], ['setpath.sh'])
('/home/sromero/prog/code', ['pcmaziacs-0.8.7', 'mansion'], ['pcmaziacs-0.8.7.tar.gz'])


Copiar, mover, borrar, comprimir ficheros y directorios (módulo shutil)

El módulo shutil empaqueta funciones varias de alto nivel de copiado / borrado / archivado de ficheros y directorios:



Un ejemplo de copytree usand ignore_patterns:

import shutil
 
shutil.copytree( "/source", "/dest", 
                 ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))

Ejemplo de compresión del directorio /home/usuario/Desktop/:

import shutil
import os
 
archive_name = os.path.expanduser(os.path.join('~', 'desktop-backup'))
root_dir = os.path.expanduser(os.path.join('~', 'Desktop'))
tgz_file = shutil.make_archive(archive_name, 'gztar', root_dir )



Ejecución y gestión de procesos


Procesos en memoria y datos del sistema

El módulo os contiene las funcionalidades necesarias para enviar señales a procesos, obtener usuario y grupo efectivo del proceso actual, ejecutar comandos, etc.


Ejecución de subprocesos

Para la ejecución de subprocesos lanzados desde nuestro script en python, anteriormente se utilizaban funciones como os.system, os.spawn, os.popen, y commands. Todas estas funciones se sustituyen, desde python 2.4, por el módulo subprocess.

Al importar este módulo podemos hacer uso de las siguientes funciones:



Las instancias de la clase subprocess.Popen() tienen los siguientes métodos y atributos:




Ejemplo de popen: Ejecución de "ls -l" e impresión de stdout:

import subprocess
from subprocess import PIPE
 
p = subprocess.Popen("ls -l", shell=True, stdin=PIPE, stdout=PIPE, close_fds=True)
 
(child_stdin, child_stdout) = (p.stdin, p.stdout)
 
print "".join(child_stdout.readlines())

Podríamos también acceder a los pipes de los procesos directamente:

pipeout = subprocess.Popen("ls -l", shell=True, bufsize=bufsize, stdout=PIPE).stdout
 
o bien:
 
pipein = subprocess.Popen("ls -l", shell=True, bufsize=bufsize, stdin=PIPE).stdin

También podemos obtener stderr o incluso agrupar stdout junto a stderr simulando popen2, popen3, y popen4:

p = subprocess.Popen("ls -l", shell=True, bufsize=bufsize,
               stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
(child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p.stderr)
 
 
p = subprocess.Popen("ls -l", shell=True, bufsize=bufsize, stdin=PIPE,
               stdout=PIPE, stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)

Hay que tener cuidado con la ejecución de comandos vía shell cuando los obtenemos vía BBDD o vía input(), porque podríamos ser vulnerables a "shell injection" (del tipo " ; rm -RF /").

En el caso de que no utilicemos la shell para la ejecución, el comando a ejecutar debe de ser una lista en lugar de una cadena, y no se admitirán builtins de la shell (pipes, expansiones de comodines, etc):

>>> args = [ "ls", "-l"]
>>> p = subprocess.Popen(args, shell=False, stdin=PIPE, stdout=PIPE, close_fds=True)
>>> print len(p.stdout.readlines())
32
 
>>> args = [ "ls", "-l", "*.txt"]
>>> p = subprocess.Popen(args, shell=False, stdin=PIPE, stdout=PIPE, close_fds=True)
   ls: no se puede acceder a *.txt: No existe el fichero o el directorio
>>> print len(p.stdout.readlines())
0
 
>>> p = subprocess.Popen("ls -l *.txt", shell=True, stdin=PIPE, stdout=PIPE, close_fds=True)
>>> print len(p.stdout.readlines())
7

Finalmente, cabe destacar que podemos utilizar el módulo shlex para analizar la sintaxis de líneas de comando complejas y así llamar a Popen() correctamente sin shell:

>>> import shlex, subprocess
 
>>> command_line = raw_input()
/bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'"
 
>>> args = shlex.split(command_line)
>>> print args
['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"]
 
>>> p = subprocess.Popen(args)

Otros reemplazados (os.system, os.spawn):

# Sustituto de os.system("mycmd" + " myarg")
p = Popen("mycmd" + " myarg", shell=True)
sts = os.waitpid(p.pid, 0)[1]
 
# Sustituto de os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
pid = Popen(["/bin/mycmd", "myarg"]).pid
 
# Sustituto de os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
retcode = call(["/bin/mycmd", "myarg"])



Debugging, logging y documentación


Docstrings (cadenas de documentación)

Los objetos (clases, funciones, etc) en python cuentan con una propiedad llamada doc que sirve para indicar la finalidad de dicho objeto, para documentarlo. Este atributo doc recibe el nombre de docstring (cadena de documentación).

Para establecer un docstring, basta con que la primera línea tras la definición del objeto sea una cadena o comentario, típicamente con triples dobles comillas, lo que permite establecer tanto docstrings de 1 línea como de múltiples. Como estamos hablando de la primera sentencia de ese objeto o función, deberá ir indentado de la misma forma que lo iría una línea de código (al nivel del código de la función/objeto que estamos documentando). Después, podemos acceder a la información de ayuda de esa función u objeto mediante objeto.doc, help(objeto).

>>> def hola( n ):
       """Imprime "n" veces la cadena hola."""
       print "Hola"*n
 
>>> print hola.__doc__
Imprime "n" veces la cadena hola.
 
>>> help(hola)
Help on function hola in module __main__:
 
hola(n)
    Imprime "n" veces la cadena hola.
(END) 

Establecer docstrings para todas las funciones tiene gran utilidad. Para empezar, los IDEs de desarrollo nos proporcionen información sobre las funciones, sus parámetros y su ayuda gracias a las docstrings. Además, podemos generar la documentación de desarrollo del programa a partir de las docstrings de forma automática. Y, finalmente, se pueden establecer tests de control de calidad dentro de las docstrings.

Según la documentación oficial de Python (PEP 8: Style Guide for Python Code y Docstrings Conventions), se deben aplicar las siguientes reglas:





Así pues, son válidas estas 2 docstrings:

>>> def hola(n):
       """Imprime por pantalla "n" veces la cadena Hola."""
 
>>> def hola(n):
       """Imprime hola por pantalla.
 
       Imprimir hola "n" veces.
 
       """


Cómo depurar scripts en python (Python Debugger, pydb)

El módulo pdb permite hacer debugging en Python. Para ello debemos importar el módulo al principio de nuestro programa (import pdb) y establecer uno o más puntos de ruptura en el programa (pdb.set_trace()):

import pdb
 
# (...)
 
pdb.set_trace()

A continuación, lanzamos el programa de la forma habitual, el cual se ejecutará con normalidad hasta llegar al punto de ruptura. En ese momento aparecerá en pantalla la próxima línea a ejecutar seguida del prompt de pydb (Pdb), y se nos permitirá ejecutar uno de los siguientes comandos o sentencias:



Nótese que "!comando" sólo permite ejecutar comandos en una única línea, aunque podemos usar el punto y coma para poner múltiples comandos, y usar if/for en la misma línea:

!if y == 3: print y; print y; print y;
!for i in range(5): print("Hello"); print("World"); print(i)

Para ejecutar múltiples líneas de código debemos utilizar "commands" acabado en "end".

Finalmente, existe la posibilidad de no establecer puntos de ruptura en el código sino en un manejador de señales del sistema, de forma que podamos iniciar el debug del programa con un kill -SIGNAL pid_del_programa (si la señal elegida es INT, podremos entonces usar Ctrl+C para ir al debugger en lugar de salir del programa).

Lo útil de esta técnica es que no se produce ralentización del programa por el hecho de utilizar "pdb" ya que el código de depuración está en el gestor de la señal. Además, podemos así iniciar el debug en el momento exacto que queramos, y con las condiciones exactas del programa deseadas.

#!/usr/bin/python
 
#---------------------------------------------------------------------------------
# Signal handler example. Debugger starts with Ctrl-C:
# From: http://pythonconquerstheuniverse.wordpress.com/2009/09/10/debugging-in-python/
#---------------------------------------------------------------------------------
import signal
 
def int_handler(signal, frame):
   import pdb
   pdb.set_trace(frame)
 
# (...)   
signal.signal(signal.SIGINT, int_handler)



Acceso a Bases de Datos


Acceso a bases de datos compatibles con DB API

Los módulos python de acceso a bases de datos compatibles con el estándar DB API permiten el acceso a bases de datos de una forma homogénea sea cual sea el motor de BBDD al que nos conectamos. Vamos a utilizar como ejemplo el módulo de acceso a "sqlite", ya que trabaja con ficheros .db y no requiere un servicio de BBDD propiamente dicho, aunque los ejemplos que veremos podría utilizar, por ejemplo mysqldb.

Como todos los métodos y atributos de los módulos que implementan DBAPI deben de ser comunes, podemos hacer:



El estándar DB API define una serie de variables globales informativas:



Por ejemplo:

>>> import sqlite3 as dbapi
>>> print dbapi.apilevel
2.0
>>> print dbapi.threadsafety
1
>>> print dbapi.paramstyle
qmark

Para trabajar con una BBDD tenemos que hacer un open() de la misma. En el caso de sqlite3, al open se le pasa como parámetro un nombre de fichero o la cadena ":memory:" si queremos crear la BBDD en RAM. En el caso de MySQLdb se le especificarían como parámetros el host, port, user, password y nombre_bbdd.

Como resultado del connect obtenemos un objeto de tipo Connection que representa la conexión con el servidor. Sobre esta conexión creamos cursores (método cursor()) a través de los cuales ejecutamos sentencias SQL en la BBDD.

import sqlite3 as dbapi
 
bbdd = dbapi.connect("bbdd.dat")
 
cursor = bbdd.cursor()
cursor.execute("""create table agenda (nombre text, telefono text, ciudad text)""")
cursor.execute("""insert into agenda values ('Santi', '12345678', 'Valencia')""")
bbdd.commit()
cursor.execute("""select * from agenda where ciudad='Valencia'""")
 
for row in cursor.fetchall():
   print row
 
cursor.close()
bbdd.close()

Para consultar las tuplas resultantes de la sentencia SQL se puede llamar a los siguientes métodos de Cursor:


O, directamente, podemos usar el objeto Cursor como un iterador:

for resultado in cursor:
   print resultado

Para realizar sustitución de parámetros en las consultas, debemos de tener en cuenta qué método usa la API que estamos utilizando. En el caso de sqlite3, que usa el sistema qmark por defecto, debemos utilizar interrogantes que se sustituyen después por los elementos de la tupla parámetro:

cursor.execute("""select * from agenda where ciudad=?""", (ciudad,))

Si nuestra base de datos soporta la característica de rollback también podemos cancelar la transacción actual con:

bbdd.rollback()

Si necesitamos trabajar con tipos de SQL (fechas, horas, timestamps, etc), podemos utilizar los siguientes constructores:



Finalmente, es importante saber que a la hora de trabajar con DBAPI podemos obtener las siguientes excepciones:




Cómo importar una BBDD CSV en una BBDD sqlite3

Sea el siguiente fichero CSV:

$ cat photos.csv
"Birthday",12-05-08,"HTC","this is my birthday"
"Sea",12-03-08,"kodak","sea"
"girl","14-03-2009","samsung","birthday"
"love","17-04-2009","SONY","view of island"

Podemos utilizar el módulo csv para importar el fichero csv, y el módulo sqlite3 para crear una BBDD en formato db e inyectar los datos en ella vía SQL:

$ cat test.py
#!/usr/bin/python
import sqlite3
import csv
 
f = open('/home/usuario/fotos.csv')
input = csv.reader(f, delimiter=',')
conn = sqlite3.connect('/home/usuario/bbdd_fotos.db')
curse = conn.cursor()
 
curse.execute('CREATE TABLE photos (Name VARCHAR(100) PRIMARY KEY,
Date INTEGER, Make VARCHAR(50), Tag VARCHAR(100))')
 
for row in input:
   curse.execute('INSERT INTO photos VALUES (?,?,?,?)', row)
 
curse.commit()



Funciones varias


Parseado de argumentos en la línea de comandos

El módulo getopt provee de la función necesaria para parsear argumentos de línea de comandos:



>>> import getopt
 
>>> args = '-a -b -cfoo -d bar a1 a2'.split()
 
>>> args
['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2']
 
>>> optlist, args = getopt.getopt(args, 'abc:d:')
 
>>> optlist
[('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')]
 
>>> args
['a1', 'a2']

Un ejemplo más elaborado, directamente reutilizable:

#----------------------------------------------------------------------
def Usage(argv):
   """Print command line flags and options."""
   print("\nUsage: %s options/flags\n" % (argv[0]))
   print(" Required flags:")
   print("    -a/--action ACTION        (start or stop)\n")
   print(" Optional flags:")
   print("    -s/--config cfgfile       (defaults to default.cfg)")
   print("    -l/--log logfile          (defaults to default.log)")
   print("    -v/--verbose              (default=no)\n")
   print("    -d/--debug                (default=no)\n")
 
 
#----------------------------------------------------------------------
def main():
 
   # Set default values   
   config_file = "default.conf"
   log_file = "default.log"
   verbose = False
   debug = False
   action = ""
 
   # Get command line arguments and parse them
   try:
      opts, remr = getopt.getopt(sys.argv[1:], 'vdc:l:a:',
                   ['verbose','debug','config=','log=','action='] )
   except getopt.GetoptError:
      print("\nError parsing arguments: Unknown flag or missing argument.")
      Usage(sys.argv)
      sys.exit(1)
 
   for opt, arg in opts:
      if opt in ('-c', '--config'):
         config_file = arg
      elif opt in ('-l', '--log'):
         log_file = arg
      elif opt in ('-a', '--action'):
         action = arg
      elif opt in ('-v', '--verbose'):
         verbose = True
      elif opt in ('-d', '--debug'):
         debug = True
 
   # "remr" var contains now items with no "-" and "--" "flags".
 
   # No action defined -> error
   if not action or action not in ["start", "stop"]:
      Usage(sys.argv)
      sys.exit(1)
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    main()


Números aleatorios

El módulo random incluye una gran variedad de funciones para obtención de números y de elementos aleatorios. Las más básicas son las siguientes:



Por ejemplo:

>>> import random
 
>>> random.random()
0.34944228956537604
 
>>> random.randint(1, 10)
3
 
>>> random.randrange(0, 101)
40
 
>>> random.choice('cadena de prueba')
'e'
 
>>> random.choice(["uno", "dos", "tres"])
'uno'
 
>>> random.sample([1, 2, 3, 4, 5],  3)
[4, 1, 5]
 
>>> items = [1, 2, 3, 4, 5, 6, 7]
>>> random.shuffle(items)
>>> items
[7, 3, 2, 5, 6, 4, 1]


Variables de entorno

El módulo os contiene las funcionalidades necesarias para acceder a las variables de entorno del Sistema.



Ejemplo:

>>> import os
>>> for k, v in os.environ.items():
...    print "%s=%s" % (k, v)
USER=sromero
LANG=es_ES.UTF-8
TERM=xterm
SHELL=/bin/bash
EDITOR=vim
(...)
 
>>> os.environ['EDITOR']="vi"
>>> print os.environ['EDITOR']
vi


Conversión de número segura

La siguiente función convierte una cadena con número tanto int como float a valor numérico, devolviendo None en caso de error.

def num(x):
   """Convert string (int, float) to number, or return None on error."""
   try:
      return int(x)
   except ValueError:
      try:
         return float(x)
      except TypeError:
         return None



Referencias y enlaces

Los siguientes enlacs resultan de interés o han sido tomados como referencia para escribir algunas partes de este documento:





<Volver a la sección de Tutoriales Python>