Herramientas de usuario

Herramientas del sitio


script:python:new:xtra

¡Esta es una revisión vieja del documento!


Python - xtra

Pequeños consejos, utilidades del lenguaje…

Patrones de diseño

Método factoría

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.

Pequeñas librerías

PyYAML

Para parsear documentos YAML.

Uso

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']})

Notas

  • Para instalar, en el directorio:
$ sudo python setup.py install

PyWin

Esta librería es únicamente para Windows.

API Win32

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)

COM

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:

  • Lanzando el script make.py dentro del directorio win32com.
  • Desde PythonWinToolsCOM 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")

BeautifulSoup

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()

pydaemon

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})

PyMongo

It's a library to access Mongo data bases.
Connection:

from pymongo import MongoClient
client = MongoClient()
client = MongoClient('localhost', 27017)
client = MongoClient('mongodb://localhost:27017/')

Getting a db:

db = client.test_database
db = client['test-database']

Getting a collection:

collection = db.test_collection
collection = db['test-collection']

Posting an object:

import datetime
post = { 
  "author": "Mike", 
  "text": "My first blog post!", 
  "tags": ["mongodb", "python", "pymongo"], 
  "date": datetime.datetime.utcnow() 
}
posts = db.posts
post_id = posts.insert(post)

Querying:

posts.find_one({"author": "Mike"})
posts.find_one({"_id": post_id})
for post in posts.find({"date": {"$lt": d}}).sort("author"): ...

Other actions:

posts.count()
posts.find({"author": "Mike"}).count()

Otras

  • psutil, para controlar el rendimiento de la máquina.

Utilidades

easy_install

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 .

pip y virtualenv

Para instalar virtualenv:

$ sudo apt-get install python-virtualenv
$ sudo apt-get install python-pip

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

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.

Fichero requirements

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

virtualenvwrapper

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.

Crear un instalador

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

Carpetas y ficheros sin mayor importancia

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.

Fichero CHANGES.txt

En una primera versión dicho fichero puede contener algo del estilo:

v<version>, <date> -- Initial release.

Fichero MANIFEST.in

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.

Fichero setup.py

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.

Comandos

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

Entry points

Acciones que se realicen según la distribución creada.

Actualizar el paquete

Para actualizar el paquete deberemos:

  1. Aumentar el número de versión en el fichero setup.py.
  2. Actualizar CHANGES.txt.
  3. Ejecutar python setup.py sdist otra vez.

Notas

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:

Tips & Tricks

Python y la codificación

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()

Modelos de concurrencia en Python

  • Processes, separan los datos por distintos procesos los cuales, cada uno, tienen sus variables y datos independientemente. Cada uno tiene su propio intérprete de Python y son la única forma de sacar provecho a una CPU multicore.
  • Threads, implementados como Posix Threads (los gestiona el sistema). Su defecto es que si hay que gestionar muchos el sistema necesita mucho tiempo para intercambiar la ejecución de estos y una vez se pasa de 100 threads se crea un cuello de botella.
  • Microthreads (tasklets), se encuentran en el intérprete Stackless (no es compatible con el oficial, lo cual es su principal problema). Es el GIL quien los gestiona
  • Greenlets, son una copia de los microthreads pero compatible con el intérprete de Python oficial. El cambio de thread lo realiza el desarrollador (cooperative concurrency model) y no se ejecutan a la vez. El cambio entre greenlets se realiza de forma rápida y permite evitar los locks.

Propiedades y descriptores

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

Descriptores

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

Easier to Ask for Forgiveness than Permission

Existen dos estilos de control de excepciones:

  • LBYL, denominado Look Before You Leap. Se comprueba si algo puede ir mal antes de hacerlo.
  • EAFP, denominado Easier to Ask for Forgiveness than Permission. Se realiza la acción y si sale mal se intenta recuperar el estado.

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

Instalaciones

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
script/python/new/xtra.1400515008.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)