Posts tagged python

Logging start and finish in Python

:: python

Just a quick post on how to log, in simple Python scripts, the start and finish times of a function.

import logging
import time
import sys


def main():
    start = time.time()
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s %(levelname)s - %(message)s',
        datefmt='%H:%M:%S',
    )
    logging.info("Start processing after %s", format_time_since(start))
    # Do some processing ...
    logging.info("Finished processing after %s", format_time_since(start))
    sys.exit(0)


def format_time_since(start):
    now = time.time()
    elapsed = now - start
    return time.strftime("%H:%M:%S", time.gmtime(elapsed))

Python egg tagged with Git commit hash

:: python, git

SVN

Python’s setuptools provide a simple mechanism to tag your built distributions with the SVN revision they belong to, by creating a setup.cfg file along the normal setup.py, and there write

[egg_info]
# Add svn revision to the file name
tag_svn_revision = 1

Git

How to do the same for Git? Or in general, with other information? (Date tagging is also supported by default).

Answer: modify directly the options dictionary in the call to setup, in setup.py. There, you can put anything you can compute with Python or system calls. E.g. for git:

import shlex
from subprocess import check_output

GIT_HEAD_REV = check_output(shlex.split('git rev-parse --short HEAD')).strip()


setup(
    # ... other keys like project name, version, etc ...
    options = dict(egg_info = dict(tag_build = "dev_" + GIT_HEAD_REV)),
)

With that setup, distributions (sdist or bdist) would be tagged with the string “dev” and the git hash of the latest commit:

$ bin/python setup.py sdist bdist_egg
$ ls dist
pyhello-0.1dev-92ffa06.tar.gz    pyhello-0.1dev_92ffa06-py2.7.egg

Whenever you want to build a stable release (without any “dev” tags), just set the key to empty in your setup.cfg, like this:

[egg_info]
tag_build =

Instalar Python 2.7 y NumPy sobre RedHat 6

:: python

RedHat y centOS tienen la fastidiosa dependencia de su gestor de paquetes, yum, con la versión 2.6 de Python desde tiempos inmemoriales. Lo que en tecnología son unos seis o siete años.

Instalar Python 2.7 no es posible con los repositorios instalados por defecto, y si tienes la mala idea de instalar un repositorio donde sí esté y meterlo como paquete que supedita al python por defecto, te acabas de cargar yum (y la posibilidad más sencilla de deshacer tus cambios).

La mejor manera, pese a lo que pueda parecer, es instalar desde fuentes, haciendo lo que se conoce como “altinstall” (instalación alternativa). Esto crea ejecutables llamados python2.7 bajo /usr/local/bin, que no sobreescriben el comando python normal del sistema; éste sigue apuntando a un ejecutable bajo /usr/bin.

$ wget http://www.python.org/ftp/python/2.7.6/Python-2.7.6.tgz
$ tar xvfz Python-2.7.6.tgz
$ cd Python-2.7.6
$ ./configure
$ make
$ sudo make altinstall

Además de esto, es recomendable instalar un easy_install alternativo también, ya que, si no, todas las librerías de python se instalarán para la 2.6 y no para la 2.7.

$ wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
$ sudo -i
# export PATH=$PATH:/usr/local/bin
# sh setuptools-0.6c11-py2.7.egg
# exit
$ sudo rm /usr/local/bin/easy_install ## remove the symlink that shadows the system easy_install
$ # You may now try the easy_install for 2.7 with e.g.:
$ sudo /usr/local/bin/easy_install-2.7 numpy

Este post es una combinación de dos respuestas en Stack Overflow, y mi propia experiencia con este mismo problema.

Unicode encodings

:: python

¡Por fin he entendido cuál es la diferencia entre Unicode y UTF–8! Y lo que es más importante, cómo se declaran correctamente strings en Unicode en Python.

Unicode es la tabla de equivalencias entre caracteres de (casi) todos los lenguajes humanos, y un número asignado a ese caracter en concreto.

UTF–8 es la manera de comprimir esos números en uno o dos bytes, en lugar de usar 4 bytes por caracter, aprovechando el hecho de que la mayoría de caracteres habituales están en los números bajos (menores de 128).

Es decir, si el “encodificador” encuentra un número < 128, deja ese número; si encuentra uno mayor, pero menor de X (donde X es un límite que no recuerdo), usa dos bytes para expresar este número. El “decodificador” entonces sabe que cada caracter menor de 128 está “solo”, mientras que si encuentra uno mayor, entonces debe leer también el siguiente byte para tener el número correcto con el que ir a la tabla Unicode y obtener el caracter adecuado.

Se puede crear una cadena Unicode en Python usando una u delante de la cadena literal, pero esto no siempre es posible, como cuando se lee de un fichero. Para convertir entre distintas codificaciones existe la función unicode.

La función unicode no utiliza UTF–8 por defecto, sino, absurdamente, ASCII. Por eso sólo es equivalente a poner una u delante una cadena si se especifica que el encoding sea 'utf-8'.

unicode_string = u"Años"

unicode_str = unicode(open('some.txt', 'r').read(), encoding='utf-8')

py2app excludes

:: python

Al generar una aplicación para Mac OS X con py2app, a menudo se generan ejecutables muy pesados, por ejemplo 20 MBs para una aplicación de línea de comandos (!).

Uno de los problemas es que py2app tiende a incluir muchos módulos de la librería estándar de Python que en realidad no hacen falta. Se pueden excluir específicamente incluyéndolos en la opción excludes que se pasa a la función setup en el archivo setup.py usado para generar la aplicación.

Con la lista siguiente, el peso baja de 22 MBs a 6 MBs. Probablemente se puede rebajar aún más.

# File: setup.py

from setuptools import setup

APP = ['path/main.py']
Plist = {}
DATA_FILES = []
OPTIONS = {'argv_emulation': True,
           #'iconfile': 'Icons/ConverterIcon.icns',
           'plist': Plist,
           'excludes': [
               'aetypes',
               'ast',
               'bz2',
               'calendar',
               'codecs',
               'collections',
               'ctypes',
               'distutils',
               'doctest',
               'email',
               'encodings',
               'functools',
               'gzip',
               'inspect',
               'itertools',
               'locale',
               'logging',
               'optparse',
               'pickle',
               'platform',
               'pprint',
               'random',
               're',
               'sets',
               'shutil',
               'socket',
               'ssl',
               'subprocess',
               'tarfile',
               'tempfile',
               'threading',
               'traceback',
               'types',
               'unittest',
               'urllib',
               'urllib2',
               'urlparse',
               'weakref',
               'xml',
               'zipfile',
           ]
           }

setup(
    app=APP,
    package_dir={'': 'src'}, # you may not need this
    data_files=DATA_FILES,
    version="0.1",
    description="<Your description>",
    author="<Your name>",
    author_email="<Your email>",
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)