iconv en python

ago 04 2011

Últimamente he necesitado pasar algunos ficheros de una web a codificación utf-8, codificación de caracteres más acorde con lo que se lleva hoy en día. En sistemas linux es una labor que se puede hacer fácilmente con la utilidad iconv:

$ iconv -f cp850 -t utf8 <fichero_entrada.txt >fichero_salida.txt

Pero hay veces que es necesario realizar esta conversión en windows. Si tenemos instalado python, una forma rápida de hacerlo sería:

$ python -c "import sys,codecs;codecs.EncodedFile(sys.stdout,'latin-1','utf-8').writelines(sys.stdin)" <fichero_entrada.txt >fichero_salida.txt

…¡y todo en una sóla línea!1

Tan sólo puntualizar que esta conversión emplea iteradores, por lo que no tiene que ser un problema el tamaño del fichero de texto a convertir.


  1. Para ver más ejemplos de “one-liners” os recomiendo este artículo de Joe di Castro 

One response so far

Evento Python Madrid 2011

jul 06 2011

El grupo Python-Madrid y el Grupo de Usuarios de Linux de la Universidad Carlos III de Madrid, celebrarán el próximo día 15 de Julio de 10:00 a 18:00 el evento Python Madrid 2011. El evento tendrá lugar en la Universidad Carlos III de Madrid, en su campus de Leganés. Se trataran temas como Python, Django, PyGame y mucho más. Toda la información en www.espython.org

No responses yet

Método __getattribute__

jun 26 2011

Si has seguido hasta ahora la serie de artículos sobre descriptores, habrás visto que buena parte de la magia de los objetos en python se debe al método __getattribute__ que todo objeto adquiere de su antecesor común, la clase object.

En el último artículo, donde hablaba de las optimizaciones de los métodos especiales, también comentaba algunas optimizaciones que tenían qué ver con el método __getattribute__ y proponía un ejercicio:

¿Sabrías qué es lo que pasa en el siguiente caso? ¿Se invoca el método getattribute en algún momento? ¿Sería una llamada implícita o explícita?

   obj.__getattribute__("__getattribute__")

Quien se enfrenta a este código por primera vez, lo primero que piensa es que se va a producir una autorecursividad puesto que en el acceso al método __getattribute__ se debería invocar el propio método __getattribute__ y así indefinidamente.

Si embargo, cuando se prueba se ve que funciona tal y como se espera. Entonces, ¿cómo se evita la recursividad?

En el artículo de optimizaciones de los métodos especiales hablábamos de dos optimizaciones (atajos) de las llamadas implícitas a métodos especiales:

  1. Implícitamente, sólo se buscará métodos especiales en la clase, nunca en el diccionario del objeto.

  2. Implícitamente, nunca se accederá a un método especial a través de __getattribute__

La intuición nos dice que aquí está la respuesta de que no tengamos autorecursividad.

Antes de analizar lo que está pasando, señalar que en el acceso a atributos se usa el operador ‘.‘ (punto) que, como cualquier otro operador, está sujeto a las mismas optimizaciones que hemos apuntado. Para su labor, el operador . empleará el método especial __getattribute__.

La invocación obj.__getattribute__("atributo") se produce en dos pasos:

  1. Implícitamente, el operador ‘.‘ accede directamente al método __getattribute__, aplicando las optimizaciones.
  2. Se invoca explícitamente a __getattribute__ para que retorne el valor del "atributo"

Así pues, el resultado final consiste en la combinación de una llamada implícita y otra explícita.

Como corolario, se puede afirmar que “Nunca se invocará a __getattribute__ para acceder a __getattribute__“. No será la primera vez que alguien lo haya intentado.

One response so far

Optimizaciones con los Métodos Especiales

jun 24 2011

Métodos Especiales

Dentro del llamado “modelo de datos” de python, la sobrecarga de operadores, tan característica de la programación orientada a objetos, se realiza mediante la definición de algunos métodos de nombre especial. A través de esta técnica se define cómo se comportará una clase frente a los operadores del lenguaje.

Estos métodos especiales son invocados implícitamente por el intérprete para realizar la operación, decidiendo en tiempo de ejecución cuáles de los métodos serán más adecuados para realizar la operación.

Por ejemplo, el método especial __nonzero__ determina el valor True o False de la instancia, útil para expresiones condicionales. De no estar definido este método, se considera el método __len__ (usado por la función len()) para determinar como False si tiene tamaño cero. En caso de que tampoco cuente con este método, se considera siempre como True.

