From ef07bc5a287e496e6c9bf2460d229bf9602c150f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Elliott=20B=C3=A9cue?= Date: Wed, 4 Mar 2015 00:01:10 +0100 Subject: [PATCH] =?UTF-8?q?Commentaires=20dans=20CPS,=20=C3=A0=20=C3=A9tof?= =?UTF-8?q?fer=20un=20peu.=20Et=20l=C3=A9ger=20typofix.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/dialog/CPS.py | 165 +++++++++++++++++++++++++------------ gestion/dialog/adherent.py | 10 +-- 2 files changed, 116 insertions(+), 59 deletions(-) diff --git a/gestion/dialog/CPS.py b/gestion/dialog/CPS.py index 1ea88eb7..09fd44ee 100644 --- a/gestion/dialog/CPS.py +++ b/gestion/dialog/CPS.py @@ -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') - if isinstance(txt, list): - for v in txt: - mydebug(' ' + str(v)) - else: - debugf.write(str(txt)+'\n') - debugf.flush() - os.fsync(debugf) + """Petite fonction pour logguer du debug avec CLogger. + LOGGER.debug n'écrit que si level est à debug""" + if isinstance(txt, list): + 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) + ret = self.f(*args, **kwargs) except Continue as c: - ret = c.tailCall + # 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)) diff --git a/gestion/dialog/adherent.py b/gestion/dialog/adherent.py index f806060b..dfb11bb3 100644 --- a/gestion/dialog/adherent.py +++ b/gestion/dialog/adherent.py @@ -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)