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
-->
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".
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 ]
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():
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.
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.
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).
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:
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
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()
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:
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')
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']
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'])
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 )
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.
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"])
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. """
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)
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:
select * from table where value=?
select * from table where value=:1
select * from table where value=:valor
select * from table where value=%s
select * from table where value=%(valor)
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:
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()
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()
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]
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
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
Los siguientes enlacs resultan de interés o han sido tomados como referencia para escribir algunas partes de este documento: