¡Esta es una revisión vieja del documento!
Pequeños consejos, utilidades del lenguaje…
class ProjectType(object): build_cmd = "" markers = [] @classmethod def make_project(cls, path): prj_types = (PythonProject, AutoconfProject, MakefileOnly, ProjectType) for p in prj_types: markers = p.markers if any(path.isfile(path.join(path, x)) for x in markers): return p() class PythonProject(ProjectType): build_cmd = "python setup.py develop --user" markers = ['setup.py'] class AutoconfProject(ProjectType): #TODO: there should be also a way to configure it build_cmd = "./configure && make -j3" markers = ['configure.in', 'configure.ac', 'makefile.am'] class MakefileOnly(ProjectType): build_cmd = "make" markers = ['Makefile']
Este código elige para los ficheros en una ruta a qué proyecto pertenece a partir de los markers (ficheros concretos para cada tipo de proyecto). Devolverá un objeto del tipo de sub-clase correspondiente para tratar dicho proyecto.
Realmente no es necesario que make_project sea un @classmethod debido a que no utiliza el atributo cks.
Para parsear documentos YAML.
Agregaremos la librería.
import yaml
Cargar un texto yaml:
print yaml.load(""" name: Vorlin Laruknuzum sex: Male class: Priest title: Acolyte hp: [32, 71] sp: [1, 13] gold: 423 inventory: - a Holy Book of Prayers (Words of Wisdom) - an Azure Potion of Cure Light Wounds - a Silver Wand of Wonder """)
Crear un texto yaml:
print yaml.dump({'name': "The Cloak 'Colluin'", 'depth': 5, 'rarity': 45, 'weight': 10, 'cost': 50000, 'flags': ['INT', 'WIS', 'SPEED', 'STEALTH']})
$ sudo python setup.py install
Esta librería es únicamente para Windows.
Una vez instalado el paquete PyWin podremos acceder a la API de Windows mediante el módulo win32gui. Las constantes para este las encontraremos en win32con.
>>> import win32gui as gui >>> import win32con as con >>> bsp = gui.FindWindow("BSPlayer", None) >>> bsp 525126 >>> bsp_cmd = con.WM_USER + 2 >>> gui.SendMessage(bsp, bsp_cmd, 0x10000, 0)
Mediante Python podemos acceder a objetos COM, esto nos permite desarrollar para programas (ya sean tareas automatizadas, plugins, scripts…). Para ello al instalar Python en Windows se nos agrega un paquete denominado PythonCOM que nos permite enlazar fácilmente estas dos tecnologías, viene también con una herramienta denominada Makepy que genera código Python que adapta y enlaza la interface COM elegida a nuestro código.
Hay dos formas de ejecutar Makepy:
make.py dentro del directorio win32com.PythonWin → Tools → COM Makepy utility. Esta agregaráa autocompletado al código que escribiesemos.
Para enlazar código COM…
import win32com.client Visum = win32com.client.Dispatch ("visum.visum.11")
Es un parser muy sencillo de HTML.
Uso básico:
from BeautifulSoup import BeautifulSoup # Para procesar HTML from BeautifulSoup import BeautifulStoneSoup # Para procesar XML soup.contents[0].name # u'html' soup.contents[0].contents[0].name # u'head' head = soup.contents[0].contents[0] head.parent.name # u'html' head.next # <title>Page title</title> head.next.string # Page title head.nextSibling.name # u'body' titleTag = soup.html.head.title
Coger una web y procesarla (búsqueda de elementos):
import urllib2 from BeautifulSoup import BeautifulSoup page = urllib2.urlopen("http://www.icc-ccs.org/prc/piracyreport.php") soup = BeautifulSoup(page) for incident in soup('td', width="90%"): ...
Visualizar html en un formato aceptable:
print soup.prettify()
Se utiliza como wrap a un código python que hará de daemon. Para ello se ha de heredar de la clase Daemon y sobreescribir el método run:
import logging import time from pydaemon import Daemon class FooDaemon(Daemon): def run(self): while True: logging.debug("I'm here...") time.sleep(1) def bar(self): logging.debug("bar") if __name__ == "__main__": logging.basicConfig(filename="foodaemon.log",level=logging.DEBUG) daemon = FooDaemon("/tmp/foodaemon.pid", "FooDaemon") daemon.main(extended_args={"bar": daemon.bar})
Es un gestor de paquetes para Python. Permite instalar librerías de una forma muy sencilla. Para instalarlo simplemente haremos:
wget http://peak.telecommunity.com/dist/ez_setup.py sudo python ez_setup.py
Puede que exista el paquete instalable (nombre python-setuptools) para el sistema operativo utilizado.
A partir de entonces podremos instalar los paquetes con simplemente poner el nombre (esto hará que lo busque en el repositorio PyPi, lo baje y lo instale]]:
easy_install SQLObject
O a partir de una ruta, de un .tgz, de un .egg…
easy_install http://example.com/path/to/MyPackage-1.2.3.tgz easy_install .
Para instalar virtualenv:
$ sudo apt-get install python-virtualenv $ sudo apt-get install python-pip
Al igual que easy_install, pip es un comando para instalar paquetes desde el repositorio Pypy. Para instalarlo haremos:
$ sudo easy_install pip
Para instalar un paquete haremos:
$ sudo pip install django
Si queremos actualizar un paquete haremos:
$ sudo pip install --upgrade django
Aún así los paquetes no deberían ser instalados globalmente (en el directorio site-packages donde todo script python puede acceder) debido a que pueden coexistir problemas entre versiones y paquetes.
… virtualenv permite instalar los paquetes no-globalmente. Crea un entorno aislado para un programa donde incluye una copia del binario de pyton, una copia del directorio site-packages, una copia del instalador pip con el cual instalará los paquetes únicamente en dicho entorno.
Realmente sólo serían necesarios estos dos paquetes de forma global (pip y easyinstall).
Para instalarlo:
$ sudo pip install virtualenv
Para crear un entorno (denominado env):
$ virtualenv env
Cuando lo hagamos se crearán tres directorios: bin, include y lib. Recuerda ignorarlos en el control de versiones. El directorio bin es donde se copiarán los binarios python, para usar su comando pip (ya no es necesario sudo):
$ env/bin/pip install requests
Para no tener que escribir toda la ruta podemos llamar al comando source que la cambiará temporalmente:
$ which python /usr/bin/python $ source env/bin/activate $ which python /Users/jamie/code/myproject/env/bin/python
Para dejar de usar el entorno llamaremos al comando deactivate.
En el fichero requirements se guardan los paquetes instalados con pip.
env/bin/pip install -r requirements.txt
En el fichero requirements podremos añadir repositorios svn, git, mercurial, Bazaar (svn+, git+, hg+ o bzr+)… Para ello los agregaremos con -e (puedes incluir @<num> (por ejemplo @275) para indicar la revisión deseada):
-e svn+http://myrepo/svn/MyApp#egg=MyApp
Para crear un fichero requirements desde un entorno:
$ pip freeze > stable-req.txt
Copiar un fichero requirements:
$ pip freeze -r devel-req.txt > stable-req.txt
Es una herramienta para hacer más cómodo el uso de virtualenv.
$ workon lista los virtualenv creados.$ mkvirtualenv <nombre> crea un virtualenv.$ workon <nombre> usa dicho virtualenv.Imaginemos un proyecto con la siguiente estructura:
TowelStuff/
bin/
CHANGES.txt
docs/
LICENSE.txt
MANIFEST.in
README.txt
setup.py
towelstuff/
__init__.py
location.py
utils.py
test/
__init__.py
test_location.py
test_utils.py
En la carpeta bin se pueden añadir scripts que puedan ser útiles para usar el código de towelstuff. Si no existiesen puede ser eliminada.
La carpeta docs debería contener documentos de diseño, implementación, FAQ o cualquier otro documento escrito.
El fichero LICENSE.txt será, generalmente, un copy\paste de la licencia escogida.
El fichero README.txt contendrá la explicación del código, el formato debería de ser reST.
La carpeta test debe contener los tests escritos usando el paquete unittest.
En una primera versión dicho fichero puede contener algo del estilo:
v<version>, <date> -- Initial release.
Debería contener algo del estilo:
include *.txt recursive-include docs *.txt
Es un fichero que, mediante comandos, uno por línea, indica acciones a realizar con ficheros externos. Por ejemplo:
include *.txt recursive-include hindoor/modules/interface *.* prune hindoor/modules/interface/test
Dice que del directorio raíz se incluirán todos los .txt. Del directorio hindoor/modules/interface todos los ficheros recursivamente excepto los del directorio test.
Un fichero con el siguiente formato:
#from distutils.core import setup from setuptools import setup setup( name='TowelStuff', version='0.1.0', author='J. Random Hacker', author_email='jrh@example.com', packages=['towelstuff', 'towelstuff.test'], scripts=['bin/stowe-towels.py','bin/wash-towels.py'], url='http://pypi.python.org/pypi/TowelStuff/', license='LICENSE.txt', description='Useful towel-related stuff.', long_description=open('README.txt').read(), install_requires=[ "Django >= 1.1.1", "caldav == 0.1.4", ], )
La versión puede ser perfectamente 0.1.0.
Podemos ver los comandos disponibles con:
$ python setup.py --help-commands
Crear el paquete a distribuir:
$ python setup.py sdist
Crear un paquete binario:
$ python setup.py bdist
Instalar desde el setup.py:
$ python setup.py install
Acciones que se realicen según la distribución creada.
Para actualizar el paquete deberemos:
setup.py.CHANGES.txt.python setup.py sdist otra vez.
Para recoger los install_requires de un requirements.txt podemos hacer una copia del fichero en la misma carpeta, agregar el import de este en el MANIFEST.in y:
frequirem = open ('./requirements.txt') requirem = frequirem.readlines() frequirem.close() ... install_requires=requirem,
En setup para…
setup ( ... include_package_data = True, # añadir al directorio de instalacion los datos no .py scripts=['bin/serverstarter','bin/start_interface'], # añadir scripts python como ejecutables data_files=[('', ['hindoor/modules/interface/index.html'])], # añadir ficheros no indicados ... )
Ejemplos:
Puedes cambiar la codificación del script agregando al principio:
# -*- coding: utf-8 *-*
Podemos saber la codificación por defecto haciendo:
sys.getdefaultencoding()
Podemos indicar la codificación en un programa haciendo (PERO NO ES ACONSEJABLE):
import sys reload(sys) sys.setdefaultencoding("utf-8")
Podemos cambiar la configuración por defecto de python en el script site.py, en la funcion setencoding (en mi caso en la ruta /usr/lib/python2.7):
def setencoding(): encoding = "utf-8" ...
Podemos saber la codificación del sistema haciendo:
sys.getfilesystemencoding()
Stackless (no es compatible con el oficial, lo cual es su principal problema). Es el GIL quien los gestiona
Podemos definir getters y setters en las propiedades de una clase python con los decoradores @property y @<variable>.setter. Aún así existe un problema, y es que si tenemos varios el código puede hacerse demasiado largo:
class Movie(object): def __init__(self, title, rating, runtime, budget, gross): self._rating = None self._runtime = None self._budget = None self._gross = None self.title = title self.rating = rating self.runtime = runtime self.gross = gross self.budget = budget #nice @property def budget(self): return self._budget @budget.setter def budget(self, value): if value < 0: raise ValueError("Negative value not allowed: %s" % value) self._budget = value #ok @property def rating(self): return self._rating @rating.setter def rating(self, value): if value < 0: raise ValueError("Negative value not allowed: %s" % value) self._rating = value #uhh... @property def runtime(self): return self._runtime @runtime.setter def runtime(self, value): if value < 0: raise ValueError("Negative value not allowed: %s" % value) self._runtime = value #is this forever? @property def gross(self): return self._gross @gross.setter def gross(self, value): if value < 0: raise ValueError("Negative value not allowed: %s" % value) self._gross = value def profit(self): return self.gross - self.budget
Los descriptores nos permiten definir una lógica común para el get, el set y el delete de las variables:
from weakref import WeakKeyDictionary class NonNegative(object): """A descriptor that forbids negative values""" def __init__(self, default): self.default = default self.data = WeakKeyDictionary() def __get__(self, instance, owner): # we get here when someone calls x.d, and d is a NonNegative instance # instance = x # owner = type(x) return self.data.get(instance, self.default) def __set__(self, instance, value): # we get here when someone calls x.d = val, and d is a NonNegative instance # instance = x # value = val if value < 0: raise ValueError("Negative value not allowed: %s" % value) self.data[instance] = value class Movie(object): #always put descriptors at the class-level rating = NonNegative(0) runtime = NonNegative(0) budget = NonNegative(0) gross = NonNegative(0) def __init__(self, title, rating, runtime, budget, gross): self.title = title self.rating = rating self.runtime = runtime self.budget = budget self.gross = gross def profit(self): return self.gross - self.budget m = Movie('Casablanca', 97, 102, 964000, 1300000) print m.budget # calls Movie.budget.__get__(m, Movie) m.rating = 100 # calls Movie.budget.__set__(m, 100) try: m.rating = -1 # calls Movie.budget.__set__(m, -100) except ValueError: print "Woops, negative value"
El WeakKeyDictionary lo que hace es que cuando el objeto key se borra, se borra también del diccionario.
Esto funciona debido a que cuando se hace una asignación tipo m.rating = 100 python reconoce el set y llama a Movie.rating.<nowiki>__set__(m, 100).
Para crear métodos haremos:
class CallbackProperty(object): """A property that will alert observers when upon updates""" def __init__(self, default=None): self.data = WeakKeyDictionary() self.default = default self.callbacks = WeakKeyDictionary() def __get__(self, instance, owner): if instance is None: return self return self.data.get(instance, self.default) def __set__(self, instance, value): for callback in self.callbacks.get(instance, []): # alert callback function of new value callback(value) self.data[instance] = value def add_callback(self, instance, callback): """Add a new function to call everytime the descriptor within instance updates""" if instance not in self.callbacks: self.callbacks[instance] = [] self.callbacks[instance].append(callback) class BankAccount(object): balance = CallbackProperty(0) def low_balance_warning(value): if value < 100: print "You are now poor" ba = BankAccount() BankAccount.balance.add_callback(ba, low_balance_warning) ba.balance = 5000 print "Balance is %s" % ba.balance ba.balance = 99 print "Balance is %s" % ba.balance
Existen dos estilos de control de excepciones:
El código con estilo LBYL sería algo parecido al siguiente:
def print_object(some_object): # Check if the object is printable... if isinstance(some_object, str): print(some_object) elif isinstance(some_object, dict): print(some_object) elif isinstance(some_object, list): print(some_object) # 97 elifs later... else: print("unprintable object")
… Y con estilo EAFP:
def print_object(some_object): # Check if the object is printable... try: printable = str(some_object) print(printable) except TypeError: print("unprintable object")
Pero este código puede ser mejorable. Podríamos controlar en qué parte del código puede haber un error y poner otro código que no controlamos en un else:
def print_object(some_object): # Check if the object is printable... try: printable = str(some_object) except TypeError: print("unprintable object") else: print(printable)
Otro uso del else podría ser, por ejemplo, para limpiar el entorno:
def display_username(user_id): try: db_connection = get_db_connection() except DatabaseEatenByGrueError: print('Sorry! Database was eaten by a grue.') else: print(db_connection.get_username(user_id)) db_connection.cleanup()
raise nos sirve para lanzar una excepción si le añadimos la creación un objeto “Exception”. Aún así también nos puede servir para, por ejemplo, mantener unas estadísticas si no queremos controlar la excepción. Si la incluimos en un exept propagará la excepción al siguiente nivel de código.
def calculate_value(self, foo, bar, baz): try: result = self._do_calculation(foo, bar, baz) except: self.user_screwups += 1 raise return result
MySQL
$ apt-get install libmysqlclient-dev $ pip install MySQL-python
Gevent
$ apt-get install libevent-dev $ apt-get install python-dev $ pip install gevent
ZeroMQ
$ sudo apt-get install python-dev libzmq-dev $ pip install pyzmq
PyQt4
$ sudo apt-get install python-qt4