Commentaires dans CPS, à étoffer un peu. Et léger typofix.

This commit is contained in:
Pierre-Elliott Bécue 2015-03-04 00:01:10 +01:00
parent 890a1067fc
commit ef07bc5a28
2 changed files with 116 additions and 59 deletions

View file

@ -1,10 +1,23 @@
#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# Copyright (C) Valentin Samir
# Licence : GPLv3
u"""
Copyright (C) Valentin Samir
Licence : GPLv3
Module implémentant la "Continuation Passing Style".
Le concept est d'exécuter des fonctions qui sont des TailCallers,
qui elles-mêmes seront amenées à appeler d'autres fonctions,
qui sont des TailCalls, ou qui lèvent des exceptions de type
Continue(TailCall).
Ces TailCalls pourraient être amenés à exécuter d'autres fonctions,
dans ce cas, pour faire en sorte que leur empreinte mémoire soit
faible, auquel cas, soit ces fonctions sont elles-mêmes des
TailCalls, soit ce sont des TailCallers.
L'intérêt de ce concept est de favoriser une stack à empreinte
basse.
"""
import os
import sys
@ -16,27 +29,33 @@ import traceback
if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts')
from pythondialog import Dialog as PythonDialog
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
from pythondialog import error as DialogError
from gestion.affich_tools import get_screen_size, coul
from gestion import affichage
debug_enable = False
debugf = None
from cranslib import clogger
# Modifier level à debug pour débugguer.
LOGGER = clogger.CLogger('gest_crans_lc', service="CPS", level='info')
ENCODING = affichage.guess_preferred_encoding()
def mydebug(txt):
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log"""
global debugf, debug_enable
if debug_enable:
if debugf is None:
debugf = open('/tmp/gest_crans_lc.log', 'w')
"""Petite fonction pour logguer du debug avec CLogger.
LOGGER.debug n'écrit que si level est à debug"""
if isinstance(txt, list):
for v in txt:
mydebug(' ' + str(v))
else:
debugf.write(str(txt)+'\n')
debugf.flush()
os.fsync(debugf)
txt = "\n".join(txt)
if isinstance(txt, unicode):
txt = txt.encode(ENCODING)
LOGGER.debug(txt)
class Continue(Exception):
"""Exception pour envoyer des TailCall en les raisant
l'argument tailCall est soit une fonction décorée, soit
la fonction retournante elle-même."""
def __init__(self, tailCall):
self.tailCall = tailCall
# Implémentation "à la main" de la tail récursion en python
# voir http://kylem.net/programming/tailcall.html
@ -45,63 +64,91 @@ def mydebug(txt):
# code en CPS (Continuation Passing Style)
class TailCaller(object) :
"""
Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion
faite "à la main" en python (voir http://kylem.net/programming/tailcall.html)
Un TailCaller est le doux nom d'un objet (une fonction décorée) qui crée et
joue avec des TailCall. Les TailCalls sont des jouets qui servent à réduire
l'empreinte dans la stack d'appels récursifs.
Parameters
----------
f : function
Fonction décoré
Lorsqu'on __call__ un TailCaller, celui-ci exécute la fonction qu'il décore.
Si le retour de celle-ci est un TailCall, on exécute le call de celui-ci,
et ainsi de suite, jusqu'à avoir un retour qui ne soit pas un TailCall.
Un call ou la fonction du TailCaller peuvent retourner des erreurs de type
continue, qui généralement contiennent un TailCall, mais peuvent aussi
contenir une sortie.
"""
other_callers = {}
def __init__(self, f) :
def __init__(self, f):
"""On prend f et son nom et on les stocke dans le TailCaller, wouhou,
c'est trop fou."""
self.f = f
self.func_name = f.func_name
TailCaller.other_callers[id(self)]=f.func_name
def __del__(self):
"""Supprime le TailCaller de la liste"""
del(TailCaller.other_callers[id(self)])
def __call__(self, *args, **kwargs) :
def __call__(self, *args, **kwargs):
"""Quand on appelle le TailCaller pour de vrai,
on vérifie la position actuelle du bordel dans la stack,
et on exécute la fonction référencée, et les TailCalls qu'on
peut croiser sur la route, pour peu que leur position en
stack soit postérieure à celle du TailCaller actuel."""
mydebug("***%s calling" % self.func_name)
stacklvl = len(inspect.stack())
# On applique f une première fois aux arguments
# qui vont bien.
# Cela peut retourner un TailCall, un résultat, ou
# lever une erreur de type Continue. Souvent les fonctions
# raisent des Continue contenant des TailCall, dans le but
# de ne pas augmenter le contenu de la stack.
try:
ret = self.f(*args, **kwargs)
except Continue as c:
# Le TailCall est là-dedans.
ret = c.tailCall
# Si f a terminé, ret n'est pas un TailCall mais sa valeur
# de retour. Sinon, on entre dans la boucle.
while isinstance(ret, TailCall):
# Si la continuation a été créer par un TailCaller précédent, on propage
# Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall
# Crée après lui.
if stacklvl>=ret.stacklvl:
# Ici, on regarde l'endroit dans la stack de l'objet courant
# vis-à-vis de la position dans la stack de ret. (plus le stack
# lvl est élevé, plus on a été appelé récemment)
# On constate donc ici qu'on ne s'intéresse qu'aux TailCall qui
# ont été créés après l'appel du TailCaller actuel.
if stacklvl >= ret.stacklvl:
mydebug("***%s terminate" % self.func_name)
# Ici, on peut donc raise un Continue, qui fait sortir
# de l'exécution du TailCaller courant.
raise Continue(ret)
mydebug("***%s doing %s" % (self.func_name, ret))
# L'appel à un TailCall se fait via la méthode handle.
# Si ça retourne une continuation, on récupère le tailCall
# dedans dans ret, et on boucle. Sinon, soit le TailCall
# retourne un TailCall, soit il retourne un résultat.
try:
ret = ret.handle()
except Continue as c:
ret = c.tailCall
mydebug("***%s returning %s" % (self.func_name, ret))
return ret
def tailcaller(f):
"""
Décorateur retardant la décoration par TailCaller d'une fonction
À utiliser sur les fonctions faisant de la tail récursion
et devant retourner une valeur. On l'utilise de toute
façon sur la fonction d'entrée dans l'application
Une fonction de devrait pas instancier de TailCall sans
être décoré par ce décorteur de façon générale
"""Décorateur permettant aux fonctions d'une classe d'être
décorées comme TailCaller à l'instanciation de ladite classe.
Cela permet que seules les instances aient des méthodes qui
soient des TailCallers. La conversion se faisant au premier
appel de la fonction dans l'instance.
"""
f.tailCaller = True
return f
class Continue(Exception):
"""Exception pour envoyer des TailCall en les raisant"""
def __init__(self, tailCall):
self.tailCall = tailCall
class TailCall(object) :
"""
Appel tail récursif à une fonction et ses argument
@ -109,12 +156,17 @@ class TailCall(object) :
l'appel à une fonction (avec ses listes d'arguements)
à un moment futur. La fonction sera appelée dans les
cas suivant :
* Le TailCall est retourné par une fonction qui est un TailCaller
* Le TailCall est retourné dans l'appel de la fonction d'un
TailCaller.
* L'exception Continue(TailCall(...)) est levée et traverse
une fonction qui est un TailCaller
"""
def __init__(self, call, *args, **kwargs) :
"""Création du TailCall, on note la position dans la pile
Si la fonction passée à TailCall est un TailCall, on override
le TailCall qu'on est en train d'instancier.
"""
self.stacklvl = len(inspect.stack())
if isinstance(call, TailCall):
call.kwargs.update(**kwargs)
@ -129,6 +181,8 @@ class TailCall(object) :
self.check(self.args, self.kwargs)
def check(self, args, kwargs):
"""On vérifie si le TailCall a le bon nombre d'arguments,
et si les keywords arguments sont bons."""
call = self.call
if isinstance(call, TailCaller):
call = call.f
@ -136,6 +190,7 @@ class TailCall(object) :
if targs.varargs is not None:
if len(args) + len(kwargs) > len(targs.args):
raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs)))
for key in kwargs:
if key not in targs.args:
raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key))
@ -160,7 +215,11 @@ class TailCall(object) :
return result
def __call__(self, *args, **kwargs):
tmpkwargs={}
"""Met à jour le TailCall en ajoutant args et kwargs
à la liste des arguments à passer au call lors de son appel
via handle.
"""
tmpkwargs = {}
tmpkwargs.update(self.kwargs)
tmpkwargs.update(kwargs)
self.check(self.args + args, tmpkwargs)
@ -171,14 +230,12 @@ class TailCall(object) :
def handle(self) :
"""
Exécute la fonction call sur sa liste d'argument.
on déréférence les TailCaller le plus possible pour réduire
la taille de la stack
Si la fonction à exécuter est un TailCaller, on déréférence.
"""
caller = None
call = self.call
while isinstance(call, TailCaller) :
caller = call
call = self.call.f
while isinstance(call, TailCaller):
call = call.f
return call(*self.args, **self.kwargs)
def unicode_of_Error(x):
@ -227,7 +284,7 @@ class Dialog(object):
"""
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
"""
(lines, cols) = get_screen_size()
(lines, cols) = affichage.getTerminalSize()
print "\033[48;5;17m"
print " "*(lines * cols)
cols = int(min(cols/2, 65))

View file

@ -723,19 +723,19 @@ class Dialog(proprio.Dialog):
def set_chambre(adherent, chbre):
try:
adherent['postalAddress'] = []
adherent['chbre'] = unicode(output, 'utf-8')
adherent['chbre'] = unicode(chbre, 'utf-8')
except UniquenessError:
if expulse_squatteur(adherent, chbre):
# La chambre est maintenant normalement libre
adherent['chbre'] = unicode(output, 'utf-8')
adherent['chbre'] = unicode(chbre, 'utf-8')
else:
raise Continue(self_cont)
return adherent
def todo(chbre, adherent, self_cont, success_cont):
if not output:
if not chbre:
raise Continue(self_cont)
if output == "????":
if chbre == "????":
raise ValueError("Chambre ???? invalide")
if create:
return set_chambre(adherent, chbre)
@ -745,7 +745,7 @@ class Dialog(proprio.Dialog):
adherent.validate_changes()
adherent.history_gen()
adherent.save()
self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output)
self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % chbre)
raise Continue(success_cont(adherent=adherent))
(code, output) = self.handle_dialog(cont, box)