Sigues dinàmic
Escrit per Aaloy a 29 de June , 2008 a les 3:53 p.m.
L'altra dia vaig fer un petit prototip per veure amb quines dificultats em trovaba a l'hora de connectar amb Python contra l'LDAP de l'empresa (un Notes) i contra l'Active Directory. Aquesta funcionalitat ja la tenia desenvolupada en Java, però com que l'aplicació que estava plantejant es faria amb Python, vaig començar a mirar els tempes més problemàtics: l'autentificació i com imprimir els pdfs.
La connexió amb l'LDAP i la funcionalitat que volia, per tal d'obtenir tota la informació del l'usuari que es connectava no va ser gens problemàtica, en total 76 línies de codi davant les 350 llargues de Java, o el que és el mateix en Python vaig haver d'escriure un 80% menys de línies per a tenir la mateixa funcionalitat. D'això no poden concloure que sempre els programes en Python seran un 80% més curts, però és una evidència més del que parlam quan deim que s'escriu molt menys codi i que és més ràpid fer-ho.
El perquè en aquest cas concret, dóna peu a aquest apunt, presentarem la manera de tractar amb Python dues situacions bastant comuns quan ens hem de connectar a altres sistemes o fer servir llibreries de tercers: la transformació de diccionaris en objectes i el com tractar el cas en que tenim múltiples paràmetres opcionals.
Diccionaris a objectes
La situació és la següent: tenim un diccionari que volem passar com a paràmetre i fer servir les seves claus com si fossis propietats de la classe, de tal manera que si una clau no existeix ens dóni el valor per defecte.
Aquesta situació me la vaig trobar connectant a l'LDAP. La llibreria de Python en interroga l'LDAP torna un diccionari i volia que aquest diccionari formàs part de la classe Usuari que havia de contenir tota la informació de l'usuari que s'estava connectant a l'aplicació.
Suposem doncs que el diccionari que ens retornen és:
dades = {'nom': 'Antoni Aloy',
'telefon': '555 55 55 55',
'localitat': 'Binissalem',
'email': 'aaloy@example.com',
'blog': 'http://trespams.com'
}
El que volem és posar tota aquesta informació dins una objecte de tipus Usuari de tal manera que sigui fàcilment manipulable i entendible. És a dir, que puguem fer usuari.telefon,
class Usuari:
"Exemple de com transformar les claus d'un diccionari en propietats"
def init(self,ldap_prop = dict()):
self.__propietats = ldap_prop
def __getattr__(self, name):
"Obtenim l'el valor de la propietat del diccionari"
try:
return self.__propietats[name]
except:
return "No assignada"
def __str__(self):
"Representació textual de l'usuari"
return "%s - %s" % (self.nom, self.email)
def propietats(self):
"Retorna la llista de propietats"
return self.__propietats.keys()
Ho podríem fer servir amb el codi següent:
if __name__ == "__main__":
u = Usuari( ldap_prop = dades)
print "Nom %s" % u.nom
print "Telefon %s " % u.telefon
print "Provincia %s " % u.provincia
print u
El nostre cas era prou senzill, si volem quelcom més complexe podem anar a a una recepte de Michael Foord, on podem veure com s'extén l'objecte diccionari per a fer el mateix que hem fet en el nostre exemple i a més permetre la utilització de paràmetres normals.
Parametrització
Tenim una classe amb una gran quantitat de paràmetres que es poden modificar. Per defecte tots aquests paràmetres tenen un valor per defecte. Volem que l'usuari pugui actualitzar els valors i obtenir-los. A més hi pot haver paràmetres que són sols de lectura.
Aquesta situació me la vaig trobar instanciant classes de Reportlab. A l'hora d'utilitzar la llibreria ens trobam en aquesta situació: tenim una gran quantitat d'atributs que podem assignar, però la major part del temps els valors per defecte ja ens estan bé. Vegem com ha resolt la situació la gent de Reportlab:
class BaseDocTemplate:
"""...."""
_initArgs = { 'pagesize':defaultPageSize,
'pageTemplates':[],
'showBoundary':0,
'leftMargin':inch,
'rightMargin':inch,
'topMargin':inch,
'bottomMargin':inch,
'allowSplitting':1,
'title':None,
'author':None,
'subject':None,
'keywords':[],
'invariant':None,
'pageCompression':None,
'_pageBreakQuick':1,
'rotation':0,
'_debug':0}
_invalidInitArgs = ()
def __init__(self, filename, **kw):
"""create a document template bound to a filename (see class
documentation for keyword arguments)"""
self.filename = filename
for k in self._initArgs.keys():
if not kw.has_key(k):
v = self._initArgs[k]
else:
if k in self._invalidInitArgs:
raise ValueError, "Invalid argument %s" % k
v = kw[k]
setattr(self,k,v)
p = self.pageTemplates
self.pageTemplates = []
self.addPageTemplates(p)
...
A l'hora de crear la classe BaseDocTemplate els atributs es defineixen dins un diccionari _initArgs, a la inicialització de la classe l'únic paràmetre obligatori és filename, però perfectament podem fer
myTemplate = BaseDocTemplate(filename="test.pdf", showBoundary=1, author="aaloy")
A l'init el que fa es repassar-se tots els atributs que hem definit al diccionari, si els paràmetres que s'han passat no coincideixen amb la clau del diccionari es crea un nou atribut a l'objecte amb el valor que té al diccionari (el valor per defecte). En canvi si hi és, verifica primer que no sigui un paràmetre de sols lectura, comprovant-ho a _invalidInitArgs i en cas que no ho sigui crea l'atribut amb el valor que li passam com a paràmetre en lloc del valor per defecte que té definit al diccionari.
D'aquesta manera ens permet utilitzar i assignar valor molt fàcilment i sols inicialitzar allò que necessitam.
La quantitat de codi que ens estalvien aquests deus receptes és proporcional al nombre d'atributs que tengui la nostra classe si la fessin en un llenguatge de programació no dinàmic.
4 comentaris, 0 trackbacks (URL) , Tags: Python
No estàs sol
Escrit per Aaloy a 26 de June , 2008 a les 8:15 p.m.
"When the productive have to ask permission from the unproductive in order to produce, then you may know your culture is doomed."
Llegit a un apunt de James Carr que a la seva vegad ho havia llegit del lblog de Reg.
Pels que els costa un poc més llegir l'anglès que el català:
Quan els productius han de demanar permís als improductius per tal de produïr, llavors saps que la teva cultura està condemnada.
On diu cultura, posau-hi empresa o la vostra organització altament jerarquitzada, sí, aquella que posen sempre d'exemple quan es parla del principi de Peter.
2 comentaris, 0 trackbacks (URL) , Tags: General Conyes marineres
La meva experiència amb Django
Escrit per Aaloy a 22 de June , 2008 a les 12:03 p.m.
En Maties Bonet ens va escriure un e-mail a mi i a Guillem demanant-nos per la nostra experiència en l'ús de Django.
Amic Maties, bona cosa has demanat! Pepara't perquè aquest apunt pot ser llarg :)
La meva relació seriosa amb Django ja té més de dos anys, és difícil estimar la data, però si en tingués que donar una seria la del 3 d'agost del 2006, data del primer commit del major projecte que hem fet amb Django fins ara,amb actualment més de 12.000 línies de codi
Aquest projecte inicialment estava desenvolupat en PHP, però necessitàvem més funcionalitat i essent les nostres filies més tendents cap al Python que cap a altra cosa, començarem a investigar els bastiments que començavent a surgir. Cercàvem quelcom que permetés una bona escalabilitat, facilitat de desenvolpament i una separació molt clara entre codi i capa de presentació. A més una de les restriccions inicials era que el servidor que tenia que dur tot això no havia de ser massa potent, un host virtual baratet hauria de ser suficient per començar.
Amb Juan avaluarem un grapat d'opcions. Anàvem mirant frameworks i en discutíem els avantatges i inconvenients. Férem una ullada a Pylons, a TurboGears i alguns altres, fent algun prototip i discutint-ne els resultats.
TurboGears estava força bé, i Pylons representava gairebé l'estat de l'art en quant a integració de componentes, però Django tenia una avantatja per a nosaltres fonamental: la seva integració entre els distints components (integració que a més no compromet a res), una documentació fantàstica i el ser un bastiment que s'havia fet servir en projectes grans, en projectes que estaven funcionant. Es podria dir que no era sols una idea sinó que el funcionament del framework era una realitat.
Així doncs ens decidirem per Django. Mesos després Guido Van Rossum com el framework que li agradava més. Un any i mig després veuríem Django com un bastiment suportat per Google. Això vol dir que tenim bon ull per les tecnologies? Segurament, però vist en perspectiva l'elecció de qualsevol dels altres dos bastiments també ens hauria anat força bé.
Django, però té aquell quelcom que et fa sentir content i satisfet amb la programació que fas. És entenidor i abastable i quan t'enfrontes a un problema de la vida real trobes que hi ha una solució en Django, per una raó molt senzilla: el bastiment es va crear per resoldre problemes de la vida real, com una resposta a la necessitat que tenien els seus creadors de tenir un bastiment que els proporcionàs una gran rapidesa en el desenvolupament i al mateix temps una gran escalabilitat.
Una vegada vàrem veure la potència del bastiment, junt amb la potència de Python i ho compararem amb el que teníem i feiem en Java, el pas següent va ser lògic: utilitzar el que sabíem en la feina diària per a l'empresa per la qual treballàvem, una multinacional del turisme. Sense deixar Java del tot, ara podíem incorporar una nova eina que ens permetria poder desenvolupar webs a la velocitat que li agradava al negoci.
Posar Django i Python a una empresa molt tradicional no és senzill, "aquest tipus de la web sempre fent coses rares!" Però l'evidència s'imposa i amb un poc de ma ampla del nostre director d'informàtica anterior començarem a fer els nostres primers projectes amb Django per a l'empresa. És una tasca que avui en dia encara costa. Quant més consultors externs té l'empresa més difícil és aquesta tasca. Aquests suposats experts no tenen idea de què és Django i de les seves possibilitats, i tampoc els convé, ja que després de la consultoria hi sol haver un desenvolupament i com que el desenvolupament és més ràpid i ells cobren per hores, doncs que no convé gaire. Però això és una altra història i també serà un altra apunt.
Actualment doncs, feim/faig servir Django i Python en quatre grans tipus de projectes:
- projectes on no s'ataca a la base de dades "legacy" de l'empresa, sinó que s'han desenvolupat des de zero.
- projectes on hi ha una gran part de continguts i a més lògica de negoci senzilla.
- projectes que consumeixen web services en SOAP que estan a la seva vegada desenvolupats en Java.
- el nostres projectes particulars, com aquest blog.
Els resultats són molt bons, un exemple: fa dues setmanes ens telefonaren del dimecres per a un projecte crític, s'havia de crear una web completa per a un client que havia d'estar llesta el divendres al matí de la mateixa setmana. El dimecres horabaixa en tendríem més detalls.
L'horabaixa ens defineixen un poc millor el projecte: bàsicament contingut que se'ns aniria passant en format word i que segurament aniria sofrint modificacions damunt la marxa.
Tot just penjar el telèfon ens posarem en marxa. La gent de sistemes creà el repositori subversion pel projecte (no importa la pressa que tenguis, sempre, sempre un control de versions) i inicià la configuració del nou domini.
Una hora després ja teníem el domini intern funcionant i la primera versió del codi dins el subversions. Havíem reaprofitat la funcionalitat que teníem per a la gestió de continguts i ara sols era cosa de crear el disseny, passar-ho a plantilles i posar el contingut. El que ens feia més por era no tenir els DNS replicats per l'hora d'entrega.
El dijous al matí el disseny ja estava llest i es comença a maquetar i pujar continguts. Es crearen algunes plantilles per a fer que les opcions de menú canviessin dinàmicament i s'anava pujant tot al servidor de producció mitjançant un update del subversions. El temps de pujada d'una nova versió era aproximadament de 30 segons, versió que ja pujava testejada gràcies a que amb Django pots anar provant l'aplicació amb el seu servidor integrat (i amb recàrrega automàtica).
Ens sobraren un parell d'hores del temps limit. Total del projecte 35 hores-home. Entregable: aplicació amb subdomini propi, tipus portal de continguts, multi-idioma, amb menús desplegables, i la maquetació a partir de documents word de l'equivalent a una trentena de planes web amb una mescla de text, fotografies i descàrrega d'arxius. Rendiment de l'aplicació: generació d'una plana web en 1,2 segons sense caché.
Si ho haguéssim tingut que fer en Java encara estaríem muntant el CMS o pujant els continguts. Amb les plantilles de Django i la possibilitat d'herència que tenen, poguérem crear el nostre lloc web amb molt poc temps i passar els continguts a HTML de manera molt més ràpida que l'equivalent a crear el disseny en un CMS clàssic de PHP o Java (ja no en parlem de fer el mateix amb Java i sense CMS).
El millor de tot és que encara que no sabíem que se'ns demanaria estàvem raonablement segurs de que si no era res molt complexe es podria fer, ja que el bastiment no ens fermava (com sovint fan els CMS més habituals) sinó tot el contrari.
La meva experiència amb Django? Fantàstica i demostrable. Fins al punt que quan veig el que puc fer en aquest entorn em fa molta peresa tornar cap a Java, els temps d'espera tot i que desenvolupam en local resulten molests, les posades en producció s'eternitzen. I això que gràcies al nostres administradors de sistemes ho tenim tot que va com una seda a l'entorn J2EE i les posades en producció són ràpides, però quan ho compares amb un pocs segons tot resulta lent.
Consider Django com un avantatge competitiu: ens permet fer desenvolpaments molt ràpidament i a més sabem que escalaran bé. Com que no estam lligats a cap bastiment de javascript concret podem incorporar el que necessitem segons el projecte: jQuery, extjs, res... i la separació que es fa en capes de l'aplicació també es pot fer a l'hora de desenvolupar i permet treballar en paral·lel a la gent de sistemes, disseny i programació.
I així doncs, quan voleu que posem Django a la vostra empresa?
4 comentaris, 0 trackbacks (URL) , Tags: Python Django
A la pregunta de Servo ....
Escrit per Aaloy a 15 de June , 2008 a les 7:20 p.m.
Al comentari del darrer apunt de Servo es demanava el perquè el Set hauria de ser més ràpid si el primer algorisme és d'ordre N.
El codi dels sets es molt semblant al dels diccionaris, però està una mica més optimitzat, a l'igual que els diccionaris està implementat com una taula hash, però a diferència d'aquests els conjunts sols guarden el parell clau/hash en lloc de la tripleta clau/valor/valor del diccionari.
Com que els sets guarden els parells clau/hash, totes les operacions binàries, com la intersecció s'executen sense cap cridada al mètode _ _hash__ dels elements individuals. Això és molt més ràpid que el codi equivalent que fa servir diccionaris.
També resulta que quan feim un bucle damunt un set Python (CPython) directament recorre la taula hash en lloc de fer ús d'un iterador (que seria un poc més lent).
La pregunta ha servit per fer un poc de recerca, de fet la meva resposta és la traducció d'una resposta molt completa l'he trobada a Python in Science .
Però no ens conformem amb això, fem un "show me the code" a la plana citada anteriorment hi ha un exemple que ens anirà bé per començar. Es tracta de trobar la intersecció de dos conjunts, però ho podem adaptar per fer les cerques al diccionari:
import random import time REPETICIONS = 1000 ## Generam els valors seta = set([random.randint(0,100000) for n in xrange(10000)]) setb = set([random.randint(0,100000) for n in xrange(10000)]) print "Cerques a conjunts" t0 = time.clock() for i in xrange(REPETICIONS): total = seta.intersection(setb) print "Intersections - Time: %s seconds"%(time.clock()-t0) t0 = time.clock() for i in xrange(REPETICIONS): total2 = [] for element in seta: if element in setb: total2.append(element) print "Fent el bucle Time: %s seconds"%(time.clock()-t0) ## I ara el nostre cas print "Cerques amb diccionaris" ## Generam els valors dicta = {} dictb = {} for i in xrange(10000): dicta[random.randint(0,100000)]=i dictb[random.randint(0,100000)]=i t0 = time.clock() for x in xrange(REPETICIONS): total2 = [] for valor in dicta.keys(): if dictb.get(valor): total2.append((valor, dicta[valor])) print "Bucle - Time: %s seconds"%(time.clock()-t0) t0 = time.clock() for x in xrange(REPETICIONS): total2 = [ (repe,dicta[repe]) for repe in set(dicta).intersection(set(dictb))] print "Set intersection - Time: %s seconds"%(time.clock()-t0)
I aquí el resultat
Cerques a conjunts Intersections - Time: 0.94 seconds Fent el bucle Time: 6.11 seconds Cerques amb diccionaris Bucle - Time: 11.63 seconds Set intersection - Time: 6.97 seconds
En el primer cas estam davant un algorisme d'ordre N² davant un algorisme NlogN sin o record malament de la intersecció de conjunts.
El nostre cas és una mica menys clar en el que fa a l'ordre de l'algorisme, però amb el codi es pot veure com el resultat final s'obté és un 60% més ràpid.
Tot i això fixem-nos amb que per a obtenir el resultat en segons he tingut que fer 1.000 iteracions i que hem fet servir un conjunt de 10.000 elements, sols per a que quedi clar de que potser ambdós algorismes són prou ràpids per a la nostra tasca diària. Sols hem de tenir clar que quan cerquem maneres d'optimitzar el nostre codi, mirar si feim aquests tipus d'algorismes moltes vegades
0 comentaris, 0 trackbacks (URL) , Tags: Python
Trobar elements repetits
Escrit per Aaloy a 15 de June , 2008 a les 12:37 p.m.
Tenim el següent problema: "tenim dos diccionaris amb dades i volem trobar els elements d'una diccionari que estan també dins l'altra"
Suposem, per exemple que tenim:
x = {'a':1,'b':2,'r':3}
y = {'a':1,'r':3, 'c':14}
La opció més directe pareix ser la de recorre els elements de la primera llista i veure si hi són a la segona, una cosa com
for valor in x.keys():
if y[valor]:
print valor, y[valor]
a 1 r 3
o bé una altra opció més curta:
for repe in set(x).intersection(set(y)):
print repe, x[repe]
o si m'apurau
[ (repe,x[repe]) for repe in set(x).intersection(set(y))]
[('a', 1), ('r', 3)]
Ara quan algú us demani que és això de que Python ve amb les piles incloses ja teniu un exemple més per a mostrar.
4 comentaris, 0 trackbacks (URL) , Tags: Python
Millores al blog
Escrit per Aaloy a 08 de June , 2008 a les 11:30 a.m.
De tant en tant faig feina al blog, no per escriure-hi sinó per afegir-hi noves funcionalitats. Encara hi ha moltes coses que m'agradaria posar-hi i millorar, però a poc a poc esper anar arribant-hi.
A la darrera actualització he fet algunes millores que recomanaven al Google Webmaster Tools com la d'afegir descripcions úniques per apunt. Django a les seves plantilles té el filtre truncatewords_html que m'ha anat fantàstic per això.
Una de les millores que volia fer també era la d'amagar un poc tota la llista de mesos que hi ha. Aquest blog té apunts des del 2004 i la llista començava a ser molt llarga. Ara veureu que sols apareixen els anys (sempre que tingueu el javascript activat clar) i que en pitjar damunt ells es despleguen els mesos. Fer això ha estat entretingut perquè ha implicat jugar amb dos tags més de Django, el for per obtenir si estava a la primera posició del bucle o a la darrera, per tal de poder tancar els divs, i el tag ifchanged que ens permet saber si una variable (en el meu cas l'any) ha canviat o no respecte al seu valor anterior.
Amb aquestes eines ha estat possible muntar l'estructura necessària per a utilitzar un el Animated Collapsible DIV, una petita utilitat per jquery que permet ocultar i mostrar divs a voluntat, agrupant-los per categories si convé.
Ara la plana principal al meu entendre queda un poc més neta i amb prou espai a la columna lateral esquerra per anar afegint més coses.
0 comentaris, 0 trackbacks (URL) , Tags: Informàtica Python Django