Mostrando las entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando las entradas con la etiqueta programación. Mostrar todas las entradas

Checkbox estilo interruptor con CSS3

Últimamente se han puesto de moda los interruptores horizontales (véase iOS o Gnome3) en reemplazo de los checkbox cuadrados y aburridos de toda la vida, y buscando por ahí implementaciones para formularios web sólo encontré soluciones poco elegantes usando imágenes. Así que me puse manos a la obra, lo conseguí, y comparto el resultado (espero ahorrar a alguien alguna hora).

Un poco mucho de CSS hace la magia:

.cool_checkbox, .cool_checkbox *{
    height:30px; margin:0; padding:0;
    display:inline-block;
    white-space:nowrap; user-select: none;
}
.cool_checkbox{
    font-family:sans-serif;
    width:94px; position:relative; overflow:hidden;
    border:1px solid gray; border-radius:6px;
    background:gray;
}

.cool_checkbox input{
    display:none;
}
.cool_checkbox label{
    position:absolute; width:150px;
    cursor:pointer;
    transition: left 0.2s ease-in-out;
    vertical-align:top;
    line-height:100%;
    left:-56px;
    display:block;
}
.cool_checkbox > input:checked + label{
    left:0px;
}

.cool_checkbox > input:disabled + label{
    opacity:0.8;
}

