Módulos, paquetes y la librería estándar de Python¶
Como ya hemos visto, podemos importar nuevos módulos de la librería extándar de Python o de terceros con import <modulo>, pero hay varias maneras de hacerlo:
In [10]: import math # importa el módulo math
In [11]: import math as M # importa el módulo math llamándolo M
In [12]: from math import sin, cos, pi # importa las funciones sin, cos y pi de math
In [13]: from math import * # importa todas las funciones de math
De manera similar podemos crear un módulo propio que puede usarse como un programa independiente o importase como un módulo y poder reutilizar sus funciones:
#!/usr/bin/python3
#-*- coding: utf-8 -*-
"""Programa de calculo del cubo de un numero"""
__author__ = "Jorge"
__copyright__ = "Curso de Python"
__credits__ = ["Pepe", "José Luis", "Roberto"]
__license__ = "GPL"
__version__ = "1.0"
__email__ = "japp@denebola.org"
__status__ = "Development"
def cubo(x):
"""Calcula el cubo de un numero"""
y = x**3
return y
if __name__ == "__main__":
x = int( input("Dame un numero: ") )
y = cubo(x)
print("El cubo de %.2f es %.2f" % (x, y))
Bien, ahora podemos usar este programa como un ejecutable como ya hemos hecho o importarlo como un módulo y usar la función cubo(). La primera de comentario multilínea, limitada por comillas triples, se asiga automáticamente a la variable mágica doc como la documentación del módulo o programa y el resto de variables especiales como información adicional. Igualmente la primera línea de def() es la documentación de la función. La variable especial name es el nombre del módulo cuando se usa como tal, que en este caso vale cubo, pero tiene valor «main» cuando se ejecuta como un programa. De esta manera distinguimos cuando el código se está ejecutando como un programa o está siendo llamado como un módulo.
import cubo
In [20]: cubo.__doc__
Out[20]: 'Programa de calculo del cubo de un numero'
In [21]: cubo.cubo(3)
Out[21]: 27
In [22]: cubo.cubo.__doc__
Out[22]: 'Calcula el cubo de un numero'
In [23]: cubo.__version__
Out[23]: '1.0'
Para poder importar un módulo nuestro, debe estar en el directorio donde lo estamos llamando, o bien estar en una ruta incluida en el PATH de la librería o bien en la variable PYTHONPATH.
$ echo $PYTHONPATH
:/home/japp/codigo/lib/:/usr/local/aspylib/:/usr/local/lib/python2.7/dist-packages/
Alternativamente, se puede incluir el PATH en el programa ejecutable añadiéndolo a la lista sys.path:
import sys
sys.path.append('/home/japp/mis_modulos/')
En Windows, funciona de forma idéntica pero usando las rutas de Windows:
sys.path.append('C:\mis_modulos')
Para modificar de forma temporal el PYTHONPATH
en Windows haríamos:
C:\>set PATH=C:\Program Files\Python 3.6;%PATH%
C:\>set PYTHONPATH=%PYTHONPATH%;C:\mis_modulos
C:\>python
Si se quiere añadir permanentemente es algo más complicado. Desde el botón de inicio hay que buscar Propiedades del sistema (System properties) -> Advanced system settings y pinchar en el botón de variables de entorno, donde se pueden modificar la variables de entorno del sistema (solo el administrador).
Estructura de un paquete de Python¶
Los paquetes de python son un espacio de nombres que contiene varios módulos o paquetes, a veces relacionados entre ellos aunque no tiene porqué. Se crean en un directorio que debe incluir obligatoriamente un fichero especial llamado __init__.py que es el que indica que se trata de un paquete y luego pueden haber otros módulos e incluso otros paquetes. La siguiente es la estructura típica de un paquete:
mi_paquete/
__init__.py
modulo1.py
modulo2.py
utiles/
__init__py
utiles1.py
config.py
El fichero __init__.py puede y suele estar vacío, aunque se puede usar para importar modulos comunes entre paquetes.
import mi_paquete
from mi_paquete import utiles1
La librería estándar de Python¶
La instalación básica de Python viene con una muy completa librería de módulos para todo tipo de tareas, incluyendo acceso a ficheros y directorios, compresión de ficheros, ejecución recurrente (multihilo), email, html, xml, csv y un largo etcétera. Lo más conveniente es consultar la documentación de la librería estándar para tener una idea de todo lo disponible, pero podemos probar los más importantes.
Creación y administración de ficheros¶
La forma más directa y práctica de interactuar con el sistema, independientemente de la plataforma, es empleando el módulo os
, que básicamente es una interfaz para sistema operativo del ordenador que ejecuta el programa.
import os
os.chdir("/home/japp/Documentos/")
os.getcwd()
# /home/japp/Documentos/
# Esto no imita a ls, no distingue ficheros y directorios
ficheros = os.listdir(".") # hay que poner una ruta
for fichero in ficheros:
print os.path.isdir(fichero) # .isfile(), islink()
Para mayor flexibilidad en la selección de ficheros, por ejemplo usar caracteres comodín, se puede usar el paquete glob
:
from glob import glob
ficheros = glob("*.txt")
# Son listas también pero con una ruta relativa, así que no funciona igual que listdir
ficheros = glob("/home/japp/") # no devuelve nada
ficheros = glob("/home/japp/*") # Esto si
os.mkdir("registro")
# os.makedirs('/home/japp/Documentos/datos/pruebas') # Linux, Mac
# os.makedirs('C:\\Mis Documentos\\datos\\pruebas') # Windows
os.chmod("registro", 0700)
os.rename("registro", "registros")
Lectura y escritura de ficheros de texto¶
Si queremos leer o escribir ficheros de texto primero hay que abrirlos en el modo adecuado (r, w, a) para terner una instacia del fichero, que luego se puede leer a string
de varias formas.
# Leo un fichero CSV con código y nombre de paises
fichero = open("paises.csv")
contenido = fichero.read() # Lo mete todo en un único string
fichero.close()
len(contenido)
print(contenido[:30])
#'nombre, name, nom, iso2, iso3,'
fichero = open("paises.csv")
lineas = fichero.readlines() # Lee línea a línea, devuelve una lista
fichero.close()
len(lineas)
247
De haber querido separar por columnas, pudimos haber usado algo como:
nombre, name, nom, iso2, iso3, phone_code = lineas.split(";")
justo después de readlines()
, al hacerlo, split()
devuelve una lista de dos elementos (en este caso) que desempaquetamos en las variables que damos a la izquierda.
Podemos igualmente escribir un fichero creando un fichero en modo lectura y usar el método write(str)
para guardar una cadena de texto o bien usar writelines(lista)
para guardar el contenido de una lista de strings.
¿Y si el fichero es remoto? hay varias maneras de resolverlo, pero lo más cómodo es con el módulo urllib
:
import urllib.request
import csv
# Fichero remoto
# https://gist.github.com/brenes/1095110
url = "https://gist.githubusercontent.com/brenes/1095110/raw/f8eeb4a7efb257921e6236ef5ce2dbc13c50c059/paises.csv"
# Terremotos del día de USGS
# url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.csv"
# Leemos remotamente el fichero csv
respuesta = urllib.request.urlopen(url)
# Pasamos la instancia a un string
contenido = respuesta.read() # o readlines()
# Usamos esta vez el módulo csv de Python para interpretar el CSV
reader = csv.reader(contenido)
Ahora probemos a hacer una selección de los paises que empiezan por «P», pero en su nombre, no en su código
# Lista de paises que empiezan por P, vacía al principio
lineas_P = []
for linea in lineas:
codigo, nombre = linea.split(";")
if nombre.startswith('P'):
lineas_P.append(linea)
# Abro el fichero donde voy a guardar
f_out = open("paises_P.txt", "w")
f_out.writelines(lineas_P)
f_out.close()
El fichero resultante es un fichero igual que el anterior pero solo con los paises que empiezan con «P», uno por línea, pero es es línea a línea porque el fichero original incluye caracteres de nueva línea. El método writelines(lista)
no escribe a líneas y éstas deben añadirse explícitamente:
# Lista de numeros enteros, que paso a string y añado nueva línea
numeros = [str(n)+"\n" for n in range(100)]
f_out = open("numeros.txt", "w")
f_out.writelines(numeros)
f_out.close()
Es posible guardar tambien variable en binario para usarlas después, empleando shelve()
:
import shelve
shelf_file = shelve.open('datos')
shelf_file['numeros'] = numeros
shelf_file.close()
# Al cerrar el fichero se guardan los datos, que se pueden recuperar abriendo el fichero.
shelf_file = shelve.open('datos')
shelf_file['numeros']
El módulo os
tiene otros métodos útiles para interactuar con el
sistema y los procresos que ejecuta.
os.getlogin()
#'japp'
os.getgroups()
#[191, 256, 294, 329, 350, 2000]
os.getenv('HOME')
os.putenv('HOME', '/scratch/japp')
os.uname()
# ('Linux', 'vega', '4.1.13-100.fc21.x86_64', '#1 SMP Tue Nov 10 13:13:20 UTC 2015', 'x86_64')
Si se desea más información sobre el equipo, se puede emplear el módulo platform
, que da información más completa y detallada sobre y ordenador y el SO:
import platform
print('uname:', platform.uname())
print('system :', platform.system())
print('node :', platform.node())
print('release :', platform.release())
print('version :', platform.version())
print('machine :', platform.machine())
print('processor:', platform.processor())
print('distribution:', " ".join(platform.dist()) ) # Linux, mac_ver() para OS X
"""
uname: ('Linux', 'vega', '4.1.13-100.fc21.x86_64', '#1 SMP Tue Nov 10 13:13:20 UTC 2015', 'x86_64', 'x86_64')
system : Linux
node : vega
release : 4.1.13-100.fc21.x86_64
version : #1 SMP Tue Nov 10 13:13:20 UTC 2015
machine : x86_64
processor: x86_64
distribution: fedora 21 Twenty One
"""
Si se desea mayor control sobre los ficheros y directorios, el módulo
shutil
permite operaciones con ficheros a alto nivel.
improt shutil
shutil.copy('paises.csv', 'paises-copy.csv') # Copia un fichero
shutil.copytree("/home/japp/Documentos", "/home/japp/Documentos-copia") # Copia el directorio y su contenido
shutil.move('paises-copy.csv', '/home/japp/Documentos/') # Mueve un fichero
¿Cómo borrar ficheros? Existen tres métodos principales:
os.unlink(path) # Borra el fichero en path
os.rmdir(path) # Borra el directorio en path, que debe estar vacío
shutil.rmtree(path) # Borra path recursivamente
Si queremos borrar con más cuidado podemos usar condicionales:
for filename in os.listdir("."):
if filename.endswith('.csv'):
os.unlink(filename)
En el ejemplo anterior hemos hecho un listado sencillo del directorio en el que estamos. Para hacer una exploración recursiva de un directorio, distinguiendo en ficheros y directorios, podemos usar os.walk()
:
for directorio, subdirectorios, ficheros in os.walk("/home/japp/Documentos/"):
print('El directorio ' + directorio)
os.walk()
devuelve una tupla de tres elementos con el nombre del directorio actual, una lista de subdirectorios que contiene y una lista de ficheros que contiene.
Con el módulo zip
se pueden leer y escribir ficheros zip:
fichero_zip = zipfile.ZipFile('datos', 'w')
ficheros = ['medidas_PV_He.txt', 'medidas_radio.txt', 'bright_star.tsv']
for fichero in ficheros:
newZip.write(fichero, compress_type=zipfile.ZIP_DEFLATED)
fichero_zip.close()
fichero_zip = zipfile.ZipFile("datos.zip")
fichero_zip.namelist()
# informacion sobre un fichero en concreto del zip
bright_star_info = fichero_zip.getinfo('bright_star.tsv')
bright_star_info.file_size
# 926482
bright_star_info.compress_size
# 248269
# Extraigo el contenido
fichero_zip.extract('bright_star.tsv', '/home/japp/Documents/')
fichero_zip.extractall() # todos los ficheros
fichero_zip.close()
Trabajando con fechas y tiempo¶
La librería estándar de Python incluye varios módulos para tratar y manipular fechas, tiempo e intervalos. Como con otros módulos, una vez importado el módulo se define un objeto específico que permite hacer malabarismos con fechas y tiempo. El módulo principal es datetime
, que permite trabajar con fechas y tiempo mientras que el módulo time
, ofrece métodos avanzados para tiempo, ignorando la fecha.
import datetime
print("La fecha y hora actuales: " , datetime.datetime.now() # Devuelve un objeto datetime
print("Fecha y hora en string con formato: " , datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
print("Año actual: ", datetime.date.today().strftime("%Y"))
print("Mes del año: ", datetime.date.today().strftime("%B"))
print("Semana del año: ", datetime.date.today().strftime("%W"))
print("Número de día de la semana: ", datetime.date.today().strftime("%w"))
print("Día del año: ", datetime.date.today().strftime("%j"))
print("Día del mes: ", datetime.date.today().strftime("%d"))
print("Día día de la semana: ", datetime.date.today().strftime("%A"))
import time
print("Segundos desde inicio de época: %s" %time.time())
# Para una fecha específica
fecha = datetime.date(1937, 10, 8) #year, month, day
print(fecha.strftime("%A"))
# Friday
print(fecha.strftime("%b %d %Y %H:%M:%S"))
# Oct 08 1937 00:00:00
En el ejemplo anterior usamos el método strftime()
para obtener un string en el formato deseado según la sintaxis de fechas de Python. De manera similar podemos usar strptime()
para convertir un string de fecha a un objeto date
o datetime
de Python:
# Fecha en string
fecha_str = "2017-05-16 10:30:00"
# Formato en el que está la fecha en string
fecha_fmt = "%Y-%m-%d %H:%M:%S"
# Objeto datetime a partir de la fecha en string
fecha = datetime.datetime.strptime(fecha_str, fecha_fmt)
print(fecha.strftime("%A %d %B, %Y"))
# 'Tuesday 16 May, 2017'
# Cambio de idioma
import locale
idioma = locale.setlocale(locale.LC_TIME, "es_ES")
print(fecha.strftime("%A %d %B, %Y"))
# martes 16 mayo, 2017
# Intervalos de tiempo y operaciones con fechas
hoy = datetime.date.today()
print('Hoy:', hoy)
un_dia = datetime.timedelta(days=1)
print('Lapso de un día:', one_day)
ayer = hoy - un_dia
print('Ayer:', ayer)
manhana = hoy + un_dia
print('Manhana :', manhana)
print('Manhana - ayer:', manhana - ayer)
print('Ayer - manhana:', ayer - manhana)
ayer > hoy
False
ayer < hoy
True
Hay que tener en cuenta que los tiempos se toman de ordenador, incluyendo la zona horaria, por lo que generalmente serán en hora local. Si queremos convertir a otra zona horaria, debemos usar el módulo pytz
:
# Hora local canaria actual
hora_local = datetime.datetime.now()
# datetime.datetime(2017, 5, 12, 10, 30, 0, 379146)
# Hora actual en UTC
hora_utc = datetime.datetime.utcnow()
# datetime.datetime(2017, 5, 12, 9, 30, 0, 226718)
from pytz import timezone
hora_us_pacific = hora_utc.replace(tzinfo=timezone('US/Pacific'))
Finalmente, el módulo calendar
ofrece alguna funciones de calendario:
import calendar
cal = calendar.month(2017, 5)
print(cal)
May 2017
Mo Tu We Th Fr Sa Su
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
print(calendar.TextCalendar(calendar.MONDAY).formatyear(2017, 2, 1, 1, 2))
Llamadas al sistema¶
La forma más sencilla de ejecutar comandos sistema, por ejemplo para
lanzar programas o ejecutar comandos de la consola es el método
os.system()
import os
os.system('touch /home/japp/Documents')
Sin embargo system()
es muy limitado y no permite recoger el
resultado la ejecución, de haberla. Mucho más útil y potente es el
módulo subprocess
:
import subprocess
# Uso básico similar a os.system()
subprocess.call(['ls', '-l'])
Puesto que los canales de entrada y salida del proceso call()
están
ligados a la entrada y salida padre, no puede capturar la salida del
comando que ejecuta, como ocurre con os.system()
. Si queremos
capturar la salida podemos emplear check_output()
y luego procesar
el resultado como queramos.
output = subprocess.check_output(['ps', '-x'])
print(output)
"""
PID TTY STAT TIME COMMAND
3901 ? S 0:00 sshd: invweb@pts/2
3902 pts/2 Ss 0:00 -bash
4248 pts/2 Sl 0:02 gedit cdb_import.py
4527 ? Sl 0:00 /usr/libexec/dconf-service
6134 ? Sl 0:15 /usr/local/apache//bin/httpd -k start
13324 pts/2 Sl+ 0:00 /usr/bin/python /usr/bin/ipython
13613 pts/2 R+ 0:00 ps -x
26515 ? S 0:03 sshd: invweb@pts/0
26516 pts/0 Ss+ 0:00 -bash
"""
# Separo for filas
output_lines = output.split("\n")
# Trozo que contiene el comando
output_lines[1][27:]
# Busco los procesos que usan Python
resultados = []
for line in output_lines:
if 'python' in line.lower():
resultados.append(line[:5]) # Me quedo con trozo que tiene el PID
print(resultados)
Usando tuberías directamente podemos usar parámetros para indicar la
entrada y salida y capturar errores. Veamos este ejemplo de una función
que llama al ping
del sistema:
def esta_viva(hostname):
"""
Hace un ping a una maquina para saber si esta conectada
"""
ping = subprocess.Popen(["ping", "-n", "-c 1", hostname], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, error = ping.communicate()
if error == "":
print("El ordenador {} está conectado".format(hostname))
return True
else:
print("El ordenador {} está KO".format(hostname))
return False
esta_viva('vega')
Conexión remota por FTP y SSH¶
La librería estándar de Python incluye un módulo ftp con todas las funcionalidades necesarias. Veamos un ejemplo para copiar ficheros locales al FTP público del IAC.
import ftplib
import os
from glob import glob
# Origen de los datos
origin_dir = "/home/japp/Documents/"
# directorio destino (en el servidor externo)
destination_dir = "in/curso_python"
# Lista de los ficheros a copiar, todos los *.py
files = glob(origin_dir + "*.py")
ftp = ftplib.FTP("ftp.iac.es")
login = ftp.login("japp@iac.es", "anonymous")
ftp.cwd(destination_dir)
os.chdir(origin_dir)
for filename in files:
infile = open(filename, 'r')
ftp.storlines('STOR ' + os.path.basename(filename), infile)
infile.close()
Hay que fijarse que solo copia ficheros de uno en uno, si se quiere copiar recursivamente hay que implementar una copia recursiva con os.walk()
o similar e ir creado directorios con mkdir()
.
No hay un módulo específico para ssh
, pero se puede usar el del sistema usando el módulo pexpect
, que permite manejar envío de información entre un servidor y un cliente, en ambas direcciones.
import pexpect
import time
host = "vega"
password = "secreto"
ssh_newkey = 'Are you sure you want to continue connecting'
ssh = pexpect.spawn("ssh {}".format(host))
i = ssh.expect([ssh_newkey, 'password:', host, pexpect.EOF], timeout=10)
if i == 0:
ssh.sendline('yes')
# Se dan una lista de posibles salidas del comando: nueva key,
# la contraseña o el prompt del sistema si no pide contraseña
i = ssh.expect([ssh_newkey, 'password: $', pexpect.EOF])
ssh.sendline(password)
ssh.expect(pexpect.EOF)
elif i == 1:
ssh.sendline(password)
ssh.expect(pexpect.EOF, timeout=10)
elif i == 2:
pass
# Extraemos el resultado del comando
p = pexpect.spawn("df -h")
print(p.read())
ssh.close()