Commentaires dans CPS, à étoffer un peu. Et léger typofix.
This commit is contained in:
parent
890a1067fc
commit
ef07bc5a28
2 changed files with 116 additions and 59 deletions
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue