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:
- Estilo viejo: Sustitución de cadenas con especificadores de formato tipo C (%s, %d, %f…), incluyendo especificadores de ancho ("%10d, %2.1f, %10s, %-10s, etc)
>>> print('El valor de %s es aproximadamente %2.5f.' % ("PI", math.pi) )
- Estilo nuevo: Uso de corchetes y .format, tanto con identificadores numéricos como sin ellos:
>>> 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
- Estilo nuevo: también se aceptan identificadores de cadenas, e incluso un sistema mixto:
>>> 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.
- Estilo nuevo: añadir un número mínimo de caracteres de anchura mediante un valor numérico después de los dos puntos y antes del identificador de formato (ej: "0:10d", "1:.3f", etc):
>>> agenda = {'Juan': 12345, 'Jose': 22222} >>> for nombre, telefono in agenda.items(): print('{0:10} ==> {1:10d}'.format(nombre, telefono)) Juan ==> 12345 Jose ==> 22222
- Estilo nuevo: uso de diccionarios con corchetes, o directamente expandir el diccionario con "**":
>>> 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():
- lista = list( el_generador )
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:
- Un método next() que permita acceder a los elementos de uno en uno, devolviendo un elemento cada vez, o bien lanzar una excepción StopIteration para que lo detecte el for.
- Un método iter() que devuelva un objeto iterable, es decir, un objeto que soporte next(). Si hemos implementado next() en nuestro propio objeto, el método iter() puede simplemente devolver self.
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:
- func( *variable ) → Variable es, dentro de la función, una tupla con todos los argumentos recibidos.
- func( **variable ) → Variable es, dentro de la función, un diccionario con todos los argumentos recibidos.
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:
- func( var1, var2, *varmulti)
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:
- property([fget[, fset[, fdel[, doc]]]]) → Devolver un atributo de tipo property para clases de nuevo estilo, con el getter, setter, deleter y docstring indicados.
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:
- pickle.dump(objeto, fichero, [protocol=0]) → Vuelca el objeto indicado sobre el flujo "fichero" abierto en modo "w". Podemos especificar un modo de "volcado" (0 = ASCII plano, 1 y 2 = binarios).
- pickle.load(fichero) → Lee desde el flujo "fichero" (abierto en modo "r") un objeto y lo devuelve.
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.
- os.path.isabs(path) → Devuelve True si la ruta indicada es absoluto.
- os.path.isfile(path) → Devuelve True si la ruta indicada es un fichero regular existente. Esta función sigue los enlaces simbólicos, por lo que tanto islink() como isfile() pueden ser True para el mismo path.
- os.path.isdir(path) → Devuelve True si la ruta indicada es un directorio existente. Esta función sigue los enlaces simbólicos, por lo que tanto islink() como isfile() pueden ser True para el mismo path.
- os.path.islink(path) → Devuelve True si la ruta indicada se refiere a un enlace simbólico. Es siempre False en sistemas que no soportan symlinks.
- os.path.ismount(path) → Devuelve True si la ruta indicada se refiere a una unidad remota montada (punto de montaje).
- os.path.getsize(path) → Devuelve el tamaño, en bytes, del recurso apuntado por la ruta indicada, o bien provoca os.error si no existe o es inaccesible.
- os.path.exists(path) → Devuelve True si la ruta indicada se refiere a un path existente. Devuelve False para enlaces simbólicos rotos, o si no se tienen permisos de acceso sobre el fichero o el path.
- os.path.samefile(path1, path2) → Devuelve True si ambas rutas hacen referencia al mismo fichero o directorio.
Creación de ficheros, symlinks, directorios, etc
El módulo os proporciona funciones para crear directorios, cambiar permisos de ficheros, etc:
- os.access(path, mode) → Utiliza el uid/gid del usuario para testear si se dispone de acceso a la ruta indicada (True/False).
- os.chdir(path) → Cambiar el directorio de trabajo actual al indicado.
- os.getcwd() → Obtener una cadena con el actual directorio de trabajo.
- os.chroot(path) → Cambiar el directorio raíz del proceso actual al indicado (chroot).
- os.chmod(path, mode) → Cambiar el modo de la ruta especificada al valor numérico indicado.
- os.chown(path, uid, gid) → Cambiar el propietario y grupo de la ruta especificada a los valores numéricos de uid y gid indicados (-1 en caso de no querer modificar alguno de ellos).
- os.link(source, link_name) → Crear un hard-link de nombre "link_name" apuntando a "source".
- os.symlink(source, link_name) → Crear un enlace simbólico blanco de nombre "link_name" a "source".
- os.listdir(path) → Obtener una lista con los nombres de las entradas de fichero/directorio en la ruta indicada (no incluye '.' y '..').
- os.mkdir(path[, mode]) → Crear un directorio de nombre "path" (con un valor de modo opcional, 0777 por defecto). Si ya existe el directorio, se levanta una excepción OSError.
- os.readlink(path) → Devolver una cadena representando la ruta real a la que apunta el enlace simbólico especificado como parámetro. Si el resultado es relativo, se puede convertir a absoluto con os.path.join(os.path.dirname(path), result).
- os.remove(path) y os.unlink(path) → Borrar el fichero apuntado por la ruta indicada. Si el destino es un fichero, se produce un OSError.
- os.rename(src, dst) → Renombrar el fichero o directorio "src" como "dst". Si "dst" es un directorio, se lanzará un OSError. La operación es atómica.
- os.rmdir(path) → Borrar el directorio apuntado por "path", sólo si está vacío (si no, se produce un OSError). Para borrar el directorio con contenido, hay que utilizar shutil.rmtree().
- os.stat(path) → Realiza un stat() sobre la ruta indicada (además, se siguen los enlaces simbólicos. Para no seguirlos, usar os.lstat). Se devuelve un objeto cuyos atributos definen:
- result[0] → st_mode - protection bits
- result[4] → st_uid - user id of owner
- result[5] → st_gid - group id of owner
- result[6] → st_size - size of file, in bytes
- result[7] → st_atime - time of most recent access
- result[8] → st_mtime - time of most recent content modification
- result[9] → st_ctime - time of most recent metadata change on Unix, or the time of creation on Windows)
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:
- pwd:
- 0 → pw_name → Login name
- 1 → pw_passwd → Optional encrypted password
- 2 → pw_uid → Numerical user ID
- 3 → pw_gid → Numerical group ID
- 4 → pw_gecos → User name or comment field
- 5 → pw_dir → User home directory
- 6 → pw_shell → User command interpreter
- grp:
- 0 → gr_name → the name of the group
- 1 → gr_passwd → the (encrypted) group password; often empty
- 2 → gr_gid → the numerical group ID
- 3 → gr_mem → all the group member’s user names
Las funciones son:
- pwd.getpwuid(uid) → Obtener los datos del usuario especificado por el UID numérico "uid".
- pwd.getpwnam(name) → Obtener los datos del usuario especificado por la cadena de texto "name".
- pwd.getpwall() → Obtener el listado completo de usuarios, en orden arbitrario.
- grp.getgrgid(gid) → Obtener los datos del grupo especificado por el GID numérico "gid".
- grp.getgrnam(name) → Obtener los datos del grupo especificado por la cadena de texto "name".
- grp.getgrall() → Obtener el listado completo de grupos, en orden arbitrario.
Generar / extraer componentes de una ruta (path)
El módulo os.path proporciona funciones para crear y partir rutas a ficheros y directorios:
- os.path.basename(path) → Devuelve el nombre base de la ruta indicada. Es, concretamente, la segunda parte de la pareja obtenida con split(path). Para directorios, al contrario que basename de UNIX, devolvería una cadena vacía.
- os.path.dirname(path) → Devuelve el nombre del directorio de la ruta indicada. Se corresponde con el primer elemento devuelto por split(path).
- os.path.expanduser(path) → Expande las variables "~" y "~user" con el nombre del directorio HOME del usuario. Si falla la expansión, no se altera el path destino.
- os.path.expandvars(path) → Devuelve la rutina indicada después de expandir las variables de entorno que contenga (tanto la forma $VAR como ${VAR} están soportadas).
- os.path.normcase(path) → Normaliza el caso (mayúsculas / minúsculas) de la rutina indicada (normalmente a minúsculas). En Windows, también se convierten las barras en barras invertidas.
- os.path.normpath(path) → Normaliza un path, eliminando redundancias del tipo "/.", "/../", "//", etc.
- os.path.realpath(path) → Devuelve el path real de la ruta especificada, expandiendo los enlaces simbólicos de la misma si los hubiera.l
- os.path.abspath(path) → Devuelve una versión normalizada y absoluta de la ruta indicada.
- os.path.join(path1[, path2[, …]]) → Une dos o más componentes de una ruta de forma inteligente (usando el separador os.sep adecuado para el Sistema Operativo en que se ejecuta el script). Si el path a concatenar es absoluto, descarta inteligentemente la unidad / punto de montaje más allá del punto de unión.
- os.path.split(path) → Parte una ruta en 1 pareja donde el segundo elemento es el último componente de la ruta, y el primero todo el path necesario para llegar a éste. El segundo elemento nunca contendrá una barra: si la ruta es un directorio, el segundo componente será una cadena vacía. Si la ruta es un fichero absoluto (no hay barras o referencias a directorios) el primer componente estará vacío. Un join(split(path)) devuelve el path original (al menos, una ruta al mismo).
- os.path.splitdrive(path) → Parte la ruta indicada en una pareja (unidad, camino) en sistemas Windows. En otros sistemas, el primer componente estará vacío.
- os.path.splitext(path) → Parte la ruta indicada en una pareja donde el primer elemento contiene toda la ruta sin la extensión del fichero, y el segundo la extensión del mismo (o vacío).
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:
- os.walk() → Recorre todos los subdirectorios desde una ruta dada y devuelve una lista de tuplas de 3 elementos que constan de (path, lista de directorios de ese path, lista de ficheros de ese path). Se omiten los directorios especiales "." y "..".
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:
- shutil.copyfile(src, dst) → Copia el fichero origen src como dst.
- shutil.copy(src, dst) → Copia el fichero src al fichero o directorio destino dst.
- shutil.move(src, dst) → Mueve (recursivamente) el fichero o directorio src a dst. Si el destino es el mismo filesystem, se usa renombrado.
- shutil.copytree(src, dst[, symlinks=False[, ignore=None]]) → Copia el árbol de directorios que cuelga de src en dst. Se puede indicar con symlinks=True que se copien los enlaces simbólicos como enlaces, siendo la opción por defecto (False) una copia del contenido del mismo. También acepta una función ignore para excluir listas de ficheros.
- shutil.ignore_patterns(*patterns) → Crea una función para excluir patrones de ficheros en copytree().
- shutil.rmtree(path) → Borra un árbol de directorio completo, recursivamente.
- shutil.make_archive(base_name, format[, root_dir]) → Crear un archivo ZIP o TAR, con un base_name concreto. El valor de format puede ser "zip", "tar", "bztar" o "gztar". Si no se especifíca root_dir, se comprime el directorio actual (cwd).
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.
- os.kill(pid, sig) → Enviar la señal "sig" al proceso "pid". Las constantes que definen las diferentes señales según S.O. están definidas en el módulo "signal".
- os.system(command) → Ejecuta el comando "command" en una subshell. Se recomienda utilizar el módulo "subprocess" en lugar de esta función.
- os.getloadavg() → Devuelve el número de procesos en cola del sistema en una media de 1, 5 y 15 minutos, o OSError si no se puede obtener este valor.
- os.getegid() → Devuelve el group id efectivo del proceso actual.
- os.geteuid() → Devuelve el user id efectivo del proceso actual.
- os.getgid() → Devuelve el group id real del proceso actual.
- os.getgroups() → Devuelve el listado de group ids completos asociados al proceso actual.
- os.getlogin() → Devuelve el nombre del usuario logado en la terminal que controla el proceso. En muchos casos es más útil utilizar la variable de entorno LOGNAME o incluso pwd.getpwuid(os.getuid())[0].
- os.getuid() → Devuelve el user id que ejecuta el proceso actual.
- os.umask(mask) → Establecer un valor de umask determinado y devolver el anterior umask.
- os.uname() → Devuelve una tupla de 5 elemento conteniendo información que identifican al Sistema Operativo: (sysname, nodename, release, version, machine). Algunos sistemas trucan el nodename a 8 caracteres, por lo que puede ser recomendable obtenerlo con: socket.gethostname() o socket.gethostbyaddr( socket.gethostname() ).
- sys.version_info → Esta tupla del módulo sys nos indica la versión del intérprete de python que ejecuta nuestro script, siendo el Major Version el primer elemento (sys.version_info[0]) y el Minor Version el segundo ([1]).
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:
- subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, close_fds=False, shell=False, cwd=None) → Ejecuta el comando con argumentos especificado en "args" (cadena con shell=True, lista con shell=False) o en "executable + args" (si executable != None). Permite indicar si stdin, stdout y stderr son PIPEs para poder obtener la salida del comando o interactuar con él. Si "shell" es True, tenemos que formatear los argumentos como si los fueramos a ejecutar en la shell (escapar barras, etc), y se ejecutará vía la shell en uso para el usuario. Si "shell" es false, se utiliza os.execvp() para la ejecución (no reconocerá los comandos builtin de la shell).
- subprocess.call() → Ejecuta un comando con argumentos, espera a que éste acabe, y devuelve el código de retorno en el atributo returncode.
- subprocess.check_call() → Ejecuta comando con argumentos y espera a que éste acabe. Provoca una excepción CalledProcessError si no vuelve con exitcode 0.
- subprocess.check_output() → Permite ejecutar un comando con argumentos y obtener directamente la salida de stdout (Versión 2.7 o superior).
Las instancias de la clase subprocess.Popen() tienen los siguientes métodos y atributos:
- subprocess.Popen.poll() → Chequea si un proceso hijo ha terminado.
- subprocess.Popen.wait() → Espera a que termine un proceso hijo.
- subprocess.Popen.communicate(input=None) → Interactuar con el proceso (enviar datos a stdin). Es necesario haber creado el proceso Popen con stdin=PIPE, stdout=PIPE y stderr=PIPE. El argumento opcional input es una cadena que enviar al proceso. Esta función devuelve una tupla (stdoutdata, stderrdata). Toda la gestión de IN/OUT/ERR se realiza en memoria, por lo que se recomienda tener cuidado con el consumo de memoria para comandos de muchos datos o infinitos.
- subprocess.Popen.send_signal() → Enviar la señal "signal" al proceso indicado.
- subprocess.Popen.terminate() → Detener el proceso hijo.
- subprocess.Popen.kill() → Hacer un kill del proceso hijo.
- subprocess.Popen.stdin, .stdout y .stderr → Si los argumentos stdin, stdout y stderr de Popen fueron "PIPE", este atributo es un objeto de tipo file que provee el acceso a stdin, stdout y stderr para el proceso.
- subprocess.Popen.pid → El PID del proceso lanzado.
- subprocess.Popen.returncode → El código de returno devuelvo por el proceso hijo, o None si todavía no ha terminado su ejecución. Un valor negativo "-N" indica que el proceso fue terminado por una señal UNIX "N" (kill -N).
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:
- Se deben escribir docstring para todos los módulos públicos, funciones, clases y métodos de las clases. No son necesarias para los métodos privados, aunque también pueden resultar útiles a nivel de desarrollo (si bien en este caso bastará con un comentario después del def:).
- No deben indicar información redundante (como repetir el nombre de la función y sus parámetros).
- En las docstrings de una sóla línea:
- Empiezan y acaban en triples comillas """ (aunque por ser una sóla línea no fuera necesario, permite expandirlas después).
- La frase debe de ser un resumen corto y conciso de la finalidad de este objeto, función o clase, empezando por una letra mayúscula y acabando en un punto.
- No deben repetir la definición de la función, puesto que ya van asociadas a la misma, sólo información sobre su propósito.
- En las docstrings multilínea:
- La primera frase será igual que en el caso de las docstrings de una línea: un resumen claro y conciso, empezando por las triples comillas.
- A esa primera frase le seguirá una línea en blanco para separar el resumen de la información adicional (convenciones de llamada, efectos, consideraciones, etc).
- La indentación de la primera línea no en blanco después de las primeras triples comillas define la indentación de toda la docstring. La indentación equivalente al nivel de esa línea será "eliminado" de cada línea de la docstring para generar la documentación.
- Las triples comillas """ que acaban una docstring deben de estar en una línea propia para ellas, y con una línea en blanco sobre ella (separándola de la información).
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" (next) → Ejecutar la sentencia actual (la que aparece en pantalla).
- "enter" → Repetir el último comando pdb ejecutado (por ejemplo, "n").
- "p variable" (print) → Imprimir el valor de una variable (o de varias, separadas por comas).
- "l" (list) → Ver parte del listado alrededor del punto de ejecución actual (11 líneas). Acepta como parámetros opcionales un número inicial o final.
- "c" (continue) → Continuar normalmente la ejecución del programa (finalizar pydb), o, si existe, hasta el siguiente punto de ruptura.
- "s" (step) → Al ejecutar una llamada a subrutina, entrar en ella en modo depuración en vez de ejecutarla de un sólo paso.
- "r" (return) → Continuar la ejecución normal (sin depuración) de la subrutina actual hasta el final de la misma.
- "!comando" (ejecutar) → Ejecutar comando python en ese punto del programa (ej: !variable=valor).
- "!commands" (ejecutar) → Nos permite introducir múltiples líneas de código python, hasta introducir un end.
- "q" (quit) → Salir del programa (finalizarlo abruptamente).
- "h" (help) → Ayuda
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:
- import sqlite3 as dbapi
- import MySQLdb as dbapi
- import xxxxxx as dbapi
El estándar DB API define una serie de variables globales informativas:
- dbapi.apilevel → Versión de la DB API que se utiliza.
- dbapi.threadsafety → Valor entre 0 y 3 que indica lo seguro que es el módulo para su uso con threads (0=no se pueden usar threads, 3=módulo seguro para usar con threads).
- dbapi.paramstyle → Sintaxis que debemos utilizar para la generación de las consultas SQL a la hora de insertar valores:
- qmark → Usar interrogaciones como parámetros:
select * from table where value=?
- numeric → Se utiliza un número (precedido por ":") para el parámetro y su orden.
sql =select * from table where value=:1
- named → Se utiliza un nombre (precidido por ":") para el valor.
select * from table where value=:valor
- format → Se utilizan los identificadores tipo printf / C.
select * from table where value=%s
- pyformat → Se utilizan los identificadores tipo python
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:
- .fetchone() → Devuelve la siguiente fila del resultado o None cuando no quedan filas por devolver.
- .fetchmany(N) → Devuelve N filas del resultado (o Cursor.arraysize si no se pasa parámetro, siendo 1 por defecto)
- .fetchall() → Devuelve un objeto iterable con todas las filas del resultado.
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:
- Date(year, month, day): Para almacenar fechas.
- Time(hour, minute, second): Para almacenar horas.
- Timestamp(year, month, day, hour, minute, second): Para almacenar timestamps.
- DateFromTicks(ticks): Crear una fecha EPOCH (segundos desde 1970).
- TimeFromTicks(ticks): Crear una hora EPOCH.
- TimestampFromTicks(ticks): Similar al anterior, para timestamps.
- Binary(string): Valor binario.
Finalmente, es importante saber que a la hora de trabajar con DBAPI podemos obtener las siguientes excepciones:
- DatabaseError → Error relacionado con la BBDD. Se puede dividir (para más precisión) en:
- DataError → Error relacionado con los datos obtenidos.
- OperationalError → Error relacionado con el funcionamiento de la BBDD.
- IntegrityError → Error relacionado con la integridad referencial de los datos.
- InternalError → Error interno de la BBDD.
- ProgrammingError → Errores en el código SQL.
- NotSupportedError → Método solicitado no soportado por la BBDD.
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:
- getopt.getopt(args, options[, long_options]) → Parsea los argumentos de la línea de comandos (típicamente, sys.argv[1:]). Permite especificar opciones cortas (precedidas de un "-") o largas (de "–"), con y sin argumentos obligatorios. Lanzará una excepción getopt.GetoptError si se encuentra una opción no reconocida.
>>> 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:
- random.seed([x]) → Establecer la semilla de generación de números aleatorios. Por defecto se utiliza el reloj del sistema (al importar el módulo).
- random.randrange(start, stop[, step]) → Devolver un elemento aleatorio entre start y stop.
- random.randint(a, b) → Devuelve un entero tal que a ⇐ N ⇐ b.
- random.choice(seq) → Devuelve un elemento aleatorio de la secuencia no vacía "seq". En caso de que "seq" sea una cadena, devolverá un carácter. Si es una lista, devolverá un elemento. Si es vacía, se produce un IndexError.
- random.shuffle(x[, random]) → Baraja la secuencia "x". Se puede especificar una función random(0.0→1.0) específica, aunque por defecto usa random().
- random.sample(seq, k) → Devuelve una lista de "k" elementos de la secuencia "seq".
- os.urandom(n) → Esta función del módulo os (no de random) devuelve una cadena de N bytes aleatorios para uso criptográfico. Es parte de os porque se realiza vía recurso del Sistema Operativo y sólo está soportado en los S.O. que lo soporten (ej: UNIX → /dev/urandom).
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.
- os.environ → Es un diccionario que da acceso directo a los valores de las variables de entorno del Sistema (Por ejemplo: os.environ['HOME']), tanto para lectura como para escritura.
- os.getenv(varname[, value]) → Devuelve el valor de la variable de entorno solicitada, o "value" si no existe (por defecto, value=None).
- os.putenv(varname, value) → Permite establecer un valor en una variable de entorno. Este cambio afectará a subprocesos lanzados posteriormente.
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:
- Python Tutorial (tutorial oficial de Python).
- Python Para Todos: (de mundogeek.net).
- Guía de Estilo Python (traducción de mundogeek.net).
- Modismos y antimodismos en Python (traducción de mundogeek.net).
- Python idiomático (traducción de mundogeek.net, original de David Goodger).