诛仙兽神:Twisted的Deffered就是用CPS写Python程序? - cloverprince的恶搞空间 - ItEye技术网站

来源:百度文库 编辑:九乡新闻网 时间:2024/04/26 15:35:40
2009-03-22

Twisted的Deffered就是用CPS写Python程序?

关键字: cps, twisted, 网络, 异步Twisted是个不错的Python网络应用程序框架。可以免去你写Socket的烦恼。
链接:http://twistedmatrix.com/

一个示例程序:
这个服务器程接受TCP连接,并将收到的小写字母变成大写:
Python代码 
  1. from twisted.internet.protocol import Protocol,Factory  
  2. from twisted.internet import reactor  
  3.   
  4. class Echo(Protocol):  
  5.     """ 一个Protocol类,负责一个连接。 
  6.         Twisted是基于事件的框架。建立连接,收到消息,都会引发事件,由方法处理。 """  
  7.   
  8.     def connectionMade(self):  
  9.         self.transport.write("Hello!\r\n")  
  10.         self.transport.write("Send in lowercase and I reply in uppercase.\r\n\r\n")   
  11.   
  12.     def dataReceived(self, data):  
  13.         self.transport.write(data.upper())  
  14.   
  15. class EchoFactory(Factory):  
  16.     """ 工厂用于实例化Protocol类。一个监听端口需要一个工厂。 """  
  17.     protocol = Echo  
  18.   
  19. reactor.listenTCP(8007, factory) # 监听端口。  
  20. reactor.run() # 这是让Twisted框架进入“消息循环”,不断接受消息,引发事件。  


看,我没有显式地使用socket,bind,listen,accept,send,recv,close等等系统调用。Twisted已经帮我做了这些琐碎麻烦,而每个程序都会用到,而且稍有不慎就会出错,而且还因操作系统而异的操作(当然操作系统这一层是Python语言负责屏蔽的)。当然Twisted其实连select和poll都用上了。

总之,我讨厌用socket。Twisted挺方便的。


=== Twisted的异步事件 ===

有些事情不是马上就能做完的。比如发送数据,接收数据。这很麻烦,还涉及到“非阻塞IO”。传统的方法就是用select(或poll)帮我探测哪些文件描述符可读,可写。这样一来整个程序的顺序就混乱了。整个程序必须围绕这个select来写自动机(虽然我是北邮出身,对自动机可是相当的在行,但毕竟C语言不是专业的自动机语言,用SDL或者Erlang语言来编自动机还差不多。)

Twisted引入了Deferred类。这个类的每个实例表示一个不能马上完成的动作。例如:

Python代码 
  1. # data = sock.recv(MAX_LENGTH)  # Don't do this! Program will block!!!  
  2. # uppercase_data = data.upper()  
  3. # sock.send(uppercase_data)  
  4.   
  5. # Do this (Not strict Twisted code. Just for demonstration.)  
  6. deferred_obj = Deferred(sock.recv, MAX_LENGTH)       # Instantiate a "Deferred" object  
  7. deferred_obj.addCallback(lambda data: data.upper())  # Tell it what to do when received   
  8. deferred_obj.addCallback(lambda upper_data: sock.send(upper_data)) # And then?  


神奇之处就在于,当你获得了Deferred对象的时候,你的数据并没有返回。它“正在等待被完成”。这时候,你不要去等它完成。而是告诉它:“当完成之后,把数据变成大写,然后把大写数据发送出去。”这样,你就不用等待数据到达了。你可以继续响应其他事件。这个被“推迟”的处理交给Twisted去做。


=== CPS是什么? ===

这是函数式编程中常用的伎俩,迫使程序按照一定的顺序求值。比如计算这个算数表达式:

5*8+3*6

究竟是先计算5*8,还是先计算3*6,还是都不计算,直接将两个乘法表达式带入加法呢?

如果这样写:

Python代码 
  1. def add(a,b):  
  2.     return a+b  
  3.   
  4. def mul(a,b):  
  5.     return a*b  
  6.   
  7. def prettyprint(a):  
  8.     print a  
  9.   
  10. prettyprint(add(mul(5,8),mul(3,6)))  


这种风格叫做“Direct Style”。特点是函数值通过返回值返回。

再看另一种罕见的写法:

Python代码 
  1. def add(a,b,f):   # 这里f是一个函数。add做的事就是算出结果,然后传入f做参数。  
  2.     return f(a+b)  
  3.   
  4. def mul(a,b,f):   # 这里f也是一个函数。只不过mul算乘法。  
  5.     return f(a*b)  
  6.   
  7. def prettyprint(a):    # prettyprint是唯一不带参数f的函数。  
  8.     print a  
  9.     return None  
  10.   
  11. mul(5,8, lambda p1: mul(3,6, lambda p2: add(p1,p2,prettyprint)))  


这个叫做“Continuation Passing Style”。也就是,每个函数多带一个参数,这个参数是一个函数,这个函数成为Continuation。当计算完当前函数的值之后,不是直接返回,而是将这个值传入Continuation函数中,作为参数。

最后一句太不直观了。我来解释一下:

最外层:
Python代码 
  1. mul(5,8, lambda p1: .... )  

这个函数计算5,8的积,然后将结果传入右边的函数,就是那个lambda p1:...
这个
Python代码 
  1. lambda p1: mul(3,6, lambda p2: add(p1,p2,prettyprint))  

是一个函数,接受一个参数p1。这里p1就是5和8的积。它的返回值是mul(3,6, lambda p2:add(p1,p2,prettyprint)),而其中p1是这个lanbda的参数(p2不是。p2是内层lambda的参数),因此这个(外层)lambda函数的值就是:
Python代码 
  1. mul(3,6, lambda p2: add(40,p2,prettyprint))  # 注意p1被代换成40 (40=5*8)  


这又是个mul函数。将3和6相乘,传入右边的函数:lambda p2: add(40,p2,prettyprint),作为参数。既然参数p2是3*6==18,那么,这个lambda函数的值就是
Python代码 
  1. add(40,18,prettyprint)   # 注意p2被代换成18(18=3*6)  

然后,算出40+18==58,传入prettyprint作为参数。值就是
Python代码 
  1. prettyprint(58)  

这个函数打印58,返回Null

完毕。总结:

Python代码 
  1. mul(5,8, lambda p1: mul(3,6, lambda p2: add(p1,p2,prettyprint)))  
  2. ==>   
  3. (lambda p1: mul(3,6, lambda p2: add(p1,p2,prettyprint)))(40)  # 函数调用  
  4. ==>  
  5. mul(3,6, lambda p2: add(40,p2,prettyprint))  # 注意p1被代换  
  6. ==>  
  7. (lambda p2: add(40,p2,prettyprint))(18)  # 函数调用  
  8. ==>  
  9. add(40,18,prettyprint)  # 注意p2被代换  
  10. ==>  
  11. prettyprint(58)  
  12. ==>  
  13. 显示58,返回None  



CPS有什么好处?

如上总结:CPS的函数的求值顺序是被用户确定的。这在没有过程化结构的函数式语言中非常重要。有些具有副作用的函数,必须严格控制求值顺序。

CPS有什么坏处?

不直观。不好读。容易出错。



=== Twisted里面的Deferred和CPS有什么关系? ===

看看Twisted自己的例子:

Python代码 
  1. # Read username, output from factory interfacing to web, drop connections  
  2. from twisted.internet import protocol, reactor, defer, utils  
  3. from twisted.protocols import basic  
  4. from twisted.web import client  
  5. class FingerProtocol(basic.LineReceiver):  
  6.     def lineReceived(self, user):  
  7.         # 看到了没有,这里是典型的Continuation Passing Style  
  8.         # 提示:getUser返回一个Deferred对象,可以被addCallback和addErrback  
  9.         self.factory.getUser(user  
  10.         ).addErrback(lambda _: "Internal error in server"  
  11.         ).addCallback(lambda m:  
  12.                       (self.transport.write(m+"\r\n"),  
  13.                        self.transport.loseConnection()))  
  14. class FingerFactory(protocol.ServerFactory):  
  15.     protocol = FingerProtocol  
  16.     def __init__(self, prefix): self.prefix=prefix  
  17.     def getUser(self, user):  
  18.         return client.getPage(self.prefix+user)  
  19. reactor.listenTCP(1079, FingerFactory(prefix='http://en.wikipedia.org/wiki/'))  # 我改的,原来的网址访问不同。忽略吧,和主题无关。  
  20. reactor.run()  


这就是说,把CPS的思想带入Deferred里面了。
Deferred就是一个函数,接受另一个函数(Twisted叫它Callback,我叫它Continuation)。这就是指明:得到结果以后,下一步做什么。这不是和CPS一样吗?


=== 后记 ===

我是刚刚开始学Twisted,毕业设计可能用到。但是看到这里让我突然感到特熟悉。难道说以后要在Twisted中大量使用CPS来编程?

等着瞧吧。