.cool_checkbox label > span{
    text-align:left; text-transform:uppercase;
    font-weight:bolder; font-family:sans;
    width:50px; position:relative; z-index:0;
    box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.5);
    border-radius: 5px;
    vertical-align:middle;
    line-height:30px;
}
.cool_checkbox label > span:nth-child(1){
    padding-right:15px; text-align:right;
    background:#4085EC;
    background-image: linear-gradient(bottom, #76AEFC 0%, #4D8EEF 49%, #4085EC 50%, #336ED4 100%);
    color:white; text-shadow: 0px -1px 0.5px #1b3d72;
}

.cool_checkbox label > span:nth-child(2){
    width:40px; margin:-2px -10px; z-index:1; border-radius: 6px;height:32px;
    background:gray;
    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
}

.cool_checkbox label > span:nth-child(2) > span{
    width:38px; height:29px; margin:1px;
    border-top:1px solid white;
    overflow:hidden; text-indent:-999em;
    border-radius: 5px;
    background:#e4e4e4; background-image: linear-gradient(bottom, #FBFBFB 0%, #CECECE 100%);
}

.cool_checkbox label > span:nth-child(3){
    padding-left:15px; text-align:left;
    color:#7e7e7e;
    background:#EFEFEF; background-image: linear-gradient(bottom, #FEFEFE 0%, #F9F9F9 49%, #EFEFEF 50%, #E7E7E7 100%);
}

/* A partir de aquí: arreglos temporales hasta que CSS3 esté bien soportado */
.cool_checkbox{
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -webkit-border-radius: 6px;
    -moz-border-radius: 6px;
}
.cool_checkbox label{
    -moz-transition: left 0.2s ease-in-out;
    -webkit-transition: left 0.2s ease-in-out;
    -o-transition: left 0.2s ease-in-out;
}
.cool_checkbox label > span{
    -moz-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.5);
    -webkit-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.5);
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
}
.cool_checkbox label > span:nth-child(1){
    background-image: -o-linear-gradient(bottom, #76AEFC 0%, #4D8EEF 49%, #4085EC 50%, #336ED4 100%);
    background-image: -moz-linear-gradient(bottom, #76AEFC 0%, #4D8EEF 49%, #4085EC 50%, #336ED4 100%);
    background-image: -webkit-linear-gradient(bottom, #76AEFC 0%, #4D8EEF 49%, #4085EC 50%, #336ED4 100%);
    background-image: -ms-linear-gradient(bottom, #76AEFC 0%, #4D8EEF 49%, #4085EC 50%, #336ED4 100%);
}
.cool_checkbox label > span:nth-child(2){
    -webkit-border-radius: 6px;
    -moz-border-radius: 6px;
    -webkit-box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
    -moz-box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
}
.cool_checkbox label > span:nth-child(2) > span{
    background-image: -o-linear-gradient(bottom, #FBFBFB 0%, #CECECE 100%);
    background-image: -moz-linear-gradient(bottom, #FBFBFB 0%, #CECECE 100%);
    background-image: -webkit-linear-gradient(bottom, #FBFBFB 0%, #CECECE 100%);
    background-image: -ms-linear-gradient(bottom, #FBFBFB 0%, #CECECE 100%);
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
}
.cool_checkbox label > span:nth-child(3){
    background-image: -o-linear-gradient(bottom, #FEFEFE 0%, #F9F9F9 49%, #EFEFEF 50%, #E7E7E7 100%);
    background-image: -moz-linear-gradient(bottom, #FEFEFE 0%, #F9F9F9 49%, #EFEFEF 50%, #E7E7E7 100%);
    background-image: -webkit-linear-gradient(bottom, #FEFEFE 0%, #F9F9F9 49%, #EFEFEF 50%, #E7E7E7 100%);
    background-image: -ms-linear-gradient(bottom, #FEFEFE 0%, #F9F9F9 49%, #EFEFEF 50%, #E7E7E7 100%);
}

Hay que incluir algo de HTML:

<span class="cool_checkbox"><input type="checkbox" id="chk1"/><label for="chk1"><span>on</span><span><span>/</span></span><span>off</span></label></span>

Demostración

El botón luce tal que así (podéis hacer click en él, no os cortéis, en serio). Y degrada tal que así

Esperando que os haya sido útil, me despido.

PD: Es muy triste que webkit siga pintando nodos hijos mas allá del borde redondeado de su padre, a ver si lo arreglan algún día. Y otra cosa, uso transition y linear-gradient, lo que puede ser muy pesado de renderizar para dispositivos android e iOS menos potentes, la culpa de nuevo a webkit.

Python y la piscina de hilos

Siempre he tenido poblemas con la gestión de hilos en Python a la hora de desarrollar aplicaciones multiproceso. Algunos debidos a ciertas particularidades de las librerías gráficas, fácilmente solucionables, y otros dada la naturaleza dinámicamente impredecible de Python (he usado hilos en Java y Mono, y nunca me han parecido tan inestables en este aspecto).

De casualidad navegando por CRySoL, me he topado con una solución genial para Python, que hace transparente la gestión la ejecución multihilo, de una forma mucho más eficiente que andar construyendo y destruyendo diferentes subprocesos para cada hilo paralelo: tener un almacén o piscina de subprocesos que vayan ejecutando las instrucciones de una cola de ejecución.

Parece complicado, pero veréis que no lo es en absoluto.

EDITO 19.19.2010: Podéis descargar la librería desde: https://arco.esi.uclm.es/svn/public/prj/atheist/pyarco/Thread.py

Podéis descargar la librería desde https://arco.esi.uclm.es/svn/public/prj/pyarco/pyarco/threads.py.

Hacemos una prueba sencilla: pidiendo ejecutar dos veces una función con diferentes parámetros, usando dos hilos en el threadpool, y luego uno sólo.

# -*- mode: python; coding: utf-8 -*-

from time import sleep
from threads import ThreadPool

def messages_sleep(msg1,msg2,tm):
print msg1
sleep(tm)
print msg2

print "Ejecución con un ThreadPool de dos hilos"
p = ThreadPool(2)
p.add(messages_sleep,("iniciando hilo 1 (y esperando 6 segundos)","finalizando hilo 1",6))
p.add(messages_sleep,("iniciando hilo 2 (y esperando 8 segundos)","finalizando hilo 2",8))
p.join() # Esperamos a que terminen los hilos para continuar

print "Ejecución con un ThreadPool de un sólo hilo"
p = ThreadPool(1)
p.add(messages_sleep,("iniciando hilo 1 (y esperando 6 segundos)","finalizando hilo 1",6))
p.add(messages_sleep,("iniciando hilo 2 (y esperando 8 segundos)","finalizando hilo 2",8))
p.join() # Esperamos a que terminen los hilos para continuar

Y el resultado será:

Ejecución con un ThreadPool de dos hilos
iniciando hilo 1 (y esperando 6 segundos)
iniciando hilo 2 (y esperando 8 segundos)
finalizando hilo 1
finalizando hilo 2
Ejecución con un ThreadPool de un sólo hilo
iniciando hilo 1 (y esperando 6 segundos)
finalizando hilo 1
iniciando hilo 2 (y esperando 8 segundos)
finalizando hilo 2

Fuente: Patrón ThreadPool en Python en CRySoL.

Python: Sistema de sesiones (Google App Engine)

Primero toca explicar qué es un sistema de sesiones. Cuando se programa para web, se debe tener en cuenta que, puesto que el cliente y el servidor, son entidades bien diferenciadas, que sólo se ponen en contacto cuando el cliente (en este caso el navegador del usuario) hace una petición.

Esta cosa tan simple, base del protocolo TCP/IP, sobre el cual que se erige el HTTP, es obviado por mucha gente que programa usando las herramientas inadecuadas, pero esa es otra historia1.

La pregunta surge entonces ¿cómo identificamos eficazmente al usuario que navega por nuestras páginas? Puede que por el navegador, el sistema operativo, la IP, o todo a la vez. Pero no es suficiente, porque toda esa información no es de fiar pues puede falsearse, y en el caso de la IP, suele pasar que hayan miles de personas con la misma a la vez.

¿No hay un identificador único para el usuario? La respuesta es rotundamente no, por motivos de privacidad, a menos que uses Google Chrome (no es un bug, es una feature).

Pero no estéis tristes, todavía queda por probar algo: las cookies2. Sí, esas pequeñas ilegibles y endemoniadas cadenas de texto que web sí, y otra también, van llenando poco a poco, lenta pero incansablemente, tu disco duro, hasta explotar en un maremágnum de bytes a cascoporro.

Por todo ello, tan sólo deberíamos mantener una cookie con un id único de sesión, para saber quien nos está pidiendo cada cosa. Dicho id debería ser aleatorio y volátil, y debería ser el servidor, y no la cookie, quien decide cuando caduca para evitar suplantaciones de sesión. La información relativa a la sesión, al usuario, y demás se guardaría en el servidor, lejos de todo peligro.

Sin embargo, puede ser que queramos guardar algún tipo de información en las cookies del cliente, ya sea por comodidad o para validar la sesión con información extra. Dicha información, obviamente no debería ser "muy peligrosa", porque aunque la cifremos (y guardemos la clave del cifrado en el servidor junto con la id de sesión) existe la amenaza potencial de que alguien rompa el cifrado. Se podría decir que aplicamos el cifrado solo para evitar los script kiddies.

Para guardar y obtener datos de esta segunda cookie lo oṕtimo es usar un serializador seguro (recordemos que las cookies pueden ser manipuladas), como el presentado días atrás, y cifrándolo con una clave generada aleatoriamente y se guardaría en el servidor.

Teniendo todo esto en cuenta, he desarrollado una librería para Google App Engine, aunque podría ser adaptada fácilmente para otros usos simplemente cambiando la forma en la que las cabeceras son enviadas al cliente, y cómo son guardados los valores en el servidor (usa memcache). Podéis descargarla (46.2 KiB) desde las descargas del blog en Google Code.

Usarlo, con Google App Engine, resulta francamente fácil.

from google.appengine.ext.webapp.util import run_wsgi_app

from google.appengine.ext import webapp
from appSession import Session

class RequestWeb(webapp.RequestHandler):
def get(self):
session = Session(self)
if session["visited"]:
text = "Times visited %d." % session["visited"]
session["visited"] += 1
else:
text = "First time on page."
session["visited"] = 1

session["_foo"] = "This string will be stored on server."
session["bar"] = "This string will be stored on a cookie."

session.write()
self.response.out.write(
"<html><head></head><body><p>%s</p></body></html>" % text
)

application = webapp.WSGIApplication([
('/',RequestWeb)
],
debug=True)

run_wsgi_app(application)

EDITO 2011.08.30: Ejemplo corregido para evitar la llamada del /favicon.ico

En este ejemplo, el número de veces que se ha visitado la página, en lo que dura la sesión, es guardado en una cookie, junto con otra cadena de texto. Y otra cadena de texto es guardada en el servidor.

Espero que os sea útil.

  1. Si empiezo con el tema, no pararé hasta que se me agoten las pilas.
  2. No es realmente la última opción: la especificación del HTML5 añade al navegador la posibilidad de mantener una especie de base de datos en el navegador, pero su aceptación está tardando eones.

Programar Javascript eficientemente

Código sin errores

Puede parecer una obviedad, pero muchos programadores noveles cometen grandes errores de sintaxis en su código sin darse cuenta y, puesto que los intérpretes de javascript tienen cierta tolerancia a dichos errores, no se dan cuenta.

Dichos errores, aunque no frenen la ejecución en según que intérpretes, drenan en gran medida el rendimiento, y pueden convertir el algoritmo más eficiente del mundo en el código más lento jamás ejecutado.

// Errores comunes
var a = 5 // Omitir ;
b = 6; // No declarar variables apropiadamente
function c(){
return this; // this fuera de objetos (devolverá window)
}

Y por último un caso un poco complicado de entender.

var d = [ 1, 2, 3, 4, 5, 6 ], e = 0;
for( var f in d ){ e += f; }

La sintaxis for( x in y ) ejecuta el código dentro del bucle asignando a x todos y cada uno de los atributos de y. Es decir, en el caso anterior, no sólo recorrería todos los elementos del Array, sino sus métodos tales como splice, slice... o sus atributos como length. O al menos es así en intérpretes que respetan el estándar, no obstante la mayoría tolera este error y oculta deliberadamente las propiedades extras.

Para comprobar los errores del código recomiendo usar jslint.com.

Estructuras en tiempo de compilación

Muchos intérpretes de javascript precompilan el código para mejorar su eficiencia. No obstante, y dada la enorme flexibilidad de javascript, podemos declarar clases mediante prototipado en tiempo de ejecución, en cuyo caso la mejora del rendimiento será mucho menor.

// Declarada en tiempo de compilación
var objeto = {
foo : function( o ){ return o+1; },
bar : function( o ){ return 0; }
};

// Declarada en tiempo de ejecución
var objeto = ( function(){
var r = {};
r.foo = function( o ){ return o + 1; };
r.bar = function( o ){ return 0; };
return r;
}() );
Evitar ejecuciones repetitivas en los bucles

Esto es algo muy común a todos los lenguajes de programación, el evitar llamadas extras innecesarias. Recordemos que el código de dentro de un bucle se ejecutará en todas las iteraciones, de modo que si podemos reducir llamadas extras desde su interior aumentaremos el rendimiento notablemente.

function foo(){return 5;}
// Estos bucles ejecutarán una función en cada iteración.
var i = 0;
while( i < foo()){
i++;
}
for( var j = 0; j < foo(); j++ ){
i++;
}

// Cuando en realidad podrían reducir su ejecución a una única vez
var vfoo = foo(), i = 0;
while( i < vfoo ){
i++;
}
for( var f = 0; j < vfoo; j++ ){
i++;
}

Esta norma incluye una de las penalizaciones de rendimiento más comunes.

var a=[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], b=0;

// Este bucle obtiene, en cada iteración, al atributo length del Array
for( var i = 0; i < a.length; i++ ){
b += a[ i ];
}

// Cuando debería acceder a él una única vez
for( var i = 0, l = a.length; i < l; i++ ){
b += a[ i ];
}

Tened en cuenta que en este último caso, al estar tanto el Array como el bucle en el mismo ámbito de variables, la mejora no es perceptible, pero cuando el Array se encuentra en un ámbito más global, o en un objeto, la mejora es sustancial.

Evita los dinosaurios

Hay miles de frameworks pululando por la red, muchos de ellos demasiado grandes y pesados. Si bien es cierto que es un absurdo el reinventar la rueda con cada nuevo proyecto, también lo es el cargar un framework enorme para sólo utilizar determinada funcionalidad.

Siempre es preferible reutilizar porciones de código conforme se vayan necesitando o, si finalmente optamos por un framework, cerciorarse de que es modular, y evitar ciertas características extremadamente ineficientes que muchos incluyen.

Espero no haberme olvidado de demasiadas cosas, aunque probablemente mantenga el texto actualizado.

Javascript: Localizar nodos del DOM

Javascript dispone de tres funciones para localizar nodos.

getElementById(id)
Recibe el id del elemento que se quiere encontrar, y lo retorna.
getElementsByName(name)
Recibe un nombre, y retorna una lista de nodos con ese nombre en su atributo name.
getElementsByTagName(tagName)
Recibe el nombre de nodo (p.ej: input, div, img...), y retorna una lista de nodos de ese tipo.

Éstas son las estándares, pero muchos frameworks poseen los suyos propios (algunos extremadamente ineficientes) además de muchos otros algoritmos que rondan por foros y blogs más visitados que éste.

Pero ¿qué ocurre si quiero filtrar sus resultados según el valor de algún otro de sus atributos? Sencillo: buscamos usando alguno de los estándares y filtramos.

Podéis bajar el mismo código (0.5KiB) o la versión minificada (0.3KiB) de las descargas del blog en Google Code.

function require(a,o){
/* Filter a list of DOM nodes by theirs attributes
* a: hash with attribute name as key, and attributes value (or cmp function) as value
* b: list of DOM nodes
*/
var tr=[];
for(var j in a){if(typeof(a[j])!="function"){
a[j]=(function(a){return function(v){return v==a;}}(a[j]));}}
for(var i=0,l=o.length,j,k;i<l;i++){
k=1;
for(var j in a){
k=(typeof(o[i][j])!="undefined")&&(a[j](o[i][j]));}
if(k){tr[tr.length]=o[i];}}
return tr;
}

Tengo la buena costumbre de comentar el código en inglés, pero por si acaso explico, recibe un objeto hash (o diccionario) con los atributos a buscar y sus valores (o funciones de comparación), y una lista de nodos del DOM.

Siempre habrá que pasarle una lista de nodos puesto que se trata simplemente de un filtro. Pero puesto que los atributos posibles dependen directamente del tipo de nodo, usarlo en conjunción con getElementsByTagName es bastante trivial.

Usarlo es muy sencillo y potente, como ejemplo vamos a buscar todos los botones de envío de formulario.

var submits=require(
{type:"submit"},
document.getElementsByTagName("input"));

O buscamos los cuadros de selección que estén activados.

var checkedboxes=require(
{checked:"checked"},
document.getElementsByTagName("checkbox"));

Y también permite especificar filtros personalizados.

var divs=require(
{className:function(v){
return v.search("test")>-1;}},
document.getElementsByTagName("div"));

Como veis, es un método muy sencillo a la par que útil.

Bespin 0.4.4

Bespin es un proyecto de la gente de Mozilla, más bien un experimento, consistente en implementar un editor de código, online, utilizando HTML5 (y su canvas) y JavaScript.

A día de hoy soporta gestión de proyectos, SVN, subidas a SFTP, coloreado de sintaxis, y diferentes comportamientos dependiendo del lenguaje en cuanto a identación.

No es fácil de usar (tiene mucha funcionalidad, pero para hacer cosas demasiado básicas hay que recurrir a su peculiar línea de comandos), pero va en bien camino, y ya han corregido ciertos errores con el soporte a teclados internacionales, de modo que ahondando un poco en su documentación, ya se puede empezar a trabajar con él.

A día de hoy dan 15 MiB de espacio para tu código, puede parecer poco, pero 15 megas de código es muchísimo, y todavía está en pañales.

Si os pica la curiosidad, podéis registraros, e inmediatamente empezar a usarlo, en su página oficial.

Serializador recursivo para Python

Un serializador es un algoritmo capaz de convertir objetos, en este caso de Python, en cadenas de bits.

Para cierto proyecto en curso, me ha hecho falta serializar objetos complejos para guardarlos en cookies del navegador, y en este caso Pickle no es una opción, ya que es inseguro y permitiría ejecutar código arbitrario en el servidor a cualquiera que se le ocurriera modificar dicha cookie.

Existe otro serializador, Cerealizer, que permite elegir qué clases son las que permitimos coger del string serializado, además de los tipos básicos del lenguaje, pero alguna razón no me llegó a funcionar. De modo que he tenido que perder una semana de mi vida para crear mi propio algoritmo, recursivo, para tal menester.

Es bastante seguro, ya que obliga registrar todas las clases que puedan ser serializadas, y dichas clases deben definir sus propios métodos, __getstate__ y __setstate__, opcionales en Pickle, que definen qué datos son realmente serializados y cómo se deben recoger dichos datos por la clase cuando se carguen.

La cadena generada contiene únicamente caracteres imprimibles (a menos que alguna cadena serializada contenga otra cosa), escapa las cadenas unicode, es compacta (más que las cadenas de Pickle en sus protocolos 0 y 1, pero menos que su protocolo 2), tienen mejor ratio de compresión y el algoritmo en general es un 35% mas rápido que el mismo Pickle (cPickle sería mucho mas rápido, pero no está escrito en Python, sino en C).

Podéis bajarlo (14.1KiB) de las descargas del blog en Google Code.

Usarlo es muy sencillo

from PRSerializer import register, loads, dumps

class Clase(object):
atributo1 = True
atributo2 = None

def __init__(self):
self.atributo2 = xrange(1000)
# El serializador reconoce xrange nativamente
# por lo que es preferible usarlo frente a range

def __getstate__(self):
# Requerido. Retorna lo que realmente será serializado
return (self.atributo1, self.atributo2)

def __setstate__(self,o):
# Requerido. Recoge y utiliza los datos serializados
self.atributo1 = o[0]
self.atributo2 = o[1]

# Registramos la clase para el serializador
register(Clase)

# Creamos una instancia de la clase y trabajamos con ella normalmente
instancia = Clase()
instancia.atributo1 = "Cadena de texto"

# Serializamos la instancia
a = dumps(instancia)
print "Cadena:",a

# Deserializamos la instancia
b = loads(a)
print "Instancia:",repr(b)

Os dejo los resultados de una prueba de rendimiento, incluida en el módulo, en un dual core de 2.80Ghz.

Performance test: serializing, unserializing, zlib compression and pickle
1 dict
2000 lists (1000 are empty)
100000 integers (1000 are dictionary keys)
100000 strings
51000 objects
100000 booleans

Serializing time: 5.641000s ( 5463337 bytes )
Unserializing time: 3.328000s ( 1641627.670456 B/s )
Zlib compress time: 0.140000s ( 26580 bytes )

Pickling time: 9.703000s ( 5268354 bytes )
Unpickling time: 4.312000s ( 1221788.950771 B/s )
Zlib compress time: 1.344000s ( 506233 bytes )

Métodos estáticos en Python

Los ejemplos de la siguiente guía puede ejecutarse en el intérprete interactivo de Python (que aparece cuando llamamos a su ejecutable sin parámetros desde consola) o en Python IDLE, su intérprete interactivo con coloreado de sintaxis y autocompletado implementado con Tkinter (Tk/Tcl para Python).

Pongamos como ejemplo la siguiente clase.

class Clase(object):
atributo = True
def __init__(self):
self.atributo = False

def getAtributo(self):
return self.atributo

Para poder trabajar con ella, deberemos instanciarla y, al hacerlo, se ejecutará el método __init__

# Instanciamos, se ejecutará __init__, y atributo será False
instancia = Clase()
# Y el ejecutamos el método, que nos devolverá False, y lo mostramos
print instancia.getAtributo()

Y así funcionan normalmente las clases en Python, recibiendo su instancia como primer parámetro.

Sin embargo, en otros lenguajes (e incluso muchos módulos de Python) no se necesitan instanciar todas las clases para poder acceder a sus métodos, que en este caso se denominan estáticos. Si intentamos acceder al método del ejemplo anterior sin instanciar la clase, tal cual, nos encontraremos con que requiere que le especifiquemos la instancia, manualmente, para su primer parámetro self.

# Esto da error
print Clase.getAtributo()

Python para definir que métodos de una clase son estáticos, dispone de dos decoradores (una característica de su sintaxis): @staticmethod y @classmethod, con una serie de diferencias entre ambos.

class OtraClase(object):
atributo = True
def __init__(self):
self.atributo = False

@staticmethod
def getAtributoStaticmethod(self):
return self.atributo

@classmethod
def getAtributoClassmethod(self):
return self.atributo

La diferencia, entre el método estático y el método de clase en Python, es que mientras el método estático no recibe por defecto ningún parámetro, el método de clase recibe como primer argumento el objeto classtype, esto es, la referencia a la declaración de la clase.

# El método estático, recibiendo la clase, retorna True
print OtraClase.getAtributoStaticmethod(OtraClase)
# Mientras que el método de clase ya recibe la clase, y retorna True
print OtraClase.getAtributoClassmethod()

Como podréis ver, en el ejemplo anterior se retorna True, ya que el método __init__, que contiene la asignación a False, sólo se ejecuta cuando se instancia la clase.

Y por probar, veremos que si no adjuntamos la instancia de la clase al método estático, veremos que le falta un parámetro.

# Error, falta un parámetro
print OtraClase.getAtributoStaticmethod()

Sin entrar a saco con el resto de decoradores de Python, estos dos de los que hemos hablado, son sumamente útiles, sobretodo para los que os dediquéis a modularizar vuestros programas o estéis acostumbrados a usar métodos estáticos tal cual aparecen en otros lenguajes de programación menos flexibles.

Python2.6: Generar strings aleatorios

El generar strings aleatorios es algo muy importante en implementaciones de todo tipo de sistemas seguros, y desde siempre me ha sorprendido que Python no disponga de un generador nativo de cadenas aleatorias cuando presumen de que viene con "pilas incluidas", refiriéndose a su extensa librería estándar.

Por otro lado, también es sorprendente lo poco que se ha tratado este tema, o al menos, lo difícil que es, relativamente, encontrar lugares en internet donde se trate este tema.

Dada la necesidad de encontrar el algoritmo óptimo, para implementar claves aleatorias en un servidor, mi primera implementación, basada en el código que encontré en un comentario del blog de Stas Ostapenko.

def simpleSample(chars, length):
return "".join( random.sample( chars*length, length ))

Pero me entró la duda de si existiría una forma más eficiente, de modo que hice la prueba con el siguiente código.

# Random string generation time
from time import time
import string
import random

def classic(chars, length):
# Classic
tr=[]
l = len(chars)-1
for i in xrange(length):
tr.append( chars[ random.randint(0,l) ] )
return "".join(tr)

def simpleSample(chars, length):
# derived from
# http://ostas.blogspot.com/2006/12/python-generate-random-strings.html#c6033016737183722956
return "".join( random.sample( chars*length, length ))

def choice(chars, length):
# http://ostas.blogspot.com/2006/12/python-generate-random-strings.html#c9062709590606913845
return "".join([random.choice(chars) for x in xrange(length)])

if __name__=="__main__":
chars = string.letters + string.digits
times = 100
print ("Random string generators comparison.\n" +
"Dictionary length: %s\n" +
"Times to test every algorithm: %s\n") % (len(chars), times)

for size in (8,16,32,64,128,256,512,1024):
print "String size: %s" % size
winnertime = None
winner = None
for i in (classic, simpleSample, choice):
tf = 0
tmin = None
tmax = None
for j in xrange(times):
t0 = time()
text = i(chars, size)
t1 = time()
t = t1-t0
tf += t/times
if not tmin or t < tmin:
tmin = t
if not tmax or t > tmax:
tmax = t
#print "Algorithm: %s\nTime: %s\nGenerated: %s\n" % ( str(i), t1-t0, text )
print " %s\n (%fs, min %fs, max %fs)" % ( repr(i), tf, tmin, tmax )
if not winnertime or tf < winnertime:
winnertime = tf
winner = i
print " Winner: %s (%f seconds)" % (winner, winnertime)

Los resultados serían, con mi AMD Sempron 1.6Ghz:

Random string generators comparison.
Dictionary length: 62
Times to test every algorithm: 100

String size: 8
<function classic at 0xb7683614> (0.000224s, min 0.000037s, max 0.018489s)
<function simpleSample at 0xb7683ae4> (0.000025s, min 0.000023s, max 0.000069s)
<function choice at 0xb7683b1c> (0.000149s, min 0.000018s, max 0.013031s)
Winner: <function simpleSample at 0xb7683ae4> (0.000025 seconds)
String size: 16
<function classic at 0xb7683614> (0.000187s, min 0.000070s, max 0.011462s)
<function simpleSample at 0xb7683ae4> (0.000035s, min 0.000033s, max 0.000070s)
<function choice at 0xb7683b1c> (0.000169s, min 0.000032s, max 0.013551s)
Winner: <function simpleSample at 0xb7683ae4> (0.000035 seconds)
String size: 32
<function classic at 0xb7683614> (0.000151s, min 0.000135s, max 0.001198s)
<function simpleSample at 0xb7683ae4> (0.000317s, min 0.000054s, max 0.026029s)
<function choice at 0xb7683b1c> (0.000064s, min 0.000062s, max 0.000098s)
Winner: <function choice at 0xb7683b1c> (0.000064 seconds)
String size: 64
<function classic at 0xb7683614> (0.000698s, min 0.000267s, max 0.017400s)
<function simpleSample at 0xb7683ae4> (0.000224s, min 0.000095s, max 0.008380s)
<function choice at 0xb7683b1c> (0.000268s, min 0.000116s, max 0.013866s)
Winner: <function simpleSample at 0xb7683ae4> (0.000224 seconds)
String size: 128
<function classic at 0xb7683614> (0.001032s, min 0.000529s, max 0.021555s)
<function simpleSample at 0xb7683ae4> (0.000198s, min 0.000182s, max 0.001264s)
<function choice at 0xb7683b1c> (0.000534s, min 0.000229s, max 0.013713s)
Winner: <function simpleSample at 0xb7683ae4> (0.000198 seconds)
String size: 256
<function classic at 0xb7683614> (0.001496s, min 0.001052s, max 0.014621s)
<function simpleSample at 0xb7683ae4> (0.000634s, min 0.000344s, max 0.017386s)
<function choice at 0xb7683b1c> (0.000928s, min 0.000451s, max 0.013740s)
Winner: <function simpleSample at 0xb7683ae4> (0.000634 seconds)
String size: 512
<function classic at 0xb7683614> (0.003289s, min 0.002106s, max 0.023516s)
<function simpleSample at 0xb7683ae4> (0.001210s, min 0.000693s, max 0.014664s)
<function choice at 0xb7683b1c> (0.001481s, min 0.000901s, max 0.011516s)
Winner: <function simpleSample at 0xb7683ae4> (0.001210 seconds)
String size: 1024
<function classic at 0xb7683614> (0.005419s, min 0.004200s, max 0.023496s)
<function simpleSample at 0xb7683ae4> (0.002135s, min 0.001389s, max 0.014853s)
<function choice at 0xb7683b1c> (0.002663s, min 0.001804s, max 0.013014s)
Winner: <function simpleSample at 0xb7683ae4> (0.002135 seconds)

Por otro lado, en una entrada de stakoverflow, mikelikespie aporta el código siguiente.

def rand1(leng):
nbits = leng * 6 + 1
bits = random.getrandbits(nbits)
uc = u"%0x" % bits
newlen = (len(uc) / 2) * 2 # we have to make the string an even length
ba = bytearray.fromhex(uc[:newlen])
return base64.urlsafe_b64encode(str(ba))[:leng]

Este último algoritmo desbanca sin dificultad a simpleSample, salvo con cadenas de 8 bytes (los cálculos iniciales no compensa en ese caso), en las mismas condiciones que los anteriores, pero que no permite controlar, exactamente, que diccionario de caracteres queremos para la cadena aleatoria generada, ya que el base64 de Python utiliza caracteres de la A a la Z (mayúsculas y minúsculas), dígitos del 0 al 9, / y + (o - y _ si especificamos que sea seguro para urls), y el carácter = como relleno.

No se con lo que me quedaré al final, pero supongo que la enorme diferencia de rendimiento del último podría compensar la falta de flexibilidad en determinados casos.

Control de versiones

Ejemplo de árbol de versiones

La gente que nos dedicamos al desarrollo de software requerimos constantemente el manipular varias versiones del mismo código, o colaborar activamente con otros desarrolladores en el desarrollo de las aplicaciones. En este último caso, puede darse comúnmente que varias personas escriban código incompatible entre sí, a causa de sobrescribir el trabajo de otros o trabajar sobre versiones antiguas de dicho código.

Para evitar esto, existen los llamados sistemas de control de versiones o de gestión de código fuente (SCM, de sus siglas en inglés), como SVN, GIT, CVS, Mercurial, etc.

Pero ¿Cómo funcionan? Resumiendo mucho, se podría decir que alguien crea un repositorio de código, y todos los programadores actualizan sus versiones desde allí, trabajan con el código, y lo vuelven a subir al repositorio.

Estos sistemas ya se encargarán de notificar posibles problemas de ediciones del mismo fichero (si no se han modificado las mismas líneas, el sistema detecta los cambios y los fusiona), intentos de subir versiones antiguas, o fusionar varias ramas de desarrollo paralelas.

A mí, personalmente, me encanta SVN, que es el sistema que ofrecen los servidores gratuitos (para código abierto) code.google.com, sourceforge.net, aunque también existen Git, con github.com, Bazaar con launchpad.com, sólo por citar unos ejemplos.

Para empezar a usarlos, deberemos crearnos una cuenta para dichos servicios, y descargar una aplicación cliente que se encargará de comunicar los ficheros que modifiquemos localmente con los del servidor. Mi preferida es, sin duda, RapidSVN. Configuraremos una copia local, en la que haremos Checkout para bajarnos los archivos, programar normalmente con nuestro editor de toda la vida, y cuando terminemos añadiremos los nuevos que hayamos creados y los subiremos, junto con los modificados, al servidor, usando la misma aplicación.

Aunque bueno, como muchas otras cosas, en la variedad está el gusto. De hecho, tanto NetBeans como Eclipse disponen de clientes SVN integrados, pero personalmente prefiero las aplicaciones sencillas que simplemente cumplen su cometido a los grandes del todo incluido.

Créditos: La imagen de las ramas ha sido robada vilmente de la Wikipedia.

Utilidades imprescindibles para desarrollo web

A la hora de meteros en el mundo del desarrollo web, muchos optan por grandes entornos de programación, otros nos contentamos con simples editores con resaltado de sintaxis, pero todos necesitamos de herramientas para comprobar errores en el código final, tanto en el CSS, XHTML e incluso Javascript que generen nuestros proyectos, y ahí entran en juego las siguientes utilidades.

Web developer toolbar (Firefox)
Web developer toolbar es una extensión que agrega una barra a Mozilla Firefox, con la que se puede validar el código, desactivar ciertas funcionalidades del navegador, facilitar el ver los elementos de la página... Muy útil para comprobar la calidad del resultado, la accesibilidad, y que el Javascript no es obstrusivo.
FireBug (Firefox)
La más famosa utilidad de este tipo. Permite analizar el código tanto HTML como CSS y editarlos en tiempo real, y hacer debug de Javascript usando puntos de interrupción, observando variables, etc. Además de poder ver y analizar, en tiempo real, las peticiones al servidor. Es un devorador recursos y hace algunas cosas de forma poco ortodoxa, pero es un imprescindible.
Developer tools (incluido en navegadores webkit)
Dependiendo del navegador puede funcionar bien o no, pero pasa a ser, junto con FireBug, una de las herramientas más potentes, con análisis y edición del marcado y sintaxis de HTML y CSS, además de un completo debugger para Javascript (aunque yo sigo prefiriendo el de FireBug). También dispone de análisis de peticiones a servidor, cookies, etc.

Además, no puedo dejar de recomendar las siguientes utilidades web para ayudaros a trabajar con javascript.

Javascript Shell y Javascript Development Environment
Un intérprete interactivo para javascript, cuyo bookmarklet lo abre utilizando la página actual como ámbito, y un editor de Javascript, algo limitado, pero que permite ejecutar directamente el código editado en el intérprete del navegador.
JSLint
Utilidad que valida la calidad del código javascript, comprobación de errores, e incluye un modo de calidad de la sintaxis.

Eventos Javascript multinavegador

Editado 15.03.2010: He eliminado el soporte específico a navegadores no estándares. No obstante, en dichos navegadores seguirá funcionando gracias al código general para navegadores antiguos. Además, se ha añadido una función para facilitar el añadir eventos que sólo se ejecuten una vez (ver más abajo). Las descargas están actualizadas.

Siempre que me dedico a diseñar interfaces asistidas con javascript, me encuentro con el mismo problema: ¿cómo manejar los eventos eficientemente y mantener la compatibilidad multinavegador?

La solución sencilla, y la más compatible1 (DOM 0), consiste en los atributos de evento, como onclick u onmouseover, pero tiene serias limitaciones: sólo admite una función (aunque podemos concatenar varias), y si optamos por concatenar funciones ni podríamos eliminar alguna, aparte de que la cancelación de eventos con return se vuelve una pesadilla.

Sin embargo, los navegadores estándares (motores mozilla, webkit, khtml...) disponen de elemento.addEventListener( nombre_evento, función, capturar ), que simplifica mucho el proceso de asignar varias funciones y elegir si capturan el evento por completo con un booleano, junto con elemento.removeEventListener( nombre_evento, función, capturaba ) para borrar funciones asignadas al elemento.

Por otro lado, navegadores no estándares2 (motor trident) recurren a elemento.attachEvent( onevento, función ), que funciona como addEventListener pero con nombres de eventos de HTML, y no permite establecer si se captura el evento o no, y para eliminar la función del evento tenemos elemento.detachEvent( onevento, función).

Crear una función para simplificar el proceso de asignación y desasignación de funciones a eventos, que sirva para los tres casos anteriores, nos lleva a renunciar a la posibilidad de definir si el evento se captura al momento de asignarlo, pero aún queda la posibilidad de capturar el evento dentro de la función asignada.

Pero de nuevo, existe una forma estándar y varias no estándar de capturar el evento, además de que los navegadores más antiguos sólo lo detendrán si la función asignada retorna false. En este último caso tendremos la fortuna3 de que dichos navegadores tampoco aceptan las formas modernas de asignación.

Teniendo todo esto en cuenta, he malgastado unas preciosas horas de mi vida en diseñar tres útiles funciones auxiliares (que empezaré a usar en todos lados). Podéis descargar este mismo código (3.2 KiB) a o la versión minificada (1 KiB).

/* Mi función auxiliar preferida, recibe un nombre de propiedad
* y un objeto, y retorna true o false dependiendo de si existe en
* el objeto o no. Si no se recibe un objeto se asume window */
function def(p,o){return (typeof((o||window)[p])!="undefined");}
function attEvent(o,e,f){
/* Función de asignación de evento, recibe objeto, nombre de evento
* (p.ej. click, no onclick) y función de callback */
var w3="addEventListener",a="attachedEvents",cp="cancel",nf,of,nr;
if(def(w3,o)){o[w3](e,f,false);} // Estándar
else{ // Funcionalidad para navegadores antiguos
if(!def(a,o)){o[a]={};} // Objeto auxiliar
if(!def(e,o[a])){o[a][e]={};} // Objeto evento
if(!def(f,o[a][e])){ // No agregar dos veces la misma función
nf=function(){
/* Detección de funciones bloqueadas por detEvent:
* si ha sido bloqueada, no se ejecuta la función */
if(o[a][e][f]){f.apply(this,arguments);}
};
nr=function(v){
// Retorna false si se ha ejecutado stopEvent
return !(def(cp,(v||window.event))&&(v[cp]==true));
};
if(o["on"+e]){
/* Si hay funciones previamente asignadas, asignamos
* una función que las ejecutará junto con la nueva */
of=o["on"+e]; // Funciones previas
o["on"+e]=function(){
// Ejecutamos:
of.apply(this,arguments); // 1. funciones previas
nf.apply(this,arguments); // 2. nueva función
// Retornamos false si se ha ejecutado stopEvent
return nr.apply(this,arguments);
};
}
else{o["on"+e]=nf;} // Agregar la primera función
}
/* Asignamos 1 en la función para el evento, si el valor
* pasa a ser 0 (por detEvent), la función no se ejecutará */
o[a][e][f]=1;
}
}
function attEventOnce(o,e,f){
attEvent(o,e,function(){
f.apply(this,arguments);
detEvent(this,e,arguments.callee);
});
}
function detEvent(o,e,f){
/* Función de desasignación de evento, recibe objeto, nombre de
* evento, y función a eliminar (o bloquear en el último caso) */
var w3="removeEventListener",a="attachedEvents";
if(def(w3,o)){o[w3](e,f,false);} // Estándar
else if(def(a,o)&&def(e,o[a])&&def(f,o[a][e])){
// Modo para navegadores antiguos (no borra, sólo desactiva)
o[a][e][f]=0;
}
}
function stopEvent(e){
/* Función de detención de eventos, recibe la instancia del evento
* por parámetro. */
var w3="stopPropagation",mz="preventDefault",e=(e||window.event);
/* Para navegadores que cumplen los estándares */
if(def(w3,e)){e[w3]();}
/* Evitamos la acción por defecto (mozilla) */
if(def(mz,e)){e[mz]();}
/* Para navegadores antiguos y no estándares: establecemos cancel y
* cancelBubble a true, returnValue a false, y retornamos false
* por si queremos usarlo en eventos asignados directamente. */
return e.returnValue=!(e.cancel=e.cancelBubble=true);
}

Usando estos métodos, agregar varias funciones al mismo evento sería bastante simple.

attEvent(window,"load",function(){alert("Primera vez.");});
attEvent(window,"load",function(){alert("Segunda vez.");});

O incluso podríamos asignar una función que se desasignara a sí misma y detuviera el evento cuando se ejecutase.

attEvent(document,"click",function(e){
alert("¡Cliclí por primera vez, capturado!");
// Desasignamos la propia función (callee) del elemento (this)
detEvent(this,"click", arguments.callee);
// Detenemos el evento
stopEvent(e);
});
Editado 15.03.2010: Ahora existe la posibilidad, a petición popular, de añadir eventos de un sólo uso de forma más sencilla: con attEventOnce.

attEventOnce(document,"click",function(){
alert("¡Cliclí una única vez (sin capturar)!");
});

Sí, ya sé que frameworks mastodónticos, como jQuery o Mootools, ya han ideado formas de hacer todo esto, listas para usar, pero ¿qué tiene eso de divertido?


  1. Hay que decirlo: Chrome "se olvida" de los onsubmit de los formularios.
  2. Es tan obvio cuál que, tal cual, obviaré su nombre.
  3. Muy relativo todo, eso sí.

Créditos:

Jugando con __getattribute__ y Django

Siguiento mi pequeño experimento de pseudoclases-dicionario para facilitarme la vida con Django, se me ocurrió una forma de poder establecer y recoger valores de variable directamente desde las plantillas de Django.

El motor de plantillas de Django, por una acertada decisión de diseño, hizo a bien de no incluir la posibilidad de establecer variables o ejecutar métodos en su motor de plantillas, al igual que ocurre por ejemplo con Smarty (motor de plantillas para PHP).

Sin embargo, dada la gran potencia de Python, existe una forma de hacerlo, muy limitada pero efectiva, sobrecargando el método __getattribute__ .

# Importamos compile del modulo de expresiones regulares
from re import compile as rec

# Declaramos la expresion regular que identifica los nombres de propiedad
gs = rec("^(get_(.*))|(set_(.*)_(.*))|(setn_(.*)_(\d+))|(setb_(.*)_(true|True|false|False|0|1|-1))$")
class Strangelet(dict):
''' Clase que permite la asignacion con nombres de propiedad '''
def __getattribute__(self,x):
# Sobrecargamos el metodo que pide los atributos
a = gs.match(x)
if a: # Si cumple la expresion regular
a=a.groups()
if a[0]:
# Primer caso, devolvemos valor
return dict.__getitem__(self, a[1])
elif a[2]:
# Segundo caso, establecemos valor como cadena
dict.__setitem__(self, a[3], a[4])
elif a[5]:
# Tercer caso, establecemos valor como entero
dict.__setitem__(self, a[6], int(a[7]))
elif a[8]:
# Cuarto caso, establecemos valor como booleano
# muy util para trabajar con los IF de Django
dict.__setitem__(self, a[9], a[10] in ("true","True","1","-1"))
# Retornamos cadena vacia, para no mostrar nada
return ""
# se podria retornar None, pero Django los muestra.
else:
# Si no se cumple la expresion regular, usamos el metodo heredado
return dict.__getattribute__(self,x)

if __name__ == "__main__":
# Test de unidad
a = Strangelet()
a.setb_var_0
print a.get_var
a.setb_var_True
print a.get_var
a.setn_var_1
print a.get_var
a.set_text_EstoEsUnTexto
print a.get_text

Y podríamos usarlo para asignar variables en Django:

{{ instancia_strangelet.setn_variable_1 }}
{% ifequal instancia_strangelet.get_variable 1 %}¡Es igual a uno!{% endifequal %}
{{ instancia_strangelet.setb_variable_false }}
{% if instancia_strangelet.get_variable %}No pasará por aquí.{% else %}¡Y ahora es False!{% endif %}

Que en efecto mostraría:

¡Es igual a uno! ¡Y ahora es False!

Todo esto es posible porque Django respeta los __getattribute__ de las clases, al ser puramente Python. ¿Divertido, eh?

Python no tiene getters ni setters, pero ni falta que le hace.

Aviso que este experimento es sólo eso, un experimento, no apto para entornos de producción o proyectos serios, y es una aberración contra la naturaleza tanto de Python como del motor de plantillas de Django. Pero es curioso, eso sí.

Pseudoclases en tiempo de ejecución con Python

Trasteando con mi código, en cierto momento (culpa de las plantillas de Django) me dí cuenta de que perdía una gran cantidad de tiempo declarando clases "auxiliares", que solo iba a usar una vez. Tiempo después, y quizás influenciado por la filosofía del Javascript, encontré una rápida solución: implementar clases a partir de diccionarios.

class DictClass(dict):
def __getattribute__(self,i):
if i[0:2] != "__" and self.__contains__(i):
return self.__getitem__(i)
return dict.__getattribute__(self,i)

Se usa muy fácilmente, en cualquier momento que quieras instanciar una clase, tan sólo tenemos que tratarlo como un diccionario. Pero sus atributos y método serán accesibles como si de una clase se tratase.

aux = DictClass({
"texto": "hola",
"method": lambda x:x+1,
})
aux["texto"] = "hasta la vista"
print(aux.texto)

Además, me he creado otra clase dinámica pero a partir de una lista, cuyos elementos son accesibles como si fuesen atributos (su utilidad se comenta al final).

import re
reitem = re_compile("^item_\d+$")
class ListClass(list):
def __getattribute__(self,i):
if reitem.match(i):
return self.__getitem__( int(i[5:]) )
return list.__getattribute__(self,i)

Y se usaría como una lista, cuyos valores son a su vez atributos.

lista = ListClass([1,2,3])
lista.append(4)
print(lista.item_4)

Tened en cuenta que esta técnica es menos eficiente que declarar las clases "tradicionales" desde el punto de vista del rendimiento, pero permite programar mucho mas rápido, y saltarse ciertas limitaciones del motor de plantillas de Django 1.0, que no permite acceder a las propiedades de los diccionarios y acceder a determinados valores de una lista, si no es a través de bucles.

<p>Valor de propiedad de DictClass: {{ clase.atributo }}</p>
<p>Valor de la primera posición de una lista: {{ lista.item_0 }}</p>

Espero que os haya sido de utilidad, no he descubierto la pólvora, y seguro que hay formas mejores o mas rápidas de ahorrarse tiempo de desarrollo (¿He oido Ruby por algún lado?), pero esto me ha sido muy útil ahorrándome un tercio de dicho tiempo, y por eso lo comparto.

Parser xHTML para Javascript

Basándome en el intérprete de HTML para javascript de John Resig, y aplicándole un par de cambios para hacerlo más adecuado para tratar con XHTML, he acabado haciendo una clase muy útil que a mas de uno os gustará.

Está bajo la Mozilla Public License por exigencia del código empleado.

Métodos:

  • HTMLParser: recibe una cadena con código HTML, y un diccionario (hashtable) con callbacks, al estilo SAX de Java, "start" para el inicio de una etiqueta, "end" para el final de una etiqueta, "chars" para texto y "comment" para comentarios html.
  • escapeXHTML y unescapeXHTML: reciben una cadena de HTML o XHTML y convierte los caracteres no permitidos por el estándar XHTML por sus códigos, y al revés, respectivamente.
  • HTMLtoXML: recibe una cadena de HTML o XHTML, y retorna otra de XHTML, intentando corregir los errores del código como etiquetas sin cerrar.
  • HTMLtoNODE: Recibe una cadena de HTML o XHTML y un nodo, y construye el árbol DOM a partir de la cadena en el nodo dado.
  • HTMLtoDOM: recibe una cadena de HTML o XHTML, y opcionalmente un nodo de tipo documento, y retorna el árbol DOM construído a partir de la cadena.

¿Qué utilidad tiene?

La técnica más usada, a día de hoy, para escribir código HTML en el documento sigue siendo innerHTML. innerHTML nunca fué estándar, es sucio, y tira por suelo la lógica de todo el DOM (Modelo del Objeto Documento, o Document Object Model), que es la forma en la que Javascript de comunica con el intérprete HTML del navegador.

Uno de dichos efectos secundarios es que, mientras construyendo nodos del DOM por javascript cualquier error en la manipulación árbol resulta en un fallo del intérprete, y no llega a reflejarse en el documento, si usamos innerHTML cualquier código malformado puede, y de hecho muy comúnmente sucede, destrozar la página web.

Utilizar un traductor de cadenas de HTML a DOM mediante javascript es una buena solución, y aunque sea menos eficiente que innerHTML, que recordemos está integrado en el navegador, es mucho más fiable y estándar.

Yo lo uso para escribir en el documento todo el HTML recibido con técnicas AJAX, y me ha ahorrado muchos dolores de cabeza con el código malformado.

El código

Podéis descargar este mismo código (6.59 KiB) a o la versión minificada (4.93 KiB).

/*
* Raw xHTML parser
* A pure JavaScript SAX-like HTML/XHTML parser.
*
* Felipe A. Hernández - http://spayder26.blogspot.com
* John Resig - http://ejohn.org/blog/pure-javascript-html-parser/
* Erik Arvidsson - http://erik.eae.net/archives/2004/11/20/12.18.31/
* Licensed under Mozilla Public License
*/
window.rhp=(function(){
/* Raw xHTML parser */
var $def=function(a,b){return typeof((b||window)[a])!="undefined";},
makeMap=function(a){
var r={},b=a.split(",");
for(var i=0,j=b.length;i<j;i++){r[b[i]]=1;}
return r;
},
startTag=/^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
endTag=/^<\/(\w+)[^>]*>/,
attr=/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
me={},
esc=[["&","&amp;"],[">","&gt;"],["<","&lt;"],["\"","&quot;"],["'","&apos;"]],
esr=[],unesr=[],esl,
getDoc=function(node){
if(node.nodeType==9){return node;}
return (node.ownerDocument||(node.getOwnerDocument&&node.getOwnerDocument())||document);};
esl=esc.length;
for(var i=0;i<esl;i++){esr[i]=new RegExp(esc[i][0],"g");unesr[i]=new RegExp(esc[i][1],"g");}
var empty=makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"),
block=makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"),
inline=makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"),
closeSelf=makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"),
fillAttrs=makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),
special=makeMap("script,style");
me.HTMLParser=function(html,handler){
// SAX-like HTML parser
var stack=[];
var parseEndTag=function(tag,tagName){
var pos,i;
if(!tagName){pos=0;}
else{for(pos=stack.length-1;pos>-1;pos--){
if(stack[pos]==tagName){break;}}}
if(pos>=0){
for(i=stack.length-1;i>=pos;i--){
if(handler.end){handler.end(stack[i]);}}
stack.length=pos;}};
var parseStartTag=function(tag,tagName,rest,unary){
if(block[tagName]){
while(stack.last()&&$def(stack.last(),inline)){
parseEndTag("",stack.last());}}
if(closeSelf[tagName]&&stack.last()==tagName){
parseEndTag("",tagName);}
unary=(empty[tagName]||!!unary);
if(!unary){stack.push(tagName);}
if(handler.start){
var attrs=[];
rest.replace(attr,
function(match,name){
var value=arguments[2]?arguments[2]:(arguments[3]?arguments[3]:(arguments[4]?arguments[4]:(fillAttrs[name]?name:"")));
attrs.push({
name:name,
value:value,
escaped:value.replace(/(^|[^\\])"/g,'$1\\\"')});});
if(handler.start){handler.start(tagName,attrs,unary);}}},
last=html,index,chars,match;
stack.last=function(){return (this.length)?this[this.length-1]:null;};
while(html){
chars=true;
if(!stack.last()||!special[stack.last()]){
if(html.indexOf("<!--")===0){
index=html.indexOf("-->");
if(index>=0){
if(handler.comment){handler.comment(html.substring(4,index));}
html=html.substring(index+3);chars=false;}}
else if(html.indexOf("</")===0){
match=html.match(endTag);
if(match){html=html.substring(match[0].length);
match[0].replace(endTag,parseEndTag);
chars=false;}}
else if(html.indexOf("<")===0){
match=html.match(startTag);
if(match){
html=html.substring(match[0].length);
match[0].replace(startTag,parseStartTag);
chars=false;}}
if(chars){
index=html.indexOf("<");
var text=(index<0)?html:html.substring(0,index);
html=(index<0)?"":html.substring(index);
if(handler.chars){handler.chars(text);}}}
else{
html=html.replace(new RegExp("(.*)<\/"+stack.last()+"[^>]*>"),
(function(){
return function(all,text){
text=text.replace(/<!--(.*?)-->/g,"$1").replace(/<!\[CDATA\[(.*?)\]\]>/g,"$1");
if(handler.chars){handler.chars(text);}
return "";};})());
parseEndTag("",stack.last());}
if(html==last){throw "Parse Error: "+html;}
last=html;}
parseEndTag();};
me.escapeXHTML=function(html){
// Escape XHTML characters
for(var i=0;i<esl;i++){
html=html.replace(esr[i],esc[i][1]);}
return html;};
me.unescapeXHTML=function(html){
// Unescape XHTML characters
for(var i=esl-1;i>-1;i--){
html=html.replace(unesr[i],esc[i][0]);}
return html;};
me.HTMLtoXML=function(html){
// Parses HTML string to XML string (XHTML)
var results="";
me.HTMLParser(html,{
start:function(tag,attrs,unary){
results+="<"+tag.toLowerCase();
for(var i=0,il=attrs.length;i<il;i++){
results+=" "+attrs[i].name.toLowerCase()+'="'+attrs[i].escaped+'"';}
results+=(unary?"/":"")+">";},
end:function(tag){
results+="</"+tag.toLowerCase()+">";},
chars:function(text){results+=text;},
comment:function(text){results+="<!--"+text+"-->";}});
return results;};
me.HTMLtoNODE=function(html,node){
// Writes an HTML string to DOM's node
var elems=[node],doc=getDoc(node),cur=node;
me.HTMLParser(html,{
start:function(tagName,attrs,unary){
var elem=doc.createElement(tagName);
for(var i=0,il=attrs.length;i<il;i++){
elem.setAttribute(attrs[i].name,me.unescapeXHTML(attrs[i].value));}
if(cur&&cur.appendChild){
cur.appendChild(elem);}
if(!unary){elems.push(elem);cur=elem;}},
end:function(tag){
elems.length-=1;
cur=elems[elems.length-1];},
chars:function(text){
cur.appendChild(doc.createTextNode(me.unescapeXHTML(text)));},
comment:function(text){
if(doc.createComment){cur.appendChild(doc.createComment(text));}}});};
me.HTMLtoDOM=function(html,doc){
// HTML string to DOM's document node
if(!doc){doc=document;}
else{doc=getDoc(doc);}
var n=doc.createElement("div");
me.HTMLtoNODE(html,n);
return n.childNodes;};
return me;}());

Créditos

Python: ágil y elegante

Nota: aunque Python ya va por la versión 3.1, dado que muchas librerías no están todavía adaptadas, los ejemplos de esta entrada están enfocadas a rama 2.6.

Python (pronúnciese paiton o paizon), es un lenguaje de programación interpretado (o de scripting) muy rápido de trabajar y con una muy suave curva de aprendizaje.

El típico hola mundo (mostar un texto en la salida estándar, comúnmente la consola):

print "Hola mundo"

Con este lenguaje, se pueden dar comúnmente esos casos de programas muy completos en menos de 100 líneas de código, programados en un tiempo ínfimo (se suele incluir un intérprete interactivo para hacer pruebas), siendo el código muy legible, y por eso es uno de los lenguajes preferidos para programar en linux (donde Python viene comúnmente incluido en muchas distribuciones).

Ejemplo de sintaxis:

# Esto es un comentario
'''
Esto es un comentario multilínea
'''

class Padre:
''' Clase Padre, con atributos y métodos '''
pelo = "negro" # Atributo de Padre, de tipo cadena de texto o str
ojos = "marrones"
dedos = 21 # Atributo de padre, de tipo número entero o int

def colorPelo(self):
''' Método, retorna el atributo pelo de la clase.
En Python, el primer argumento de un método es la
instancia de la propia clase, y sólo es necesario si
no se llama a través de una instancia.
'''
return self.pelo # Retorna un valor

class Hijo(Padre):
''' Clase Hijo, hereda los atributos y métodos de padre. '''
ojos = "verdes" # Sobreescribimos un atributo heredado con un nuevo valor

instancia = Hijo() # Instancia de una clase
print instancia.colorPelo() # Mostramos lo que nos retorna el método heredado
print Hijo.colorPelo(instancia) # Igual que el anterior

def pruebaFor(numero):
''' Función, recibe un valor por parámetro '''
for i in range(numero+1):
''' La función range devuelve una lista de números, que parten desde
cero o un valor dado, hasta el anterior al pasado por parámetro.
p.ej. range(0,5) es igual a range(5) y es igual a [0,1,2,3,4]
El for, en python, recorre todos los valores, asignándolos a
la variable, i en este caso (como hace foreach en otros lenguajes).
'''
print i # Muestro el valor de i

lista = [1, 2, "a", instancia] # Lista de valores (array en otros lenguajes)
pruebaFor(5) # Ejecuto la función, dándole el valor 5 por parámetro

Incluye una gran serie de librerías por defecto, incluyendo Tkinter, una utilería de ventanas basada en el vetusto Tk/Tcl.

Por suerte, existen PyGTK y Glade. El primero son las librerías para nuestro lenguaje, con el que poder operar con las librerías GTK, las utilizadas por Gnome, Xfce, y demás, siendo muy ligeras y bien diseñadas.

Glade, por su parte, permite desarrollar ventanas de forma sencilla, guardadas en formato XML (lenguaje de etiquetas), y utilizables por las librerías GTK para cualquier lenguaje.

Usando Python y Glade, crear un programa que tan sólo muestre una ventana es muy sencillo:

# Importamos las librerías
import gtk
import gtk.glade

# Cargamos el fichero de glade
xml = gtk.glade.XML("interfaz.glade")

# Elegimos la ventana
ventana = xml.get_widget("window1") # Si la ventana se llama window1

# Creamos la función de cierre
def cerrar():
print "Ventana cerrada, salimos del bucle de GTK"
# Cerramos el bucle de GTK para devolver el control a Python
gtk.main_quit()

# Conectamos el evento "destroy" de la ventana con nuestra función cerrar
ventana.connect("destroy", cerrar)

# Mostramos la ventana
ventana.show()

# Iniciamos el bucle principal de GTK
gtk.main()

Ya tenéis lo básico para programar con Python.

Enlaces útiles:

Créditos: