author | Alberto Bertogli
<albertogli@telpin.com.ar> 2005-07-10 06:35:20 UTC |
committer | Alberto Bertogli
<albertogli@telpin.com.ar> 2005-07-10 06:35:20 UTC |
TODO | +19 | -0 |
adatasks.py | +209 | -0 |
contract.py | +136 | -0 |
samples/adatasks/sample1.py | +31 | -0 |
samples/adatasks/sample2.py | +43 | -0 |
samples/adatasks/sample3.py | +21 | -0 |
samples/contract/sample1.py | +28 | -0 |
diff --git a/TODO b/TODO new file mode 100644 index 0000000..fc6a286 --- /dev/null +++ b/TODO @@ -0,0 +1,19 @@ + +common +------ + +* license and stuff + + +adatasks +-------- + +* documentation +* optional timeouts + + +contract +-------- + +* documentation + diff --git a/adatasks.py b/adatasks.py new file mode 100644 index 0000000..99fbea9 --- /dev/null +++ b/adatasks.py @@ -0,0 +1,209 @@ + +""" +adatasks - An ada-alike tasks implementation for Python +Alberto Bertogli (albertogli@telpin.com.ar) +3/Jul/2005 +""" + +import thread, threading + +class EntryQueue: + "Queue of pending entries to a task." + + def __init__(self, mutex): + self.lock = threading.Condition(mutex) + self.queue = [] + + def inqueue(self, entry): + for e, params in self.queue: + if e == entry: + return True + return False + + def isempty(self): + if self.queue: + return False + return True + + def push(self, entry, params): + self.queue.append((entry, params)) + + def pop(self, entry): + for e, params in self.queue: + if e == entry: + self.queue.remove((e, params)) + return params + raise 'NeverGetHere!' + + def popany(self): + if self.queue: + return self.queue.pop(0) + return None + + +# entry queue dictionary, indexes queues per task +entrydb = {} +entrydb_lock = threading.Lock() + +def create_queue(name): + "Creates a queue for the task with the given name." + entrydb_lock.acquire() + if not entrydb.has_key(name): + entrydb[name] = EntryQueue(entrydb_lock) + entrydb_lock.release() + +def get_queue(name): + "Gets a tasks queue." + entrydb_lock.acquire() + q = entrydb[name] + entrydb_lock.release() + return q + + +# list of tasks to launch +tolaunch = [] +tolaunch_lock = threading.Lock() + +# list of running tasks: running[task] = running_count +running = {} +running_lock = threading.Condition() + + +class Task: + "A task." + + def __init__(self, f): + self.f = f + self.fname = f.func_name + create_queue(self.fname) + + def __run(self, *params): + try: + self.f(*params) + finally: + running_lock.acquire() + running[self.fname] -= 1 + running_lock.notifyAll() + running_lock.release() + + def __call__(self, *params): + running_lock.acquire() + if not running.has_key(self.fname): + running[self.fname] = 1 + else: + running[self.fname] += 1 + running_lock.release() + + thread.start_new_thread(self.__run, params) + + def __getattr__(self, entry): + if entry in dir(self.f): + # return f's attribute if one exists with the given + # name + return self.f.__getattribute__(entry) + + # write the entry wrapper + def wrapper(*params): + q = get_queue(self.fname) + q.lock.acquire() + q.push(entry, params) + q.lock.notifyAll() + q.lock.release() + return + + # return the wrapper + return wrapper + + def accept(self, entry): + q = get_queue(self.fname) + q.lock.acquire() + while not q.inqueue(entry): + q.lock.wait() + ret = q.pop(entry) + q.lock.release() + return ret + + def select(self, *entries): + q = get_queue(self.fname) + + q.lock.acquire() + if not entries: + r = q.popany() + while r == None: + q.lock.wait() + r = q.popany() + q.lock.release() + return r + else: + loop = True + while loop: + for e in entries: + if q.inqueue(e): + loop = False + break + else: + q.lock.wait() + r = q.pop(e) + q.lock.release() + return (e, r) + + +def task(f): + "Task decorator" + task = Task(f) + tolaunch_lock.acquire() + tolaunch.append(task) + tolaunch_lock.release() + return task + + +def launch(): + "Launch pending tasks" + global tolaunch + tolaunch_lock.acquire() + for t in tolaunch: + t() + tolaunch = [] + tolaunch_lock.release() + + +def wait_for_task(task): + "Wait for the given task to end running" + running_lock.acquire() + while not (running.has_key(task.fname) and running[task.fname] == 0): + running_lock.wait() + running_lock.release() + + +def wait_for_all(): + "Wait for all tasks to end running" + running_lock.acquire() + while True: + for t in running.keys(): + if running[t] != 0: + break + else: + break + running_lock.wait() + running_lock.release() + + + +# +# Asynchronous functions, a simple and obvious idea which might be useful for +# some applications. If you want to see formal documentation about this, check +# this out: +# http://research.microsoft.com/Users/luca/Papers/Polyphony%20(TOPLAS).pdf +# (the paper is about much more intresting things, but mentions stuff like +# this in detail). +# +def async(f): + """Asynchronous decorator. It makes function calls asynchronous, they + will run in a new independant thread and you can't get their return + value or wait for them; if you need any of those things use the ada + tasks, this is just too simple.""" + def newf(*args, **kwargs): + thread.start_new_thread(f, args, kwargs) + return newf + + diff --git a/contract.py b/contract.py new file mode 100644 index 0000000..afa20a0 --- /dev/null +++ b/contract.py @@ -0,0 +1,136 @@ + + +""" +Design by contract decorators for Python +Alberto Bertogli (albertogli@telpin.com.ar) +8/Jul/2005 +------------------------------------------- + +This module provides a set of three decorators to implement design by +contract, using pre and post functions embedded inside the function body, +like: + +@contract +def f(x): + @precond(f) + def pre(x): + assert x > 0 + + @postcond(f) + def post(ret, x): + assert ret > 0 + + return (2 * x) / x + + +The precondition will run as soon as it gets defined, so it's wise to put it +on top of the function definition. +The postcondition can be anywhere. +You can omit either or both, at your discresion. + +The precondition function takes the same parameters as the function; the +postcondition function takes the returned value first, and then all the +function parameters. + + +Expressing the conditions as independant functions is trivial, and this module +doesn't implement it. Putting them inside the function have some drawbacks, +most notably it's harder to reuse them, and it's not really that different +from open-coding them directly in the function. However, it can be more +comfortable if you prefer looking at everything inside the same function body. +""" + + +def contract(f): + "Decorator for functions that will support DBC." + + class Wrapper: + "This class will wrap around the function" + def __init__(self): + # function in question + self.f = f + + # post-condition function to run + self.__atexit = None + + # arguments passed to f() + self.args = [] + self.kwargs = {} + + def put_post(self, postfun): + "Register a post-condition function" + self.__atexit = postfun + + def run_pre(self, prefun): + "Run a pre-condition function" + prefun(*self.args, **self.kwargs) + + def __call__(self, *args, **kwargs): + "Emulate a call to the function." + # save the arguments first + self.args = args + self.kwargs = kwargs + + # run the function; here the precondition will be ran + # by its decorator calling run_pre(), and put_post() + # will be called by the postcondition decorator to + # register the postcondition function + r = self.f(*args, **kwargs) + + # if we have a postcondition, run it + if self.__atexit: + self.__atexit(r, *args, **kwargs) + return r + return Wrapper() + + +def precond(pfun): + """Decorator for the precondition function, takes the parent function + as the first parameter, and returns the decorator to use.""" + def mkdeco(prefun): + # run the precondition function + pfun.run_pre(prefun) + return prefun + return mkdeco + + +def postcond(pfun): + """Decorator for the postcondition function, takes the parent function + as the first parameter, and returns the decorator to use.""" + def mkdeco(postfun): + # register the postcondition function + pfun.put_post(postfun) + return postfun + return mkdeco + + +# +# Example! +# + +if __name__ == '__main__': + + @contract + def f(a): + "My beautiful function." + + @precond(f) + def pre(a): + "Precondition." + print 'pre', a + + @postcond(f) + def post(ret, a): + "Postcondition." + print 'post', ret, a + + print 'f', a + return 2 * a + + + print '* run f' + r = f(26) + print '* f returned', r + print '* exit f' + + diff --git a/samples/adatasks/sample1.py b/samples/adatasks/sample1.py new file mode 100644 index 0000000..c00dbc8 --- /dev/null +++ b/samples/adatasks/sample1.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +import time +import adatasks + +@adatasks.task +def mostrador(): + print 'el mostrador!' + while True: + entry, params = mostrador.select() + print entry, params + if entry == 'quit': + break + print 'chau mostrador!' + + +def main(): + mostrador() + adatasks.launch() + mostrador.dale('arrancamos') + mostrador.mostra('mostrame') + mostrador.mostra() + mostrador.mostra('otra mas') + mostrador.quit('chau') + mostrador.quit('chau') + adatasks.wait_for_task(mostrador) + #adatasks.wait_for_all() + +if __name__ == "__main__": + main() + diff --git a/samples/adatasks/sample2.py b/samples/adatasks/sample2.py new file mode 100644 index 0000000..855c28e --- /dev/null +++ b/samples/adatasks/sample2.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +import time +import adatasks + +@adatasks.task +def f(): + print 'launch f' + params = f.accept("entry1") + print params[0] + + params = f.accept("entry2") + print params + + #raise "err" + + entry, params = f.select("entry3", "entry4") + print entry, params + + + +def main(): + adatasks.launch() + f.entry1('Hello') + f.entry2() + f.entry3('World') + + f.entry1('Vamos') + f.entry2('de') + f.entry3('nuevo') + +if __name__ == "__main__": + main() + f() + + print 'wait for f' + adatasks.wait_for_task(f) + + print 'wait for all' + adatasks.wait_for_all() + + print 'bye' + diff --git a/samples/adatasks/sample3.py b/samples/adatasks/sample3.py new file mode 100644 index 0000000..dc87925 --- /dev/null +++ b/samples/adatasks/sample3.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import time +import adatasks + + +@adatasks.async +def f(arg1, kwarg = 23, sleep = 0): + time.sleep(sleep) + print sleep, arg1, kwarg + +def main(): + f(26, sleep = 0.2) + f(kwarg = 34, arg1 = 23, sleep = 0.1) + f(22, sleep = 0) + print 'main!' + + +if __name__ == "__main__": + main() + time.sleep(2) diff --git a/samples/contract/sample1.py b/samples/contract/sample1.py new file mode 100644 index 0000000..80c01e1 --- /dev/null +++ b/samples/contract/sample1.py @@ -0,0 +1,28 @@ + +from contract import * + + +@contract +def f(a): + "My beautiful function." + + @precond(f) + def pre(a): + "Precondition." + print 'pre', a + + @postcond(f) + def post(ret, a): + "Postcondition." + print 'post', ret, a + + print 'f', a + return 2 * a + + +print '* run f' +r = f(26) +print '* f returned', r +print '* exit f' + +