Al ser parte intrínseca del lengueje, estos métodos especiales inciden seriamente en redimiento del intérprete. Con sólo definir el método especial __getattribute__, por ejemplo, encargado de controlar todo acceso a los atributos de un objeto, la sobrecarga del intérprete se volvería pesada y lenta sin posibilidad de mejorar mucho mediante rutinas en lenguaje C. Para evitar este impacto negativo, se toman algunas “optimizaciones” que sólo son aplicables a los métodos especiales.

Optimizaciones de Métodos Especiales

Con los “Métodos Especiales” se dan dos optimizaciones en la invocación “implícita” de un método especial:

  1. Implícitamente, sólo se buscará métodos especiales en la clase, nunca en el diccionario del objeto.

  2. Implícitamente, nunca se accederá a un método especial a través de __getattribute__

Estas dos optimizaciones (más bien “atajos“) son origen de muchos errores y malas interpretaciones, responsable en primera instancia de que determinado código no funcione como se esperaba en teoría.

Recalcar que estas optimizaciones sólo ocurren en las invocaciones “implícitas”. Si hacemos la invocación explícita, a través del nombre especial del método, entonces se sigue el procedimiento estándar de búsqueda de atributos.

Veamos algunos ejemplos y contraejemplos:

>>> class C(object):
...     def __len__(self):
...         return 5
...
>>> obj=C()
>>> len(obj)
5
>>>
>>> obj.__len__=lambda:100
>>> len(obj)
5
>>> obj.__len__()
100

La clase responde a la función estándar len() a través del método __len__. Como se ve, aunque cambiemos el método en el diccionario del objeto, la función len() sigue usando el método especial de la clase. Si se invoca el método explícitamente (obj.__len__()), entonces sí que se usará el método del diccionario del objeto.

Primera conclusión:

Para que funcione correctamente, toda técnica dinámica que involucre métodos especiales ha de actuar sobre la clase.

Estudiemos otro ejemplo:

class C(object):

    a=100

    def __getattribute__(self, attr):
        value=super(C,self).__getattribute__(attr)
        print "Desde C # '%r'.'%s'==%r"%(self,attr,value)
        return value
>>> obj=C()
>>> obj.a
Desde C # '<__main__.C object at 0xb77d4b2c>'.'a'==100
100
>>> C.a
100

En la prueba, accedemos al atributo a a través de la instancia obj y a través de la clase C. En el primer caso, se llama a __getattribute__ para acceder al atributo; mientras que en el segundo no lo hace. Muy a menudo se piensa errónemente que la clase usa implícitamente sus propios métodos especiales y no es así.

Como objeto que es, una clase también es una instancia. A la clase de una clase la denominaremos “metaclase”1 y tendrá como ancestro superior la clase type (del mismo modo que toda clase tenía como ancestro la clase object)2. Es a esta metaclase donde se buscan los métodos especiales de la propia clase:

class Meta(type):
    def __getattribute__(cls, attr):
        value=super(Meta,cls).__getattribute__(attr)
        print "Desde Meta# '%r'.'%s'==%r"%(cls,attr,value)
        return value

class C(object):

    __metaclass__=Meta

    a=100

    def __getattribute__(self, attr):
        value=super(C,self).__getattribute__(attr)
        print "Desde C # '%r'.'%s'==%r"%(self,attr,value)
        return value

De donde podemos sacar la segunda conclusión:

Los métodos especiales que operen con clases deberán ir en la metaclase.

Haciendo un fundido de los ejemplos anteriores:

class Meta(type):
    def __getattribute__(cls, attr):
        value=super(Meta,cls).__getattribute__(attr)
        print "Desde Meta# '%r'.'%s'==%r"%(cls,attr,value)
        return value
   
    def __len__(cls):
        return 999

class C(object):

    __metaclass__=Meta

    def __getattribute__(self, attr):
        value=super(C,self).__getattribute__(attr)
        print "Desde C # '%r'.'%s'==%r"%(self,attr,value)
        return value

    def __len__(self):
        return 100

Con este código, se puede comprobar las siguientes formas de invocar __len__ para la instancia:

>>> obj=C()
>>> len(obj)
100
>>> len(C)
999

Aquí observamos que la llamada implícita a __len__ se salta el __getattribute__ tanto de la clase como de la metaclase como ya estaba anunciado.

Analicemos algunas llamadas explícitas (recomiendo ir probándolas):

  • vía la instancia obj.__len__(): se usará el __getattribute__ de la clase para buscar el método __len__
  • vía el tipo type(obj).__len__(obj): se usará el __getattribute__ de la metaclase para buscar el método __len__. Por orden de prioridad, se usará el __len__ de la clase.
  • vía la metaclase type(C).__len__(C) se usará el __getattribute__ de type, invocando finalmente el __len__ de la metaclase

En el orden de búsqueda, tiene prioridad el método __len__ definido en la clase frente al definido en la metaclase. Por ese motivo no puede emplearse la llamada explícita C.__len__() ya que no corresponde con un método de clase.

Ejercicio de comprensión

¿Sabrías qué es lo que pasa en el siguiente caso? ¿Se invoca el método __getattribute__ en algún momento? ¿Sería una llamada implícita o explícita?

obj.__getattribute__("__getattribute__")

Conclusión

Entendiendo cómo funcionan estas optimizaciones vistas con los métodos especiales, y con bastante cuidado, será posible hacer que nuestras clases se comporten según lo esperado en las operaciones normales. Una buena planificación de nuestro modelo de datos según lo que espera el intérprete conseguirá que nuestro código sea más legible y fácil de mantener.


  1. En próximos artículos veremos el funcionamiento de las metaclases 

  2. Siguiendo con la relaciones entre clases y objetos, la clase type es a la vez instancia de type y subclase de object

One response so far

Descriptores – Parte 2

jun 21 2011

¿Cómo funciona un descriptor?

Todos los objetos y todas las clases que derivan de object1 adquieren de él un método llamado __getattribute__. Siempre a través de este método se accede a los atributos, y es en este método donde se hace toda la magia de los descriptores, de modo que un acceso al atributo obj.x se transformará en una llamada a type(obj).__dict__['x'].__get__(obj, type(obj)) si el atributo se trate de un descriptor. Una expresión casi ininteligible que va a requerir alguna que otra explicación. Lo importante es saber que al sobrecargar el método __getattribute__ deberemos cuidarnos de invocar al método de la clase padre si queremos que los descriptores sigan funcionando con normalidad.

Atributos de un objeto

De todos los atributos que tiene un objeto python, algunos son “Atributos especiales” que aporta python para su funcionamiento interno como son __class__ o __bases__. Estos atributos son bastante antipáticos de manejar ya que, o bien no son reportados por la función dir(), o bien tienen restricciones para ser modificados.

Por otro lado, están los atributos definidos dinámicamente por el programa que forman lo que se conoce como “diccionario del objeto”. Estos atributos se guandan en el (también) atributo __dict__.

Los “atributos de tipo” son los atributos asociados a un objeto por pertenencia a una clase. Estos atributos pueden estar enmascarados por los atributos del diccionario del objeto, algo muy útil cuando se aplican “técnicas dinámicas” de parcheo.

Hay que tener en cuenta que algunos de los tipos estándar como list,tuple,dict,… no tienen atributo __dict__ con lo que no tienen diccionario donde añadir o suplantar atributos dinámicamente. La única opción pasa por derivar clases a partir de ellos para añadir allí los atributos deseados.

Búsqueda de atributos

Al buscar un atributo obj.attr, se sigue un orden determinado de prioridad según el tipo de atributo que se esté buscando:

  1. Atributos especiales: son los que tienen mayor prioridad.

  2. Descriptores de datos: se buscan en el diccionario de la clase (obj.__class__.__dict__) y en todos los diccionarios de las clases padre. Si se encuentra, se retorna el resultado del descriptor (la expresión tan chula que puse al principio del artículo). Si no es un descriptor de datos, entonces se ignora y se sigue buscando.

  3. Atributos del diccionario del objeto: se busca el atributo en el diccionario del objeto (obj.__dict__). Si obj fuera una clase (==isinstance(obj,type)), entonces también se buscaría en los diccionarios de las clases padre (obj.__bases__) y, de ser un descriptor de datos, se devolverá el resultado del descriptor en su lugar.

  4. Descriptores de no-datos: se repite el paso 2, pero esta vez se buscan descriptores de no-datos.

  5. Método __getattr__: por último, si no ha habido éxito en la búsqueda del atributo, se intenta invocar el método __getattr__, de existir, para delegar en él.

  6. Si todo ha fallado, se termina la búsqueda retornando un error AttributeError.

En resumidas cuentas, se priorizan los descriptores de datos a las variables de instancia, las variables de instancia a los descriptores de no-datos y, con la más baja prioridad, se invocaría el método __getattr__.

Remarcar la diferencia que hay entre un descriptor de datos y uno de no-datos en el orden de búsqueda. Por el simple hecho de añadir un método __get__, un descriptor se pondría por delante de los atributos del diccionario del objeto en el orden de búsqueda. También apuntar que sólo se buscan descriptores entre los atributos de clase, por lo que no tendrá sentido asignar descriptores en otro atributos.

