forked from python/cpython
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add 1994 Coroutine module by Tim Peters
- Loading branch information
1 parent
704925c
commit f4e13a4
Showing
4 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# Coroutine implementation using Python threads. | ||
# | ||
# Combines ideas from Guido's Generator module, and from the coroutine | ||
# features of Icon and Simula 67. | ||
# | ||
# To run a collection of functions as coroutines, you need to create | ||
# a Coroutine object to control them: | ||
# co = Coroutine() | ||
# and then 'create' a subsidiary object for each function in the | ||
# collection: | ||
# cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional, | ||
# cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list | ||
# cof3 = co.create(f3 [, arg1, arg2, ...]) | ||
# etc. The functions need not be distinct; 'create'ing the same | ||
# function multiple times gives you independent instances of the | ||
# function. | ||
# | ||
# To start the coroutines running, use co.tran on one of the create'd | ||
# functions; e.g., co.tran(cof2). The routine that first executes | ||
# co.tran is called the "main coroutine". It's special in several | ||
# respects: it existed before you created the Coroutine object; if any of | ||
# the create'd coroutines exits (does a return, or suffers an unhandled | ||
# exception), EarlyExit error is raised in the main coroutine; and the | ||
# co.detach() method transfers control directly to the main coroutine | ||
# (you can't use co.tran() for this because the main coroutine doesn't | ||
# have a name ...). | ||
# | ||
# Coroutine objects support these methods: | ||
# | ||
# handle = .create(func [, arg1, arg2, ...]) | ||
# Creates a coroutine for an invocation of func(arg1, arg2, ...), | ||
# and returns a handle ("name") for the coroutine so created. The | ||
# handle can be used as the target in a subsequent .tran(). | ||
# | ||
# .tran(target, data=None) | ||
# Transfer control to the create'd coroutine "target", optionally | ||
# passing it an arbitrary piece of data. To the coroutine A that does | ||
# the .tran, .tran acts like an ordinary function call: another | ||
# coroutine B can .tran back to it later, and if it does A's .tran | ||
# returns the 'data' argument passed to B's tran. E.g., | ||
# | ||
# in coroutine coA in coroutine coC in coroutine coB | ||
# x = co.tran(coC) co.tran(coB) co.tran(coA,12) | ||
# print x # 12 | ||
# | ||
# The data-passing feature is taken from Icon, and greatly cuts | ||
# the need to use global variables for inter-coroutine communication. | ||
# | ||
# .back( data=None ) | ||
# The same as .tran(invoker, data=None), where 'invoker' is the | ||
# coroutine that most recently .tran'ed control to the coroutine | ||
# doing the .back. This is akin to Icon's "&source". | ||
# | ||
# .detach( data=None ) | ||
# The same as .tran(main, data=None), where 'main' is the | ||
# (unnameable!) coroutine that started it all. 'main' has all the | ||
# rights of any other coroutine: upon receiving control, it can | ||
# .tran to an arbitrary coroutine of its choosing, go .back to | ||
# the .detach'er, or .kill the whole thing. | ||
# | ||
# .kill() | ||
# Destroy all the coroutines, and return control to the main | ||
# coroutine. None of the create'ed coroutines can be resumed after a | ||
# .kill(). An EarlyExit exception does a .kill() automatically. It's | ||
# a good idea to .kill() coroutines you're done with, since the | ||
# current implementation consumes a thread for each coroutine that | ||
# may be resumed. | ||
|
||
import thread | ||
import sync | ||
|
||
class _CoEvent: | ||
def __init__(self, func): | ||
self.f = func | ||
self.e = sync.event() | ||
|
||
def __repr__(self): | ||
if self.f is None: | ||
return 'main coroutine' | ||
else: | ||
return 'coroutine for func ' + self.f.func_name | ||
|
||
def __hash__(self): | ||
return id(self) | ||
|
||
def __cmp__(x,y): | ||
return cmp(id(x), id(y)) | ||
|
||
def resume(self): | ||
self.e.post() | ||
|
||
def wait(self): | ||
self.e.wait() | ||
self.e.clear() | ||
|
||
Killed = 'Coroutine.Killed' | ||
EarlyExit = 'Coroutine.EarlyExit' | ||
|
||
class Coroutine: | ||
def __init__(self): | ||
self.active = self.main = _CoEvent(None) | ||
self.invokedby = {self.main: None} | ||
self.killed = 0 | ||
self.value = None | ||
self.terminated_by = None | ||
|
||
def create(self, func, *args): | ||
me = _CoEvent(func) | ||
self.invokedby[me] = None | ||
thread.start_new_thread(self._start, (me,) + args) | ||
return me | ||
|
||
def _start(self, me, *args): | ||
me.wait() | ||
if not self.killed: | ||
try: | ||
try: | ||
apply(me.f, args) | ||
except Killed: | ||
pass | ||
finally: | ||
if not self.killed: | ||
self.terminated_by = me | ||
self.kill() | ||
|
||
def kill(self): | ||
if self.killed: | ||
raise TypeError, 'kill() called on dead coroutines' | ||
self.killed = 1 | ||
for coroutine in self.invokedby.keys(): | ||
coroutine.resume() | ||
|
||
def back(self, data=None): | ||
return self.tran( self.invokedby[self.active], data ) | ||
|
||
def detach(self, data=None): | ||
return self.tran( self.main, data ) | ||
|
||
def tran(self, target, data=None): | ||
if not self.invokedby.has_key(target): | ||
raise TypeError, '.tran target ' + `target` + \ | ||
' is not an active coroutine' | ||
if self.killed: | ||
raise TypeError, '.tran target ' + `target` + ' is killed' | ||
self.value = data | ||
me = self.active | ||
self.invokedby[target] = me | ||
self.active = target | ||
target.resume() | ||
|
||
me.wait() | ||
if self.killed: | ||
if self.main is not me: | ||
raise Killed | ||
if self.terminated_by is not None: | ||
raise EarlyExit, `self.terminated_by` + ' terminated early' | ||
|
||
return self.value | ||
|
||
# end of module |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Coroutine example: controlling multiple instances of a single function | ||
|
||
from Coroutine import * | ||
|
||
# fringe visits a nested list in inorder, and detaches for each non-list | ||
# element; raises EarlyExit after the list is exhausted | ||
def fringe( co, list ): | ||
for x in list: | ||
if type(x) is type([]): | ||
fringe(co, x) | ||
else: | ||
co.detach(x) | ||
|
||
def printinorder( list ): | ||
co = Coroutine() | ||
f = co.create(fringe, co, list) | ||
try: | ||
while 1: | ||
print co.tran(f), | ||
except EarlyExit: | ||
pass | ||
|
||
printinorder([1,2,3]) # 1 2 3 | ||
printinorder([[[[1,[2]]],3]]) # ditto | ||
x = [0, 1, [2, [3]], [4,5], [[[6]]] ] | ||
printinorder(x) # 0 1 2 3 4 5 6 | ||
|
||
# fcmp lexicographically compares the fringes of two nested lists | ||
def fcmp( l1, l2 ): | ||
co1 = Coroutine(); f1 = co1.create(fringe, co1, l1) | ||
co2 = Coroutine(); f2 = co2.create(fringe, co2, l2) | ||
while 1: | ||
try: | ||
v1 = co1.tran(f1) | ||
except EarlyExit: | ||
try: | ||
v2 = co2.tran(f2) | ||
except EarlyExit: | ||
return 0 | ||
co2.kill() | ||
return -1 | ||
try: | ||
v2 = co2.tran(f2) | ||
except EarlyExit: | ||
co1.kill() | ||
return 1 | ||
if v1 != v2: | ||
co1.kill(); co2.kill() | ||
return cmp(v1,v2) | ||
|
||
print fcmp(range(7), x) # 0; fringes are equal | ||
print fcmp(range(6), x) # -1; 1st list ends early | ||
print fcmp(x, range(6)) # 1; 2nd list ends early | ||
print fcmp(range(8), x) # 1; 2nd list ends early | ||
print fcmp(x, range(8)) # -1; 1st list ends early | ||
print fcmp([1,[[2],8]], | ||
[[[1],2],8]) # 0 | ||
print fcmp([1,[[3],8]], | ||
[[[1],2],8]) # 1 | ||
print fcmp([1,[[2],8]], | ||
[[[1],2],9]) # -1 | ||
|
||
# end of example |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Coroutine example: general coroutine transfers | ||
# | ||
# The program is a variation of a Simula 67 program due to Dahl & Hoare, | ||
# who in turn credit the original example to Conway. | ||
# | ||
# We have a number of input lines, terminated by a 0 byte. The problem | ||
# is to squash them together into output lines containing 72 characters | ||
# each. A semicolon must be added between input lines. Runs of blanks | ||
# and tabs in input lines must be squashed into single blanks. | ||
# Occurrences of "**" in input lines must be replaced by "^". | ||
# | ||
# Here's a test case: | ||
|
||
test = """\ | ||
d = sqrt(b**2 - 4*a*c) | ||
twoa = 2*a | ||
L = -b/twoa | ||
R = d/twoa | ||
A1 = L + R | ||
A2 = L - R\0 | ||
""" | ||
|
||
# The program should print: | ||
|
||
# d = sqrt(b^2 - 4*a*c);twoa = 2*a; L = -b/twoa; R = d/twoa; A1 = L + R; | ||
#A2 = L - R | ||
#done | ||
|
||
# getline: delivers the next input line to its invoker | ||
# disassembler: grabs input lines from getline, and delivers them one | ||
# character at a time to squasher, also inserting a semicolon into | ||
# the stream between lines | ||
# squasher: grabs characters from disassembler and passes them on to | ||
# assembler, first replacing "**" with "^" and squashing runs of | ||
# whitespace | ||
# assembler: grabs characters from squasher and packs them into lines | ||
# with 72 character each, delivering each such line to putline; | ||
# when it sees a null byte, passes the last line to putline and | ||
# then kills all the coroutines | ||
# putline: grabs lines from assembler, and just prints them | ||
|
||
from Coroutine import * | ||
|
||
def getline(text): | ||
for line in string.splitfields(text, '\n'): | ||
co.back(line) | ||
|
||
def disassembler(): | ||
while 1: | ||
card = co.tran(cogetline) | ||
for i in range(len(card)): | ||
co.tran(cosquasher, card[i]) | ||
co.tran(cosquasher, ';') | ||
|
||
def squasher(): | ||
while 1: | ||
ch = co.tran(codisassembler) | ||
if ch == '*': | ||
ch2 = co.tran(codisassembler) | ||
if ch2 == '*': | ||
ch = '^' | ||
else: | ||
co.tran(coassembler, ch) | ||
ch = ch2 | ||
if ch in ' \t': | ||
while 1: | ||
ch2 = co.tran(codisassembler) | ||
if ch2 not in ' \t': | ||
break | ||
co.tran(coassembler, ' ') | ||
ch = ch2 | ||
co.tran(coassembler, ch) | ||
|
||
def assembler(): | ||
line = '' | ||
while 1: | ||
ch = co.tran(cosquasher) | ||
if ch == '\0': | ||
break | ||
if len(line) == 72: | ||
co.tran(coputline, line) | ||
line = '' | ||
line = line + ch | ||
line = line + ' ' * (72 - len(line)) | ||
co.tran(coputline, line) | ||
co.kill() | ||
|
||
def putline(): | ||
while 1: | ||
line = co.tran(coassembler) | ||
print line | ||
|
||
import string | ||
co = Coroutine() | ||
cogetline = co.create(getline, test) | ||
coputline = co.create(putline) | ||
coassembler = co.create(assembler) | ||
codisassembler = co.create(disassembler) | ||
cosquasher = co.create(squasher) | ||
|
||
co.tran(coputline) | ||
print 'done' | ||
|
||
# end of example |