mirror of
https://github.com/python/cpython.git
synced 2025-07-23 03:05:38 +00:00
Add 1994 Coroutine module by Tim Peters
This commit is contained in:
parent
704925c774
commit
f4e13a4563
4 changed files with 332 additions and 0 deletions
160
Demo/threads/Coroutine.py
Normal file
160
Demo/threads/Coroutine.py
Normal file
|
@ -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
|
|
@ -8,3 +8,7 @@ find.py Parallelized "find(1)" (looks for directories).
|
||||||
sync.py Condition variables primitives by Tim Peters.
|
sync.py Condition variables primitives by Tim Peters.
|
||||||
telnet.py Version of ../sockets/telnet.py using threads.
|
telnet.py Version of ../sockets/telnet.py using threads.
|
||||||
wpi.py Version of ../scripts/pi.py using threads (needs stdwin).
|
wpi.py Version of ../scripts/pi.py using threads (needs stdwin).
|
||||||
|
|
||||||
|
Coroutine.py Coroutines using threads, by Tim Peters (22 May 94)
|
||||||
|
fcmp.py Example of above, by Tim
|
||||||
|
squasher.py Another example of above, also by Tim
|
||||||
|
|
64
Demo/threads/fcmp.py
Normal file
64
Demo/threads/fcmp.py
Normal file
|
@ -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
|
||||||
|
print
|
||||||
|
|
||||||
|
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
|
104
Demo/threads/squasher.py
Normal file
104
Demo/threads/squasher.py
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue