Mostrando las entradas con la etiqueta python. Mostrar todas las entradas
Mostrando las entradas con la etiqueta python. Mostrar todas las entradas

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.

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.

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.

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: