Introspecció a Python

Per tenir les dades que genera una aplicació que estic fent amb Python he escrit un parell de funcions que em permeten tenir un document estructurat (en xml) de totes les dades que es generen[1].

La tècnica de introspecció es basa en aprofitar les meta-dades (per dir-ho d’alguna manera) que tens sobre els objectes que has anat construint en l’aplicació per (dinàmicament) poder-ho formatar d’alguna manera.

Amb Python tenim algunes propietats, com molt bé es descriuen en un article d’IBM [2]:

  • objecte.__dict__ : Ens retorna una llista amb els elements que formen part de l’objecte. Així per mostrar els elements faríem:
  • for element in objecte__dict__:
    print “%s val %s” % (element, objecte.__dict__[element])

  • Si volem refinar el que volem mostrar tenim tot un seguit de funcions que ens diuen quin tipus de variable estem tractant: les comparacions amb el comparador is (exemple: if x is dict ….) per als tipus bàsics, callable per saber si és tracta d’un mètode, i per a objectes i classes isinstance(objecte, classe-o-llista-de-classes) i issubclass(classe, info-de-classe).

Només amb això podem treure molt suc a la informació que tenim ara mateix a les estructures de dades que estem muntant i si es fa el més genèric possible, no caldrà tocar ni una coma quan afegim més dades o hi encadenem alguna altra estructura.

[1] En certa manera hi ha truc perquè el sistema són classes que tenen diccionaris de classes a dintre fins a 5 nivells de manera que la introspecció és recursiva per naturalesa.

[2] És una mica vell, del 2002, però pels meus objectius n’he tingut ben prou :)

pas d’arguments amb Python


Per pas d’arguments s’entén que quan crides una funció li dius amb quins valors vols que faci la seva funció.

Per exemple:

suma(3,4) # retorna 7
suma(4,5) # retorna 9

El problema pot passar (com m’acabo de trobar i solucionar) en que si haig de passar força arguments (6 en concret) i a més els haig de redirigir es fa pesat mantenir les llistes d’arguments sincronitzades, per exemple si tenim un parell de classes que una hereda (Secundaria) de l’altre (Principal) i volem cridar el constructor de Principal des del constructor de Secundaria haurem de fer una cosa per l’estil:

class Principal():
def __init__(self, primer, segon, tercer, quart, cinquè, sisé):
/* … operem amb ells …*/
class Secundaria (Principal):
def __init__(self, primer, segon, tercer, quart, cinquè, sisé):
Principal.__init__(primer, segon, tercert, quart, cinquè, sisé)

Per a simplificar-ho Python ens permet diverses coses:

  • valors predeterminats: Si sabem que un valor serà gairebé sempre el mateix i en contades vegades un de diferent podem fer que sigui opcional donant-li un valor des de la declaració de la funció
  • def taula_multiplicar(base, inici=0, final=10): # declarem una funció amb 3 paràmetres dos dels quals ja tenen un valor assignat
    taula_multiplicar(3) # ens mostraria la taula de multiplicar del 3 des de 3*0 fins a 3*10, la típica de l’escola
    taula_multiplicar(4, 5) # ens mostraria la taula del 4 des del 5 fins al 10
    taula_multiplicar(4, 9, 12) # ens mostraria la taula del 4 des del 9 fins al 12

  • llista d’arguments: Si no sabem quants arguments (o no ens importa) podem dir que els agafi tots
  • def crea_directoris(*directoris): # declarem una funció que agafa tots els arguments que li passen i els posa en una llista (tupla en Python)
    crea_directoris(‘/home/gil’, ‘/home/silvia’, ‘/home/softcatala’, ‘/home/gnome’) # ens crearia els 4 directoris que li passem
    llista = ( ‘/home/gil’, ‘/home/silvia’) # creem una llista amb els arguments
    crea_directoris(*llista) # passem la llista enlloc de tot el text en la crida, li indiquem que es una llista amb el símbol *

  • diccionari d’arguments: Si el que volem es enviar les dades però no ens recordem de l’ordre en que estan posats en la declaració (per exemple si fem taula_multiplicar(3,5,7) però volíem la taula del 7 del 3 al 5 ens trobarem amb la taula del 3 del 5 al 7) podem fer servir els noms dels arguments
  • taula_multiplicar(inici=3, final=5, base=7) # ara ja tindrem la taula de multiplicar del 7 des del 3 al 5def parelles(**valors): # declarem una funció en que rebrem parelles de valors
    parelles(un=1, dos=2, tres=3) # passem parelles de paràmetres amb nom amb el seus valors
    diccionari = { ‘un’: 1 , ‘dos’: 2, ‘tres’: 3} # creem un diccionari
    parelles(**diccionari) # enlloc d’haver-ho de posar tot en la crida a la funció creem un diccionari abans i ho indiquem en la crida amb els dos * (igual com en el pas per llista)

També es poden fer coses més sofisticades i complicades com barrejar llistes, diccionaris, valors predeterminats i paràmetres normals, per exemple:

def funcio_estranya( valor_posicional, valor_predeterminat=5, *resta_arguments): # amb aixo creem una funció que el primer paràmetre es un de normal, després en bé un d’opcional i la resta hauran de ser una llista d’arguments variable

No està malament eh :)

mètodes virtuals? polimorfisme? delegació? herència?

Aquest tros de codi amb Python:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

class main():
   def laptop(self):
      self.p()

class one(main):
   def p(self):
      print "Lenovo"

class two(main):
   def p(self):
      print "Asus"

# Creem un parell d'instàncies
i_one = one()
i_two = two()

i_one.laptop() # mostra "Lenovo"
i_two.laptop() # mostra "Asus"

El que fa bàsicament és crear 3 classes (dues d’elles –one i two– hereden de la primera –main-) de manera que totes les instàncies que es facin de les classes filles tindran els mètodes de la classe mare (el mètode laptop en aquest cas).

A més cada classe filla defineix una mateixa funció (la funció p).

La gràcia d’aquest disseny és que et permet definir una especialització de comportament de manera que per exemple, si tens una col·lecció d’instàncies de one i two barrejades però vols saber-ne quin portàtil tenen, només cal que executis el mètode laptop a tota la col·lecció que sense cap problema ens dirà per a cada un d’ells quin portàtil té.

El dubte que em queda ara és: quin nom rep aquesta tècnica? Perquè he estat buscant en el llibre de Python que tinc (Learning Python d’O’Reilly) d’on he recordat aquesta possibilitat del Python i no he aconseguit trobar-la ni a l’índex del principi (per temes) ni  el del final (per nom).

I si vull un diccionari invers?

Logotip del PythonA la feina havia d’ordenar un diccionari pels valors (i no per les claus), però després volia accedir (per clau) a les dades sense haver d’estar iterant per tot el diccionari fins que trobés una clau que tenia per valor el valor pel que estava iterant (mola l’explicació eh :D

Doncs molt fàcil, crees un altre diccionari amb els elements al revés i així ja tens l’accés directe tant a la clau com al valor :)

O sigui:

dict((v,k) for k,v in dictionary.items())

Fàcil oi?

Trobat a la llista de Python (del 2005!)

ajuda del python

Logotip del PythonRecordatori per a mi mateix i per a qui pensi que li és útil :)

dir(tipus variable o objecte ja declarat)

Amb això ens mostra totes les funcions que podem utilitzar sobre un tipus de dada, per exemple:

dir(str)

['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__',
'__getattribute__', '__getitem__', '__getnewargs__', __getslice__', '__gt__',
'__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__',
'__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode',
'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower',
'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace',
'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

I per saber què fa, per exemple, zfill, només cal que teclajem:

help(str.zfill)

zfill(...)
S.zfill(width) -> string

Pad a numeric string S with zeros on the left, to fill a field
of the specified width.  The string S is never truncated.