Clavando un clavo con un zapato I: Do-Sheet.
Te hace pensar diferente.
Es divertido.
Lo malo es, por supuesto, que el contenido de la charla tiene que ser secreto, o no tiene ninguna gracia. Como el proceso de review para charlas de PyConAr es público, no tenía manera de explicar de qué se trataba.
Como eso significa que pongo a los revisores en el compromiso de tener que aceptar mi palabra de que esta charla es algo interesante, y eso es injusto para ellos y los demás charlarines, cancelé la propuesta.
La (tal vez) buena noticia es que ahora todos van a poder ver de qué se trataba la charla. Acá está el clavo número 1: Escribir una hoja de cálculo usando doit.
Esta no es mi primera "hoja de cálculo". Todo empezó hace mucho, mucho tiempo con una famosa receta de Raymond Hettinger que he usado una y otra y otra vez (capaz que falta alguna).
Dado que vengo usando doit para Nikola estoy impresionado con lo potente que es. En breve, doit te permite crear tareas, y esas tareas dependen de otras, operan en datos, dan resultados que otras tareas usan, etc.
¿Se ve adonde va esto?
Acá va el código, con explicaciones:
cells
es nuestra hoja. Podés poner cualquier cosa, pero usá siempre
el formato "nombre=formula" y la fórmula tiene que ser Python válido ¿ok?
task_calculate
crea una tarea para cada celda, llamada calculate:NOMBRE
.
La acción que esa tarea realiza es evaluar la fórmula. Pero para hacer eso de manera
exitosa, necesitamos saber qué otras celdas hay que evaluar primero.
Eso lo implementé usando las calculated dependencies de doit, con la tarea "get_dep:FORMULA" para la fórmula de esta celda.
def evaluate(name, formula): value = eval(formula, values) values[name] = value print "%s = %s" % (name, value) def task_calculate(): for cell in cells: name, formula = cell.split('=') yield { 'name':name, 'calc_dep': ['get_dep:%s' % formula], 'actions': [(evaluate, (name, formula))], }
Por ejemplo, nuestra A1
depende de A3
y A2
que no dependen de nada. Para
parsear esto, usé el módulo tokenize, y tomo nota de cuales cosas son "nombres". Existen
maneras más soisticadas ;-)
la función task_get_dep
es una tarea de doit que crea tareas llamadas "get_dep:NOMBRE"
para cada nombre de celda en cells
.
A su vez, get_dep devuelve una lista de tareas de doit. Para nuestra celda A1
, eso
sería ["calculate:A2", "calculate:A3"]
o sea que para poder calcular A1
primero
tenemos que terminar esas tareas.
def get_dep(formula): """Given a formula, return the names of the cells referenced.""" deps = {} try: for token in generate_tokens([formula].pop): if token[0] == 1: # A variable deps[token[1]] = None except IndexError: # It's ok pass return { 'result_dep': ['calculate:%s' % key for key in deps.keys()] } def task_get_dep(): for cell in cells: name, formula = cell.split('=') yield { 'name': formula, 'actions': [(get_dep, (formula,))], }
Y eso es todo. Veámoslo en acción. Podés obtener tu propia copia acá
y probarlo instalando doit, editando cells
y usándolo así:
ralsina@perdido:~/dosheet$ doit -v2 calculate:A3 . get_dep:4 {} . calculate:A3 A3 = 4 ralsina@perdido:~/dosheet$ doit -v2 calculate:A2 . get_dep:2 {} . calculate:A2 A2 = 2 ralsina@perdido:~/dosheet$ doit -v2 calculate:A1 . get_dep:A3+A2 {'A3': None, 'A2': None} . get_dep:4 {} . calculate:A3 A3 = 4 . get_dep:2 {} . calculate:A2 A2 = 2 . calculate:A1 A1 = 6
Como podés ver, siempre hace el mínimo esfuerzo posible para calcular el resultado deseado. Si querés, hay algunas cosas mejorables, que dejo como ejercicio para el lector. Por ejemplo:
Usar uptodate para no recalcular dependencias inutilmente.
Eliminar la variable global
values
y usar los computed values de doit en su lugar.
Acá está el listado completo, buen provecho!
El link al listado completo da 404.
Arreglado, gracias!