El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Sigues dinàmic

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.

blog comments powered by Disqus