En el caso de la asignación de atributos, se seguirían estos pasos:

  1. Se busca descriptores de datos en el diccionario de la clase (obj.__class__.__dict__) y todos los diccionarios de las clases padre. Si se encuentra un descriptor de datos, entonces se invoca el método __set__ del descriptor.

  2. Se invoca el método __setattr__, si existe, para delegar en él.

  3. Como última prioridad, se inserta el atributo en el diccionario del objeto.

En estos pasos no aparecen los descriptores de no-datos. Si realizamos una asignación sobre un descriptor de no-datos, acabaría siendo reemplazado como cualquier atributo normal.

¿Se puede saltar un descriptor de datos?

La prioridad de los descriptores de datos frente al resto de atributos hace prácticamente imposible saltárselos para acceder directamente a un atributo. Todo acceso al atributo pasa por sus manos, regla que se aplica también con el propio descriptor y que da origen a bastantes recursividades sin fin. Por ello es habitual que el descriptor mantenga un atributo auxiliar “privado”, ya que de otro modo no tendrá otra forma de acceso directo.

Algo que sí podemos hacer es cambiar las prioridades con la definición de un método __getattribute__ propio. Como ejemplo, se podría priorizar los atributos del diccionario frente a los descriptores de esta manera:

class Descrip(object):
    def __init__(self, mul):
        self.mul=mul
    def __get__(self, obj, cls=None):
        return obj.value*self.mul
    def __set__(self, obj, value):
        raise AttributeError
    def __delete__(self, obj):
        del self

class C(object):
    a12=Descrip(12)
    a200=Descrip(200)
   
    def __init__(self,value):
        self.value=value
       
    def __getattribute__(self, attr):
        dic=super(C,self).__getattribute__("__dict__")
        if attr in dic:
            return dic[attr]
        else:
            return super(C,self).__getattribute__(attr)

c=C(2)

print c.a12  #--> 24  (valor del descriptor)
c.__dict__["a12"]=100
print c.a12  #--> 100 (valor del diccionario)

  1. En python 2.x, a las clases que derivan de object se las denomina “nuevas clases” por contraste con las clases que había hasta ese momento. En python 3.x, todas las clases derivarán por defecto de object

2 responses so far

Descriptores – Parte 1

jun 19 2011

Cuando accedemos a los atributos de un objeto en python, a veces existen unos intermediarios casi imperceptibles llamados “descriptores” que son los responsables últimos del funcionamiento de la programación orientada a objetos. Están detrás de propiedades, métodos, métodos estáticos, métodos de clase y del mecanismo super() responsable de la herencia múltiple. Su labor es imprescindible y, sin embargo, son los grandes desconocidos del lenguaje.

Protocolo “descriptor”

Por protocolo “descriptor” se entiende la sustitución de un atributo por un objeto que intermedia en los accesos a ese atributo. Tal vez, las propiedades (property) puedan ser el ejemplo más visible de los descriptores, pero veremos que los descriptores están más presentes de lo podemos pensar.

Como descripción formal del protocolo descriptor, podemos decir que un descriptor es todo objeto que tenga definido al menos uno de estos tres métodos:

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

Respectivamente, serían los métodos para obtener, asignar y borrar un atributo del objeto obj.

Podemos distinguir dos tipos de descriptores:

  • Descriptor de datos (data descriptor): cuando tiene definidos los métodos __get__ y __set__. Es el que usaremos para acceder y cambiar el valor de un atributo.
  • Descriptor de no-datos (non-data descriptor): cuando sólo tiene definido el método __get__. Su uso será casi exclusivo para acceso a los métodos de un objeto.

Como veremos más adelante, distinguir entre estos dos tipos de descriptores es muy importante, ya que cada uno tiene distinto orden de preferencia cuando se buscan atributos en una jerarquía de clases.

Implementación de los “Descriptores de Datos”

Empecemos por un ejemplo:

class Desc(object):
    def __init__(self, mul):
        self.mul=mul
    def __get__(self, obj, cls=None):
        return obj.value*self.mul

class C(object):
    a12=Desc(12)
    a200=Desc(200)
   
    def __init__(self,value):
        self.value=value

c=C(2)
print c.value, c.a12, c.a200  #--> 2 24 400

Los atributos a12 y a200 están definidos por instancias del descriptor Desc(). Cuando accedemos a estos atributos, en lugar de devolvernos el descriptor, nos devuelve el valor resultante del método __get__ del descriptor.

De modo más explícito, sería:

c.a12 --> c.a12.__get__(c)

Al no estar definido el método __set__, se pueden reasignar estos atributos sin mayor problema, aunque dejarían así de estar controlado por el descriptor:

c.a12=12

Para completar el protocolo de descriptor de datos basta añadir un método __set__:

class Descrip(object):
    def __init__(self, mul):
        self.mul=mul
    def __get__(self, obj, cls=None):
        return obj.value*self.mul
    def __set__(self, obj, value):
        obj.value=value

La asignación anterior, se nos convertiría en:

c.a12=12 --> c.a12.__set__(c, 12)

Como se intuye, el descriptor tiene aquí total control sobre el valor final que se guardará como atributo. Como posible utilización, se pueden crear atributos de sólo lectura, para lo que bastaría con que el método __set__ genere un error AttributeError si se intenta modificar el atributo:

class Descrip(object):
    def __init__(self, mul):
        self.mul=mul
    def __get__(self, obj, cls=None):
        return obj.value*self.mul
    def __set__(self, obj, value):
        raise AttributeError

Tan sólo falta añadir el método __delete__ para completar el protocolo. No hay que olvidarse de este método si queremos que un atributo de sólo lectura aún pueda ser modificado mediante un borrado previo a su reasignación:

class Descrip(object):
    def __init__(self, mul):
        self.mul=mul
    def __get__(self, obj, cls=None):
        return obj.value*self.mul
    def __set__(self, obj, value):
        raise AttributeError
    def __delete__(self, obj):
        del self

c=C(2)

print c.a12 #--> 24

c.a12=100 #ERROR: AttributeError

del C.a12
c.a12=100

print c.a12  #--> 100 (no descriptor)

Saltarse al descriptor

Llegados aquí, se nos plantea una pregunta: ¿hay algún modo de acceder a los atributos sin pasar por su descriptor?

Y no es para nada una pregunta caprichosa. El descriptor necesita algún modo de acceder a los atributos que está gestionando sin tener que pasar por sí mismo. Tal vez, se podría hacer a través del diccionario del objeto, accesible como __dict__:

c.__dict__["a12"]=100  #equivalente a c.a12=100

Si lo pruebas, verás que no funciona. Cuando se busca un atributo, primero se busca entre los atributos de la clase antes de mirar en el diccionario de la instancia. Este orden de prioridades lo veremos en el próximo artículo cuando veamos el funcionamiento interno de un descriptor.

One response so far

Último item de un iterable

jun 06 2011

Algunas veces necesitamos obtener el último item de un iterador. Para ello se suele iterar hasta agotar el iterador:

for it in iterador:
    pass

last_item=it

Una alternativa que se ve bastante es convertir previamente el iterable en una lista:

last_item=list(iterador)[-1]

Tiene el incoveniente de gastar recursos inultilmente al crear una lista de la que sólo nos interesa su último elemento.

En stackoverflow se pueden ver algunas respuestas a este problema, pero ninguna me convence lo suficiente.

Aquí pongo mi solución, simple y elegante donde las haya:

last_item=max(enumerate(iterador))[1]

One response so far

Estudio función factorial

jun 06 2011

Hace un tiempo me dió por recopilar distintas funciones en python para calcular el factorial. Aquí van todas, algunas bastante curiosas. Si conoces algún tipo más, no dejes de añadirla en los comentarios.

Versión recursiva

Todo programador ha tenido que ver esta definición como ejemplo de funciones recursivas :

def fact(n):
    if n==0:
        return 1
    else:
        return n*fact(n-1)

Se podría hacer algo más compacta usando el operador ternario:

def fact(n):
    return 1 if n==0 else n*fact(n-1)

Como toda función recursiva en python, existe el peligro de que nunca termine la función. Es el motivo por el que python tiene fijado un límite de recursividad dado por sys.getrecursionlimit(), que por defecto es de 1000 invocaciones recursivas o, lo que es lo mismo, que no podamos calcular factoriales mayores de 1000.

Podemos incrementar el límite con sys.setrecursionlimit(n), pero seguirá siendo una solución provisional. Lo mejor es pasarnos a una solución “iterativa”.

Versión iterativa

También es una de la funciones más conocidas por todo programador:

def fact(n):
    res=1
    for i in xrange(1,n+1):
        res*=n
    return res

Normalmente, todo lenguaje tiene un límite en el tamaño de un entero que hace que esta función no pueda calcular factoriales muy grandes. Pero python tiene la característica de pasar de entero a entero largo cuando así lo requiera la operación, lo que hace que se puede calcular cualquier número factorial, con el único límite de tiempo para calcularlo. Por lo general, con número grandes cuesta menos calcular el factorial que imprimirlos en pantalla.

Versión aproximada (función de Stirling)

Para número muy grandes, existe una aproximación llamada “Aproximación de Stirling” que se suele usar en mecánica estadística.

import math
def fact(n):
    return math.sqrt(2*math.pi*n)*math.pow(n/math.e,n)

Lamentablemente, los números reales (tipo double) son aquí una limitación de tamaño, por lo que no podemos hacer cálculos para números altos (precisamente, para los que teóricamente iba mejor esta función).

Versiones one-line

Muchas veces, los programadores se toman como reto poder expresar una fórmula compleja en una sóla línea, de modo que se pueda sustituir la llamada a la función por la definición de esta directamente. Son las llamadas funciones “oneline”.

reduce(lambda x,y:x*y,xrange(1,n+1),1)

Podemos aprovechar que tenemos el operador multiplicación y con ello evitar la función lambda (últimamente, en desuso):

import operator
reduce(operator.mul, xrange(1,n+1),1)

Algo más bizarro, evitando lambda y reduce:

[j for j in [1] for i in range(2,n+1) for j in [j*i]][-1]

Esta versión es en realidad un “reduce sin usar reduce”. Para entender cómo funciona, lo mejor es verlo como varios fors anidados:

def fact(n):
    for j in [1]:
        for i in range(2,n+1):
            for j in [j*i]:
                yield j

res=list(fact(n))[-1]

El primer for tan sólo sirve para dar una valor inicial a la variable j, y el tercer for sería el equivalente “oneline” de j=j*i.

En realidad, esta función no está muy optimizada ya que mantiene en memoria la lista completa de todos los resultados intermedios. Un modo más inteligente de usar esta expresión sería como un iterador, donde los resultados intermedios ya no son almacenados:

for res in (j for j in [1] for i in range(2,n+1) for j in [j*i]):
    pass

Aunque funciona perfectamente, no se puede considerar como función de una sóla línea. Para conseguirlo, tenemos que ir a algo totalmente críptico, incluyendo reduce y lambda, que acabaría siendo el siguiente engendro:

reduce(lambda x,y:y,(j for j in [1] for i in range(2,n+1) for j in [j*i]))

Actualización: Si queremos alguna solución sin reduce ni lambda, teniendo en cuenta que la función factorial es incremental, podemos emplear max() para obtener una versión con iterador de una sóla línea:

max(j for j in [1] for i in range(2,n+1) for j in [j*i])

¿Se os ocurren otras formas de expresar el factorial en una sóla línea?

6 responses so far

Instalación cx_Oracle para ia64

jun 04 2011

Itanium, un sistema ¿obsoleto?

Últimamente, algunos grandes de la informática como Microsoft, Oracle y RedHat han determinado que los sistemas Itanium han quedado obsoletos con lo que dejarán de darles soporte, aunque hace sólo unos pocos años estos sistemas de 64bits se ofertaban al mercado como el futuro de los sistemas servidores empresariales.

En este punto, me encuentro que tengo en mi trabajo algunos servidores Itanium II que, lejos de considerarlos obsoletos, me parecen perfectos para alojar en ellos algunos de los proyectos python desarrollados en plone o django. Con la reciente salida de la distribución Debian “Squeeze”, y con la ayuda de un alumno que vino a hacer sus prácticas con nosotros, me animé a sustituir el Linux RedHat que se había quedado sin mantenimiento por una la última versión ia64 de debian. Esta versión es algo más limitada en paquetes que las versiones para arquitecturas i686 y amd64, pero con un poco de esfuerzo es posible completar la instalación compilando paquetes a partir de los fuentes. Y puedo afirmar que ha sido todo un éxito. Vuelvo a tener un sistema potente, completo y, sobre todo, mucho más libre.

La idea de este artículo es contar cómo instalar y configurar, en debian para itanium, del cliente de oracle y el conector cx_Oracle para python.

Instalación cliente oracle

Lo primero es descargar desde la web de oracle del cliente. Para ello hay que descargar los siguiente paquetes para itanium, previo registro gratuito:

basic-10.2.0.4.0-linux-ia64.zip
sdk-10.2.0.4.0-linux-ia64.zip
sqlplus-10.2.0.4.0-linux-ia64.zip

jdbc-10.2.0.4.0-linux-ia64.zip
odbc-10.2.0.4.0-linux-ia64.zip

Los dos últimos son opcionales, pero siempre pueden venir bien guardarlos por si hacen falta en el futuro con alguna aplicación (el conector jdbc nos vendrá bien para usarlo con jython).

Se decomprimen estos paquetes en el mismo directorio y obtendremos un único directorio llamado:

instantclient_10_2

Movemos este directorio a un lugar adecuado, por ejemplo a:

/opt/oracle/instantclient_10_2

No olvidar darle permisos adecuados, sobre todo si queremos que el servidor apache (mod_wsgi) pueda acceder a él (puedo asegurar que se pierde mucho tiempo hasta que averiguas este fallo tan tonto):

# chmod +rx /opt/oracle/instantclient_10_2/

Cuando pasemos a compilar cx_Oracle, veremos algunos fallos por no ser capaz de encontrar algunas librerías compatidas. Para evitarlo, debemos crear algunos enlaces:

# cd /opt/oracle
# ln -s libclntsh.so.10.1 libclntsh.so
# ln -s libclntsh.so.10.1 libclntsh.dylib

Ahora tenemos que actualizar las referencias a la libreras compartidas. Creamos el fichero /etc/ld.so.conf.d/oracle.conf con la siguiente línea:

/opt/oracle/instantclient_10_2

Y actualizamos:

# ldconfig

Para comprobar que funciona bien, podemos probar la utilidad sqlplus a ver si conectamos. Esta utilidad viene dentro del directorio.

Para poder compilar el paquete cx_Oracle se necesita unas cuantas variables de entorno que meteremos en el.profile`:

    export ORACLE_HOME="/opt/oracle/instantclient_10_2"
    export DYLD_LIBRARY_PATH="$ORACLE_HOME"
    export SQLPATH="$ORACLE_HOME"
 
    export PATH="$PATH:$ORACLE_HOME"

Para instalar el paquete cx_Oracle, podemos instalar antes el paquete python-pip que nos ofrece la utilidad pip que nos hará más fácil la instalación:

# apt-get install python-pip
# pip install cx_Oracle

Con ésto se debería descargar, compilar e instalar cx_Oracle. Saldrán algunas advertencias que podemos ignorar. Si todo ha salido bien, podemos pasar a probar si podemos importar el módulo:

    # python
    Python 2.6.6 (r266:84292, Dec 27 2010, 21:05:55)
    [GCC 4.4.5] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import cx_Oracle
    >>>

Con el módulo instalado, lo he probado desde el “backend” de oracle para django y todo funciona a la perfección.

One response so far

Porqué uso jython

may 30 2011

Python de sabores

Cuando hablamos de python, normalmente nos referimos a su versión canónica implementada en lenguaje C, también conocida por “CPython”. Toda la evolución del lenguaje se realiza alrededor de esta implementación y pocas veces se piensa en otras implementaciones. Pero una de las características de python es ser multiplaforma, y lo demuestra con implementaciones para varias plataformas. Algunas de las implementaciones más interesantes serían:

  • Jython: Implementación para JVM que se integra y hace uso de la numerosísismas librerías Java y entornos J2EE. Combina un entorno robusto que rodea a Java con la programación dinámica de python.

  • IronPython: Una de las implementaciones para la plataforma .Net y mono. Se integra con el framework .Net de Microsoft, llegando a una eficiencia bastante cercana al lenguaje C#.

  • PyPy: Un python escrito en python. Su objetivo es librar a python de las limitaciones impuestas por el lenguaje C, dando lugar a una implementación puramente python.

Cada una de estas implementaciones lleva su propio ritmo de desarrollo, siguiendo la estela de CPython. En estos momentos, el lenguaje python (CPython) está parado debido a la moratoria PEP-3003 de dos años, a punto de terminar, que se está aprovechando para acercar a CPython el resto de implementaciones y así unir las distintas comunidades de desarrolladores en el siguiente avance de Python hacia la versión 3.

Python con sabor Java

De entre todas la implementaciones de python, la que uso habitualmente en mi trabajo es jython. Sin entrar en polémicas sobre si un lenguaje es mejor que otro, cuando uno se decide por un lenguaje híbrido como jython lo hace desde el convencimiento de que la mejor solución consiste en usar lo bueno de ambos mundos. Por un lado, los entornos java ofrecen robustez y librerías bien probadas para cualquier aplicación empresarial; por otro lado, jython ofrece técnicas de programación dinámica que mejoran la productividad.

Entrando en detalle, este sería un listado de ventajas e incovenientes de usar jython sobre python y/o java:

Multiplataforma

Aunque python ya venga instalado en prácticamente todo sistema linux o macintosh, o incluso aparezca empotrado en aplicaciones como openoffice/libreoffice, no siempre es posible controlar qué versiones, módulos y librerías hay instalados en el sistema. La disparidad de sistemas y configuraciones hace inviable contar con un entorno homogéno para ejecutar nuestro programa python. Por lo general se desarrolla con una configuración fijada, con la esperanza de que el sistema de producción cuente con la misma configuración.

Jython se aprovecha la difusión de la máquina virtual JVM en casi todos los sistemas. Esta máquina virtual nos crea una capa de abstracción que facilita el traslado de la misma configuración de nuestro entorno de desarrollo al sistema de producción, tan fácil como copiar un fichero de un sistema a otro.

Además resulta sencillo crear un entorno jython portable1 en un pendrive o un disco duro externo, con lo que podemos llevarnos nuestro entorno de desarrollo con nosotros.

Velocidad y memoria

Existe cierta idea equivocada que los programas java son lentos y consumen mucha memoria. En realidad, java se inventó para sistemas empotrados, como demostraría las aplicaciones y juegos para existen para teléfonos móviles. Hoy en día, una aplicación para java es bastante rápida una vez arrancada la máquina virtual, y el consumo de memoria puede delimitarse para no agobiar al resto del sistema.

Así mismo, el aspecto gráfico de las aplicaciones java es muy similar a las aplicaciones nativas, disponiendo de interfaces de bajo nivel para control gráficos y dispositivos de entrada y salida.

Sincronismo multihilo

Con el tiempo, la gestión de los hilos de ejecución en una máquina virtual JVM ha llegado a superar cualquier otra implementación gracias a la capa de abstracción que impone. En C, es una labor del programador realizar esta gestión a mano o, con algo de suerte, disponga de alguna librería que facilite el sistema operativo donde se vaya a ejecutar la aplicación, no consiguiendo toda la estabilidad que sería deseable (un ejemplo sería la inestabilidad de algunas extensiones de apache frente a la robustez de los servidores de aplicaciones web para java).

Por ello jython delega esta gestión de hilos delegando en el JVM e, incluso, delega en él la recolección de basura (garbage collection). Como consecuencia directa, en jython no existe el odioso GIL de CPython que impide que dos hilos se ejecuten simultáneamente.

Base de datos

La conectividad con bases de datos en Jython se realiza mediante los conectores JDBC de java, que es una especie de estándar en java que toda base de datos ofrece. En jython, gracias al grandioso módulo zxJDBC podemos usar conexiones jdbc del modo habitual en python, o sea, mediante la DB-API2. La ventaja es que sólo necesitamos añadir el conector jdbc (un fichero .jar) a la ruta de clases del JVM, sin tener que instalar la librería o todo el cliente completo como exigen algunas bases de datos en CPython.

En mi caso concreto, necesito conectarme a varios tipos de bases de datos diferentes para interoperar entre ellos. Me resultaba complejo tener que ir instalando las librerías de conexión para el sistema, con algunos pidiendo que te instales el cliente completo con licencia. Además, tenía que instalar los módulos de python, que no siempre estaban actualizados o, simplemente, no existían. Con jython, tengo todos los conectores jdbc en un directorio y con sólo un módulo, zxJDBC, tengo todo lo necesario.

¿Y sqlite? Python incluye sqlite como base de datos sencilla. Al estar programada en C no aparece con jython. Como alternativa, podríamos usar la javadb (aka derbydb) que se suele instalar junto con el java, aunque es algo de lo que no podemos fiarnos. Mi recomendación es usar h2. En un fichero de poco más de 1 Mb tenemos una implementación completa de base de datos relacional (SQL-92), tremendamente rápida en comparación con otras, que tiene acceso a través del sistema de ficheros al estilo sqlite o acceder en compartido como servidor TCP/IP, con interface de línea de comandos, con su administrador web,… Es ideal para hacer de intermediario entre bases de datos, aceptando enlaces JDBC a otras bases de datos e importaciones/exportaciones en formato CSV.

Contendores Java

Otro aspecto interesante son los contendores para aplicaciones java (J2EE). Una aplicación jython puede, del mismo modo que hace java, desplegarse en estos contenedores para aprovechar sus servicios j2ee como sería un pool de conexiones para una base de datos. Impresionante, resulta ver que con la versión 3 de glassfish ya se incluye un contenedor específico para jython, lo que permite desplegar en él aplicaciones desarrolladas en django ó pylons (éste último todavía en fase de pruebas) sin cambiar una sóla línea de código.


(CONTINUARÁ)

Espero haber conseguido interesarte con este artículo. Mi intención es continuar hablando de jython en próximos artículos y mostrar su uso en desarrollos de todo tipo. Nos veremos pronto.


  1. En próximos artículos veremos cómo usar virtualenv para crear estos entornos portables. 

3 responses so far

« Newer posts Older posts »