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 #!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# Copyright (C) Valentin Samir
# Licence : GPLv3
u""" u"""
Copyright (C) Valentin Samir Module implémentant la "Continuation Passing Style".
Licence : GPLv3
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 os
import sys import sys
@ -16,27 +29,33 @@ import traceback
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts')
from pythondialog import Dialog as PythonDialog from pythondialog import Dialog as PythonDialog
from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess
from pythondialog import error as DialogError from pythondialog import error as DialogError
from gestion.affich_tools import get_screen_size, coul from gestion import affichage
debug_enable = False from cranslib import clogger
debugf = None
# Modifier level à debug pour débugguer.
LOGGER = clogger.CLogger('gest_crans_lc', service="CPS", level='info')
ENCODING = affichage.guess_preferred_encoding()
def mydebug(txt): def mydebug(txt):
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log""" """Petite fonction pour logguer du debug avec CLogger.
global debugf, debug_enable LOGGER.debug n'écrit que si level est à debug"""
if debug_enable:
if debugf is None:
debugf = open('/tmp/gest_crans_lc.log', 'w')
if isinstance(txt, list): if isinstance(txt, list):
for v in txt: txt = "\n".join(txt)
mydebug(' ' + str(v)) if isinstance(txt, unicode):
else: txt = txt.encode(ENCODING)
debugf.write(str(txt)+'\n') LOGGER.debug(txt)
debugf.flush()
os.fsync(debugf) 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 # Implémentation "à la main" de la tail récursion en python
# voir http://kylem.net/programming/tailcall.html # voir http://kylem.net/programming/tailcall.html
@ -45,63 +64,91 @@ def mydebug(txt):
# code en CPS (Continuation Passing Style) # code en CPS (Continuation Passing Style)
class TailCaller(object) : class TailCaller(object) :
""" """
Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion Un TailCaller est le doux nom d'un objet (une fonction décorée) qui crée et
faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) joue avec des TailCall. Les TailCalls sont des jouets qui servent à réduire
l'empreinte dans la stack d'appels récursifs.
Parameters 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,
f : function et ainsi de suite, jusqu'à avoir un retour qui ne soit pas un TailCall.
Fonction décoré
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 = {} 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.f = f
self.func_name = f.func_name self.func_name = f.func_name
TailCaller.other_callers[id(self)]=f.func_name TailCaller.other_callers[id(self)]=f.func_name
def __del__(self): def __del__(self):
"""Supprime le TailCaller de la liste"""
del(TailCaller.other_callers[id(self)]) 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) mydebug("***%s calling" % self.func_name)
stacklvl = len(inspect.stack()) 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: try:
ret = self.f(*args, **kwargs) ret = self.f(*args, **kwargs)
except Continue as c: except Continue as c:
# Le TailCall est là-dedans.
ret = c.tailCall 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): while isinstance(ret, TailCall):
# Si la continuation a été créer par un TailCaller précédent, on propage # Ici, on regarde l'endroit dans la stack de l'objet courant
# Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall # vis-à-vis de la position dans la stack de ret. (plus le stack
# Crée après lui. # 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: if stacklvl >= ret.stacklvl:
mydebug("***%s terminate" % self.func_name) 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) raise Continue(ret)
mydebug("***%s doing %s" % (self.func_name, 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: try:
ret = ret.handle() ret = ret.handle()
except Continue as c: except Continue as c:
ret = c.tailCall ret = c.tailCall
mydebug("***%s returning %s" % (self.func_name, ret)) mydebug("***%s returning %s" % (self.func_name, ret))
return ret return ret
def tailcaller(f): def tailcaller(f):
""" """Décorateur permettant aux fonctions d'une classe d'être
Décorateur retardant la décoration par TailCaller d'une fonction décorées comme TailCaller à l'instanciation de ladite classe.
À utiliser sur les fonctions faisant de la tail récursion
et devant retourner une valeur. On l'utilise de toute Cela permet que seules les instances aient des méthodes qui
façon sur la fonction d'entrée dans l'application soient des TailCallers. La conversion se faisant au premier
Une fonction de devrait pas instancier de TailCall sans appel de la fonction dans l'instance.
être décoré par ce décorteur de façon générale
""" """
f.tailCaller = True f.tailCaller = True
return f return f
class Continue(Exception):
"""Exception pour envoyer des TailCall en les raisant"""
def __init__(self, tailCall):
self.tailCall = tailCall
class TailCall(object) : class TailCall(object) :
""" """
Appel tail récursif à une fonction et ses argument 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) l'appel à une fonction (avec ses listes d'arguements)
à un moment futur. La fonction sera appelée dans les à un moment futur. La fonction sera appelée dans les
cas suivant : 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 * L'exception Continue(TailCall(...)) est levée et traverse
une fonction qui est un TailCaller une fonction qui est un TailCaller
""" """
def __init__(self, call, *args, **kwargs) : 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()) self.stacklvl = len(inspect.stack())
if isinstance(call, TailCall): if isinstance(call, TailCall):
call.kwargs.update(**kwargs) call.kwargs.update(**kwargs)
@ -129,6 +181,8 @@ class TailCall(object) :
self.check(self.args, self.kwargs) self.check(self.args, self.kwargs)
def check(self, args, 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 call = self.call
if isinstance(call, TailCaller): if isinstance(call, TailCaller):
call = call.f call = call.f
@ -136,6 +190,7 @@ class TailCall(object) :
if targs.varargs is not None: if targs.varargs is not None:
if len(args) + len(kwargs) > len(targs.args): 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))) raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs)))
for key in kwargs: for key in kwargs:
if key not in targs.args: if key not in targs.args:
raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key)) raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key))
@ -160,6 +215,10 @@ class TailCall(object) :
return result return result
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
"""Met à jour le TailCall en ajoutant args et kwargs
à la liste des arguments à passer au call lors de son appel
via handle.
"""
tmpkwargs = {} tmpkwargs = {}
tmpkwargs.update(self.kwargs) tmpkwargs.update(self.kwargs)
tmpkwargs.update(kwargs) tmpkwargs.update(kwargs)
@ -171,14 +230,12 @@ class TailCall(object) :
def handle(self) : def handle(self) :
""" """
Exécute la fonction call sur sa liste d'argument. 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 call = self.call
while isinstance(call, TailCaller): while isinstance(call, TailCaller):
caller = call call = call.f
call = self.call.f
return call(*self.args, **self.kwargs) return call(*self.args, **self.kwargs)
def unicode_of_Error(x): 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 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 "\033[48;5;17m"
print " "*(lines * cols) print " "*(lines * cols)
cols = int(min(cols/2, 65)) cols = int(min(cols/2, 65))

View file

@ -723,19 +723,19 @@ class Dialog(proprio.Dialog):
def set_chambre(adherent, chbre): def set_chambre(adherent, chbre):
try: try:
adherent['postalAddress'] = [] adherent['postalAddress'] = []
adherent['chbre'] = unicode(output, 'utf-8') adherent['chbre'] = unicode(chbre, 'utf-8')
except UniquenessError: except UniquenessError:
if expulse_squatteur(adherent, chbre): if expulse_squatteur(adherent, chbre):
# La chambre est maintenant normalement libre # La chambre est maintenant normalement libre
adherent['chbre'] = unicode(output, 'utf-8') adherent['chbre'] = unicode(chbre, 'utf-8')
else: else:
raise Continue(self_cont) raise Continue(self_cont)
return adherent return adherent
def todo(chbre, adherent, self_cont, success_cont): def todo(chbre, adherent, self_cont, success_cont):
if not output: if not chbre:
raise Continue(self_cont) raise Continue(self_cont)
if output == "????": if chbre == "????":
raise ValueError("Chambre ???? invalide") raise ValueError("Chambre ???? invalide")
if create: if create:
return set_chambre(adherent, chbre) return set_chambre(adherent, chbre)
@ -745,7 +745,7 @@ class Dialog(proprio.Dialog):
adherent.validate_changes() adherent.validate_changes()
adherent.history_gen() adherent.history_gen()
adherent.save() 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)) raise Continue(success_cont(adherent=adherent))
(code, output) = self.handle_dialog(cont, box) (code, output) = self.handle_dialog(cont, box)