twisted系列教程三–开始twisted

Part 3: Our Eye-beams Begin to Twist

原文:http://krondo.com/?p=1333
作者:dave
译者:notedit
blog:www.notedit.com
时间:2011.05.24

什么也不做,the twisted way
最后我们决定重新用twisted来实现我们异步的读诗的客户端.但首先让我们写一些twisted代码找找twisted 的感觉.

下面是一个简单到屁的twisted程序,源码在basic-twisted/simple.py

from twisted.internet import reactor
reactor.run()

你可以像这样运行它:

python basic-twisted/simple.py

就像我们在第二部分所说的,twisted 是一个反应器模式的实现,包含一个反应器或者被叫做事件循环的对象,这个对象是twisted 程序的核心.在上面的程序中的第一行我们导入reactor,第二行告诉reactor去执行一个事件循环.
这个程序事实上什么也没做,你可以用Control-C停止他,否则的话他会永远执行下去.一般来说我们应该给事件循环一个或者多个文件描述符让事件循环来监测I/O,我们后面会讲到该怎么做,但现在反应器的事件循环是卡住的,记住反应器的事件循环不是一个不停循环的繁忙的循环,如果你可以监测cpu的使用情况,你可以发现这个reactor 是不耗费cpu资源的,这个反应器的事件循环会在图片五的顶部卡住,等待将要来的事件.

这个程序仍然是一个相当简单的程序,完全没有吸引力是吧? 我们接下来会做点有趣的事,不过我们已经可以得出下面的结论:

  1. twsited 的reactor 不会自动开始运行,你可以让它运行起来通过reactor.run()
  2. 反应器的循环(reactor loop)和它开始运行的地方运行在同一线程内,在这种情况下,他运行在主线程内
  3. 一但事件循环运行起来,就会永远运行下去.这时reactor受程序控制
  4. 如果没有什么事情可做,反应器的循环不耗费CPU资源
  5. reactor 不用创建,直接import进来就可以用

最后一点需要解释一下,在twisted中, reactor 是单例模式的.在一个twisted程序中只有一个reactor object,在你用import导入的时候就被创建了.如果你打开reactor 的源码,你会发现只有很少的代码,reactor 的真正实现在twisted.internet.selectreactor.

twisted 中有多种reactor 的实现.在第二部分已经提到,select 仅仅是其中的一中实现方法,也是twisted默认使用的方法.twisted中也包含其他的reactor 的实现方式,比如twisted.internet.pollreactor使用poll 的方式来实现.

如果要使用其他的reactor,你必须要在导入twisted.internet.reactor前先安装一下,你可以这样来安装pollreactor:

from twisted.internet import pollreactor
pollreactor.install()

如果你在导入twisted.internet.reactor之前没有安装其他的reactor 实现,twisted默认选择selectreactor.所以一般的不要在一个模块的上层导入reactor用来避免不经意的安装了默认的reactor,相反的,要在你要引用reactor 的时候再去导入它.
Note:自从写这些文章开始,twisted正在慢慢的转向允许多种reactor 同时存在的结构(应该是这样翻译),在计划中,一个reactor对象被传递的时候只作为一个引用而不是从一个模块中导入.
Note:不是所有的操作系统都支持poll,如果你的系统不支持,上面的例子在你的系统跑不起来

现在我们可以用pollreactor重新实现我们的第一个 twisted 程序,你可以在basic-twisted/simple-poll.py看到源码:

from twisted.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()

好吧,我们又非常成功的实现了一个什么也不做的poll 循环.
我们在以后的教程中都会使用使用twisted 的默认的reactor,为了学习twisted的话,所有的reactor都执行相同的功能.

Hello Twisted
下面让我们实现一个做点东西的twisted程序,下面的程序会打印两行消息:

def hello():
    print 'Hello from the reactor loop!'
    print 'Lately I feel like I\'m stuck in a rut.'

from twisted.internet import reactor

reactor.callWhenRunning(hello)

print 'Starting the reactor.'
reactor.run()

程序代码在 basic-twisted/hello.py,如果你运行的话,会看到以下的输出:

Starting the reactor.
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.

需要你手动杀死这个程序,因为当执行完print 之后程序会卡住.

hello 函数是在reactor 运行之后被调用的,这就意味这hello函数是被reactor调用的,所以twisted的代码必须可以调用我们的定义的函数,我们让callwhenrunning 传递一个hello函数的引用到twisted code,并触发callwhenrunning.但让必须在ractor运行之后才可以.
我们用术语callback来描述这种引用, 一个callback 是我们给twisted的一个函数的引用,twisted 会call back 在正确的时候.既然twisted 和我们的业务逻辑是分开的,reactor 和我们的业务逻辑通过callback一个函数来进行交互.
我们可以看到下面的程序看到twisted怎样和我们的程序交互的:

import traceback

def stack():
    print 'The python stack:'
    traceback.print_stack()

from twisted.internet import reactor
reactor.callWhenRunning(stack)
reactor.run()

你可以在basic-twisted/stack.py看到源代码,会输出一些像下面的东西:

The python stack:
...
reactor.run() <-- This is where we called the reactor
...
...  <-- A bunch of Twisted function calls
...
traceback.print_stack() <-- The second line in the stack function

不要在意中间的一些twisted之间的调用,注意reactor.run() 和我们的callback之间的关系

callback 是做什么的
twisted 不是唯一一个用callback 的reactor 框架, 其他的异步框架Medusaasyncore也在用,还有一个GUI 比如GTK和QT,也是基于reactor循环的.
"反应器"系统的开发者们非常爱callback,但是要考虑到下面几点:

  1. 反应堆模式是单线程的
  2. 一个reactor的框架比如twisted实现了reactor循环所以我们的代码不要再去实现了
  3. 我们的代码仍旧需要被调用来实现我们的业务逻辑
  4. 既然整个程序在单线程内,reactor循环仍需要调用我们的代码
  5. reactor不能事先知道我们哪部分代码需要被调用
考虑到上面几点,callback就是必须的了

图片六描述了callback 的时候发生了什么
null
图片六

图片六说明一些callback 的重要属性:

  1. 我们的callback 代码和twisted 循环运行在同一个线程中
  2. 当我们的callback在运行的时候,twisted 循环不运行
  3. 同上,当twisted 循环运行时,callback是不运行的
  4. reactor循环恢复,当我们的callback返回时

在一个callback中,twisted 循环可以看作是被阻塞住了.所以我们应该确保我们的callback代码不要花费很多时间.通常的,我们应该在我们的callback中避免阻塞的I/O操作.否则我们使用反应器模式的好处就不存在了.twisted 不会提供任何防范方法去阻止你的阻塞程序,我们必须确保在callback中不要做阻塞操作.不过最后你会发现,我们不用担心基本的网络I/O操作,因为我们可以让twisted去为我们实现异步通信.

其他的可能出现阻塞的操作包括从不是socket 文件描述符(比如pipe)中读写,或者等待一个子进程结束.怎样把阻塞的操作编程的非阻塞的操作跟你要完成什么有很大的关系,但是总会有twisted api 可以帮助实现它.记住,一些标准的python 函数本身就是阻塞模式的,没有方法转为非阻塞模式,比如,os.system 函数会阻塞直到子进程运行完成,他们就是那样工作的,所以在你用twisted的时候,你应该避免在你的代码中使用这些阻塞函数.
Goodbye Twisted

你可以让twisted 的reactor 停止运行通过 reactor 的stop方法.但是一但reactor 被停止它不能被重启,所以在你的程序不得不停止的时候再去调用reactor 的stop方法.

曾经在邮件列表中讨论过让reactor变为可重启动的,你可以控制reactor 什么时候开始,什么时候停止.在twisted 8.2.0中,你仅仅可以启动reactor一次

下面有一个程序,basic-twisted/countdown.py,在5秒后停止reactor:

class Countdown(object):

    counter = 5

    def count(self):
        if self.counter == 0:
            reactor.stop()
        else:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(1, self.count)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown().count)

print 'Start!'
reactor.run()
print 'Stop!'

这个程序用callLater API 注册了一个callback,这个callback作为callLater 的第二参数,第一个参数是等待多少秒然后你的callback才被执行.

twisted 是怎样在正确的实行去执行callback 的?既然这个程序没有监听任何的文件描述符,为什么也会在select循环中被卡住呢? 事实是,select 还有其他的类似的poll 也接收一个可选的timeout值,如果在一个timeout 的时间周期内没有任何一个文件描述符可以读写,select 也会返回.如果你传递给timeout 一个0值,你可以没有阻塞的监听文件描述符.
你可以把timeout 想象成一个事件循环正在等的事件(参照图片五),twisted 用timeout 来确保任何一个被定时了的callback可以在正确的时间运行.假如一个callback需要花费很长的时间,一个定时的callback会被顺延,所以twisted 的callLater 机制不能提供非常准确的时间保证.
下面是这个程序的输出

Start!
5 ...
4 ...
3 ...
2 ...
1 ...
Stop!

最后会输出一个stop表示reactor 已经退出了,现在有了一个可以自己的停止的程序了.
接招吧 (让我想起了take that 乐队)twisted
既然twisted 以callback的方法来执行我们的方法,你会想如果一个callback抛出了异常怎么办.让我们试试吧,basic-twisted/exception.py会在一个callback中抛出一个异常:

def falldown():
    raise Exception('I fall down.')

def upagain():
    print 'But I get up again.'
    reactor.stop()

from twisted.internet import reactor

reactor.callWhenRunning(falldown)
reactor.callWhenRunning(upagain)

print 'Starting the reactor.'
reactor.run()

你会看到以下输出:

Starting the reactor.
Traceback (most recent call last):
  ... # I removed most of the traceback
exceptions.Exception: I fall down.
But I get up again.

注意第二个callback仍会在第一个callback之后运行,即使我们看到了很多的异常的追踪信息.如果你把reactor.stop()注释掉的话,这个程序会仍会继续运行下去,所以reactor 会继续运行下去即使我们的一个callback抛出了异常,
网络服务器对鲁棒性要求比较高,即使在运行过程中出现了一些bug也不能让服务器down掉,不是在说我们不用去处理我们的错误,如果你感觉后面还有人支持着你,你会感觉很棒.
Poetry,please
下面我们要twisted去获取点小诗啦,在第四部分,我们会实现一个twised 的异步的客户端

发表在 python, twisted | 标签为 , | 评论关闭

twisted系列教程二–缓慢的诗

第二部分:slow poetry and the apocalypse

原文:http://krondo.com/blog/?p=1247
作者:dave
译者:notedit
时间:2011.05.19

我的假设

我假设你已经有一些基本的能力去写python程序,并且直到一些socket编程方面的额知识,如果你没有接触过socket请移步这里 socket module documention, 这个系列中的代码例子都是运行在python2.5 和twisted 8.2.0,程序如果不能正确运行请检查你的python和twisted 的版本.

获取例子代码

你可以在public git repository获取例子代码,如果你可以用git 或者其他的版本管理软件,你可以直接clone 出一份代码:

git clone git://github.com/jdavisp3/twisted-intro.git

缓慢的诗
尽管cpu比网络要快很多,但是网络还是仍旧会比你的脑袋或者眼睛快.所以你要看到cpu-network是怎样运行几乎是不可能的.我们需要的是一个slow server,可以让我们的眼睛看到变化.既然是一个server,那就让我们的server来生产两首颇具诗意的诗吧,在代码仓库的子目录中有John Donne, W.B. Yeats, 和 Edgar Allen Poe 的三首小诗,当然如果换成你自己写的,that will be nice :)
最基本的”poetry server” 的实现在 blocking-server/slowpoetry.py,你可以让他跑起来:

python blocking-server/slowpoetry.py poetry/ecstasy.txt

上面这个命令会开启一个阻塞的服务器,你可以打开这个阻塞服务器的源码看一下,你可以看到我们并没有用到twisted,只有一些简单的socket 操作.它可以在每个固定的延迟后发出固定的数量的字节,默认的它会每0.1s 发出10个字节.你可以通过–num-bytes 和 –delay 来控制时间和字节数,比如下面:

python blocking-server/slowpoetry.py --num-bytes 50 --delay 5 poetry/ecstasy.txt

当这个服务开始运行的时候,它会打印出它监听的端口.默认的,这个是一个随机的端口,你可以通过修改配置让它监听固定的端口,你可以指定它监听的端口如下:

python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt

如果你有netcat 命令的话,你可以测试这个server 通过一下的命令:

netcat localhost 10000

如果一切正常的话,你会看到一条小诗被每次输出一些字节,一旦这首小诗被完全生产出来,服务器会断开连接.

如果你看源码的话你会发现,poetry server 不仅仅是每次输出一些字节,如果你再用其他的client去连接server 的话,其他的client 必须等到第一个client被处理完才会被poetry server 处理. 现在的poetry server 确实很慢啊,慢的我蛋痛.

阻塞的client
在我们的例子中我们的客户端也是阻塞的,它可以从多个server 上一个接一个的读取内容,现在我们给我们的客户端三个任务去执行,就想图片一中的那样.首先我们先启动三个服务器,为三个不同的客户端来服务,如下:

python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt --num-bytes 30
python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt
python blocking-server/slowpoetry.py --port 10002 poetry/science.txt

接下来我们可以开启阻塞的客户端blocking-client/get-poetry.py来接收小诗了:

python blocking-client/get-poetry.py 10000 10001 10002

客户端会按照顺序的去接收小诗,只有接收完一个才会去接收下一个.你会看到一下的输出:

Task 1: get poetry from: 127.0.0.1:10000
Task 1: got 3003 bytes of poetry from 127.0.0.1:10000 in 0:00:10.126361
Task 2: get poetry from: 127.0.0.1:10001
Task 2: got 623 bytes of poetry from 127.0.0.1:10001 in 0:00:06.321777
Task 3: get poetry from: 127.0.0.1:10002
Task 3: got 653 bytes of poetry from 127.0.0.1:10002 in 0:00:06.617523
Got 3 poems in 0:00:23.065661

基本上就是图片一的文字版本.你可以看一下源代码去定位一下在接收和发送字节的时候那些地方会产生阻塞.

异步的client
下面让我们看看一个简单的异步的client,没有用twisted.先让我们运行一下, async-client/get-poetry.py:


python async-client/get-poetry.py 10000 10001 10002

你将会得到如下的输出:

Task 1: got 30 bytes of poetry from 127.0.0.1:10000
Task 2: got 10 bytes of poetry from 127.0.0.1:10001
Task 3: got 10 bytes of poetry from 127.0.0.1:10002
Task 1: got 30 bytes of poetry from 127.0.0.1:10000
Task 2: got 10 bytes of poetry from 127.0.0.1:10001
...
Task 1: 3003 bytes of poetry
Task 2: 623 bytes of poetry
Task 3: 653 bytes of poetry
Got 3 poems in 0:00:10.133169

这一次的输出结果会比较长因为每当异步的client 每一次从任何一个服务器上获取一些东西都会输出.注意,这个异步的client 会交错的执行三个任务,就像在图片三中描述的那样.

需要注意的是,异步客户端完成任务用了差不多10s,而同步的客户端需要差不多23s,现在回想一下图片三和图片四的区别.通过减少阻塞的时间,我们的异步客户端需要更少的时间.我们的异步的客户端也是会有一些阻塞,但通过交错任务来执行可以减少很多阻塞的时间.

严格的来说,我们的异步client也在执行阻塞操作:它会向stdout输出一些内容.但对我们的例子还没有太大的影响.终端其实一直在准备接收更多的print 的输出,所以print 并不会阻塞,和我们的slow 服务器来说,print语句很快.如果我们的异步程序是一个进程中管道的一部分,在处理标准的输入和输出的时候应该考虑用异步的I/O.twisted 已经提供了在处理标准输入输出时异步I/O 的支持,为了kiss 原则,这里先不用

更进一步的
让我们看一下异步client 的代码,注意异步client代码和同步client代码的区别:

  1. 异步的client会一次连接多个server,而同步的client
  2. 异步的client中的socket 通过setblocking(0)设置为了非阻塞的
  3. select 模块的selsect可以用来监听是否有socket可以提供给client 的数据
  4. 从服务器读数据的时候,异步client会尽可能的多读数据直到这个socket被阻塞,然后如果有其他的socket是可读的话就转到下一个,这意味着我们必须要记录每一首小诗传输状态

这个异步client 中最核心的部分是get_poetry 函数的上层的事件循环,这个循环要经过一下的过程:

  1. 用select监听所有打开的sockets,直到其中的一个socket有数据流可以读
  2. 如果有一个socket有数据可读,读取它
  3. 重复循环,直到所有的socket都关闭

同步的client在main 函数中也有一个循环,但是每次遍历的时候同步客户端只下载同一首小诗,直到这首小诗读完.而异步的client在每次循环的时候会读取多个小诗的片段,我们不确定在一次循环中他会读取几个小诗,或者每次读取多少字节, 这完全依赖于服务器的速度以及网络的速度.我们让select 告诉我们哪一个socket有数据可以读,然后我们就可以在不产生阻塞的情况下尽可能多的去读数据了.

假如一个同步的client一直连在一个相同的服务器上(让我们假设是三号服务器),它现在根本不需要一个外部的循环,因为get_poety函数是阻塞的,客户端会一直连在同一个server上,直到获取完整个的一首诗.而异步的客户端没有一个事件循环的话则无法进行下去,异步的客户端需要在一开始的就监听全部的socket,然后在每一次的循环中处理所能收受的所有数据.

这种用一个循环去等待事件的发生,然后去处理事件的模式我们很常见,已经形成了一种设计模式叫做:reactor pattern(反应堆模式),下面这张图比较直观的描述了反应堆模式
the reactor loop
这个循环就是一个反应器因为他在不断的等待事件的发生然后响应他们,因为这个原因也被叫做事件循环.因为”反应器”系统总是等待I/O,这些循环有时也被叫做 select loops,select call 也经常等待I/O. 在一个select loop 中,一个事件就是其中一个socket 变为可读的或者可写的.记住 select 不是等待I/O 的唯一的方法,还有其他的方法被用做等待I/O(比如epoll),他们甚至比select 的性能要好,抛开性能不说,他们都用来做同一件事情:监听一些端口,然后等待其中的一些端口可以读或者可以写.

用select来监测文件描述符来实现一个简单的非阻塞程序是可能的,在" 反应器系统中"执行一些不涉及I/O的操作也是可以的.但是在一个真正的反应器系统中,所有的工作都是I/O相关的

严格的来,咱们的异步client 不是” 反应堆模式”,因为循环的逻辑和也业务逻辑没有完全分开,他们交错在一起.一个真正的反应器模式应该让事件循环作为一个抽象来实现以下两点:

  1. 1,接收一些你要处理I/O的文件描述符
  2. 2,当任何一个文件可以读或者可以写的时候,独立的告诉你

一个非常好的反应堆模式还应实现:

  1. 可以处理各种操作系统各种奇怪的实现
  2. 提供一个非常好的抽象的实现去帮助你很容易的去应用reactor
  3. 提供各种公开的协议的实现

好吧,其实我们想说的就是twisted,一个具有鲁棒性很强,跨平台的,包罗万象的反应器模式的实现.第三部分我们将开始写一些简单的twisted 代码了.ARE YOU READY?

发表在 twisted | 标签为 , | 评论关闭

twisted系列教程一–异步编程

作者:dave
译者:notedit
日期:2011.05.18
好久没写过文章了,最近有点空闲打算翻译这个twisted系列教程,英文很烂,中文也很烂,欢迎指正
前言
最近有人在twisted邮件列表中问有没有一个可以让人快速学习twisted的文档.总体的来说:这个系列不是这样的一个文档.如果你没有很多时间或者耐心的话,这个系列的文章不太适合你.
不过,如果你对异步编程了解很少的话,相信一个简短的介绍也不让你完全明白,当然如果你是天才除外.我学习和使用twisted已经好几年了,通过这几年的学习和工作我得出的结论就是:学习twisted困难的地方就是对异步编程的理解而不是怎样去用twisted 的函数去写代码. twisted 的代码写的都很简洁和清晰,而且有很好的注释和文档,但是如果没有理解异步编程的思想而直接去读twisted 的源码的话会让你有twisted 的感觉.
这个系列的第一篇会讲异步编程,后面的章节才会降到twisted.首先让我们以一个简单的实例来说明异步编程是怎么工作的吧.
模型

为了更好的理解异步模型,我们先来回顾一下我们经常遇到的一些模型:同步模型和多线程模型.让我们假想一个程序要完成三个任务,先让我们看看同步模型是怎么来工作的,如下图,
图片一
这个是最简单的执行任务的方式,也是我们在平常写程序的时候经常用到的,完成一项工作之后再去做另外一件事情,每次只执行一项任务.
我们可以比较同步模型多线程模型,如下图:
图片二
在这种模型当中,每一个任务被分配在单独的线程当中工作,多个任务可以同时进行,这种模型下,每个任务是被认为是独立的.但是在现实中,很多时候每个线程并不是独立,在运行的过程中需要从其他的线程中去获取结果,这样就使各个进行的交互和协作变得复杂,在一个大的系统中,进程之间的交互会更复杂.
最后是我们要讲的异步模型.如下图
图片三
在异步模型中,每个任务进行交替进行,但是仍在一个进程中.异步模型会比多线程模型更简单些,因为每个任务的运行状态都是可以被我们控制的.虽然在同步模型也可以让任务交替运行,但这往往需要多个线程协作才能完成.单线程异步模式可以保证程序运行在一个线程中,即使在一个多进程系统中.
异步模型和多线程模型还有一个不同的地方是,多线程除了程序的控制之外,还受到操作系统的控制.相反的在一个异步模型的程序中,一个任务会一直运行下去,直到任务被运行完或者程序暂停这个任务而去执行令一个任务.
重要的一点是,在异步模型可以多线程模型可以很好结合起来,但在这个系列教程中我们还只涉及到异步模型.
为什么(为毛)

从上面的讲解中我们可以看到异步模型比多线程模型更简单些,因为异步模型只有一个进程而且任务的停止和运行状态是可控的.但比同步模型相比还是比较复杂,程序员必须把每一个任务分成很多步然后再有序的把他们组合起来,如果一个任务用到了令一个任务的结果,这个任务需要接受另一个任务的输出做为他自己的输入,而且这种接收的数据经常是一段一段的而不是一个整体. 你不禁要问既然异步模型和同步模型都是一个线程,他们执行相同的任务应该花费相同的时间啊,甚至比同步模型花费的时间更多,为什么要才采用异步的模型呢?
这里最少有两个原因,第一,如果多个任务中的一个任务负责实现一个人机交互接口,在等待用户输入的时候,可以让其他的任务先去执行,等用户输入时再去处理用户的输入.
所以如果说异步模型比同步模型快的话是有条件限制的,如果你的程序中会有阻塞,或者被强迫等待,异步模型会是你的选择.同步模型在有阻塞的时候的执行过程应该是这样的
图片四
在这个图中灰色的部分代表了一个任务正在等待(阻塞).  为什么一个任务会被阻塞呢? 一个经常的原因就是等待执行I/O ,传输数据. 一般来说CPU 处理数据的速度比磁盘和网络块,因此当一个同步的程序要处理很多I/O时会花费很多时间用于等待,这样的一个同步程序也被叫做”阻塞程序”
注意图片4,一个阻塞程序,有点像图片3,一个异步程序.这不是一个巧合,异步模型的设计原理就是,当其中一个任务被阻塞时,可以先去执行其他的可以执行的任务.所以一个异步程序仅仅会在没有任务可以执行的时候,所以一个异步程序也会被叫做无阻塞程序.如果一个程序中有很多阻塞的任务,异步模型可以比同步模型更高效.
和同步模型相比,异步模型在下列情况时表现更好:
1,有很多任务,经常总有一个任务可以继续执行的时候
2,这些任务中要执行很多I/O操作
3,这些任务大多都是独立的
这些情况大都描述了一个非常繁忙的web server,每一个任务代表了一次接收请求和发送结果,而这些client 请求大多都是独立的,所以一个web server 的实现一个很好的异步模型的实现,这就是twisted被叫做网络编程库.
Onward and Upward
这个系列的第一个部分就完啦,在第二部分,我们将写一些网络程序,阻塞的和非阻塞的都有,没有用twisted.让我们先体会一下一个异步的程序是怎样运行的.
发表在 twisted | 标签为 , | 评论关闭

django apps with buildout

最近一直在学习django,学了些基础之后开始找一些开源的代码学习一下,装了几个之后发现一个都没有跑起来,总会有各种各样的依赖,各种各样的错误.最后想起了以前一个朋友说起的zc.buildout,用他们的说法就是“最文明的开发方式”

Developing Django apps with zc.buildout

作者:Jacob Kaplan-Moss
翻译:notedit

django-shorturls 这个应用为例让你认识一个更文明的软件开发、发布方式

配置BUILDOUT环境

首先你建立你的app 目录结构:

django-shorturls/
    LICENSE
    README
    bootstrap.py
    buildout.cfg
    setup.py
    src/
        shorturls/
            __init__.py

“bootstrap.py“

这个是Buildout 的驱动脚本,把它放入你的包中就可以来建立buildout环境 ,你可以从http://svn.zope.org/*checkout*/zc.buildout/trunk/boottrap/bootstrap.py

这里得到它

“buildout.cfp“

buildout 的配置文件,在这里你可以配置你的buildout,需要依赖哪些包,需要怎样的配置文件等等

“setup.py“

这个就是你安装python 包的时候经常看到的了,python setup.py install  会包含一些包的说明文件 等等

然后你要做的就是运行buildout 让他产生一些我们需要的文件

$ python bootstrap.py
Creating directory '.../django-shorturls/bin'.
Creating directory '.../django-shorturls/parts'.
Creating directory '.../django-shorturls/eggs'.
Creating directory '.../django-shorturls/develop-eggs'.
Generated script '.../django-shorturls/bin/buildout'.
$ ./bin/buildout
Getting distribution for 'zc.buildout'.
Got zc.buildout 1.2.1.
Upgraded:
  zc.buildout version 1.2.1;
restarting.
Generated script '.../django-shorturls/bin/buildout'.

现在我们就建立一个了独立的python 环境,用过virtualenve的会清楚,往后你在这个app 中装的一些包就不会进入到系统路径中,避免了系统包的混乱。

django-shorturls/
    bin/
        buildout
    bootstrap.py
    buildout.cfg
    develop-eggs/
    eggs/
        setuptools-0.6c9-py2.5.egg/
        zc.buildout-1.1.1-py2.5.egg/
        zc.buildout-1.2.1-py2.5.egg/
    parts/
    src/
        shorturls

“/bin/buildout“

你可以通过它来对buildout进行修改,比如每一次修改buildout.cfg之后 你再运行一次buildout 去应用这些改变

“deveop-eggs/“

用来存放你现在正在开发的egg,就是那个你python setup.py install 安装的egg(汗啊  我很纠结的英文,大学逃课太多没好好学)

“eggs/“

这里存放的就是那些你要依赖的包了 在buildout.cfg 中配置的包,buildout 会默认装上setuptools 还有zc.buildout 的

“parts“

这个是不是特别明白 你可以认为他是来存放缓存  杂物的地方

配置你的SETUP.PY

就是简单的写一下你要写的这个app 的基本信息 如下简单demo

from setuptools import setup, find_packages

setup(
    name = "django-shorturls",
    version = "1.0",
    url = 'http://github.com/jacobian/django-shorturls',
    license = 'BSD',
    description = "A short URL handler for Django apps.",
    author = 'Jacob Kaplan-Moss',
    packages = find_packages('src'),
    package_dir = {'': 'src'},
    install_requires = ['setuptools'],
)

开始干点真正的活

前面都是在配置,现在对你要开发的egg做一些修改了  修改你的buildout.cfg 如下

[buildout]
parts = python
develop = .
eggs = django-shorturls

[python]
recipe = zc.recipe.egg
interpreter = python
eggs = ${buildout:eggs}

下面来讲一下这些配置,配置文件支持变量替换,比如这里的${buildout:eggs} 就是指[buildout] 中的eggs  咱们这里的值为django-shorturls。parts 是只你的应用要包含几部分,这里只有一个python部分 ,develop 指的是我们的eggs 的目录,`.` 这里指当前目录。下部分,[python],这里会建立一个 解释器,想想/usr/bin/python 吧 ,[python]会建立一个类似/usr/bin/python的环境,只不过[python]建立的bin/python是独立的  仅限于这个buildout环境(不知道我这样理解对不对,有能力的还是阅读原文吧,翻译到这里我都快放弃了)。

我们修改了buildout.cfg,运行bin/buildout 去应用这些改变

$ ./bin/buildout
Develop: '.../django-shorturls/.'
Getting distribution for 'zc.recipe.egg'.
Got zc.recipe.egg 1.2.2.
Installing python.
Generated interpreter '.../django-shorturls/bin/python'.

“发生了什么“

运行./bin/buildout 之后发生了什么?buildout会按照配置文件获取到你需要的依赖,到这里你已经建完你的egg了,不过还没有什么实际的内容。你可以从下面看出区别

$ /usr/bin/python
>>> import shorturls
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named shorturls
$ ./bin/python
>>> import shorturls
>>> shorturls
<module 'shorturls' from '.../django-shorturls/src/shorturls/__init__.py'

看出区别了吗  shorturls 已经加入到./bin/python 的路径当中去了,而对全局/usr/bin/python 没什么影响

包装一下测试代码

这一部分主要讲怎样用buildout 进行测试

建立一个简单的配置文件 src/shorturls/testsettings.py 内容如下:

DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/tmp/shorturls.db'
INSTALLED_APPS = ['shorturls']
ROOT_URLCONF = ['shorturls.urls']

建立一个models.py 文件

$ touch src/shorturls/models.py

告诉buildout再建立一个test环境,继续往buildout.cfp 中加入下面部分,不过别往忘了修改parts   现在的parts = python django

[django]
recipe = djangorecipe
version = 1.0.2
project = shorturls
projectegg = shorturls
settings = testsettings
test = shorturls
eggs = ${buildout:eggs}

recipe可以理解为按照哪种方式建立此部分(讲不太清楚),settings 指的是要用到的配置文件  test 是指为哪几个part 建立测试。

然后继续./bin/buildout

$ ./bin/buildout
Develop: '.../django-shorturls/.'
Installing 'djangorecipe'.
Getting distribution for 'djangorecipe'.
Got djangorecipe 0.17.1.
Installing django.
Downloading Django from: http://www.djangoproject.com/download/1.0.2/tarball/
Generated script '.../django-shorturls/bin/django'.
Generated script '.../django-shorturls/bin/test'.

bin/django 是manage.py 的一个封装  比如 bin/django syncdb | runserver  等,bin/django shell 会进入一个python shell。 bin/test  就是建立的测试环境了, 往src/shorturls/tests.py 中写入下面的代码

from django.test import TestCase

class ShortURLTests(TestCase):
    def test_environment(self):
        """Just make sure everything is set up correctly."""
        self.assert_(True)

ps:这里的TestCase 就是unittest.TestCase 的一个简单封装,运行一下./bin/tests

$ ./bin/tests
Creating test database...
.
------------------------------
Ran 1 test in 0.003s

OK
Destroying test database...

然后你就可以继续开发你的Django app 了

更完善一些

你可以更完善你的setup.py

import os
from setuptools import setup, find_packages

def read(fname):
    return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup(
    name = "django-shorturls",
    version = "1.0",
    url = 'http://github.com/jacobian/django-shorturls',
    license = 'BSD',
    description = "A short URL (rev=canonical) handler for Django apps.",
    long_description = read('README'),

    author = 'Simon Willison, Jacob Kaplan-Moss',
    author_email = 'jacob@jacobian.org',

    packages = find_packages('src'),
    package_dir = {'': 'src'},

    install_requires = ['setuptools'],

    classifiers = [
        'Development Status :: 4 - Beta',
        'Framework :: Django',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Topic :: Internet :: WWW/HTTP',
    ]
)

然后你还可以上传到CHEESESHOP 别人通过easy_install 就可以安装了。不过你想在令一个地方部署你的代码  可以直接python setup.py install 。涉及到的依赖问题都会自动帮你安装好。


发表在 python | 标签为 , | 评论关闭

twisted 简单服务器实现

最近一段时间看了一下twisted,这个框架的底层全部用c实现,效率自然不用说,但它其中的一些逻辑与你平常接触到框架会有一些不同,如果你已经习惯了其他的一些框架,看这个框架的时候会觉得很别扭,需要一些思维上的转变.

下面看一个用twisted 实现简单server的代码

from twisted.web import http
class MyRequestHandler(http.Request):
    pages = {'/':'< h1 >Home home page',
             '/test':'< h1 >test< /h1 >Test Page',}
    def process(self):
        if self.pages.has_key(self.path):
            self.write(self.pages[self.path])
        else:
            self.setResponseCode(http.NOT_FOUND)
            self.write("< h1 >not Found< /h1 >sorry, no such page.")
        self.finish()
class MyHttp(http.HTTPChannel):
    requestFactory = MyRequestHandler
class MyHttpFactory(http.HTTPFactory):
    protocol = MyHttp
if __name__ == '__main__':
    from twisted.internet import reactor
    reactor.listenTCP(8000,MyHttpFactory())
    reactor.run()

先写一个简单的demo,随着深入的学习,我会继续修改这篇文章

发表在 python | 标签为 | 评论关闭

pidgin qq分组功能实现

话说360耍流氓间接的影响了我们使用webqq, 不过由于工作上的原因还是离不开qq

于是又开始用起了linux一款不那么非常蛋同的pidgin,用起来之后比较让人恼火的是没有分组,

200 多个qq好友在一个组里面查找起来非常麻烦,于是又是一晚上的折腾,hacking 出了一个

解决办法.

经过一翻查找,我发现pidgin 的配置文件在 /home/用户名/.purple/ 下面,然后blist.xml 文件

就是存放联系人列表的文件,通过构造一些xml 文件可以达到分组的目的.

首先需要获得你的qq 的联系人列表

打开QQ菜单,选择”好友与资料”->”好友管理器”,在”所有分组”项点击右键->”导出地址簿”

(qq2008有这个功能,2010版本我没找到)

导出地址薄之后,就是写一段程序了,如下:
http://code.google.com/p/notedit-code/downloads/detail?name=qqlist.py
程序自己看吧 不得不说的是 用python处理中文简直就是一场灾难.自己慢慢体会吧

把这个程序生成的一个文件的内容 替换掉 /home/用户名/.purple/blist.xml 中

< group name = qq (你的QQ号) > XXXXXXXXXX < /group >

的部分

重启一下pidgin 是不是那个qq下面熟悉的分组

发表在 python | 标签为 | 评论关闭

抢火车票的程序

这篇文章写于十月份  现在把它转载过来

这两天帮朋友买火车票,身在帝都我早就懂得了快稳狠的道理,

虽然这样你想买到一张火车票那還是相当难的

话说在九月十九号的那一天,经过多方打听我终于知道火车开始售票的时间,

于是早早的去排队买票,

可是我還是太低估帝都的实力了,这里是帝都,一切不可能的事情在这里都可能发生,

火车票刚开始买就没了,娘里的蛋蛋啊,

我纠结了一分钟后回到公司后 开始在网上找有没有转让票的,

这一招是我去年过年回家买票的时候学到的,

现实再一次很打击人,禀着快稳狠的准则,帝都的人们把网上转让的票也抢个一个不剩

在深深的反省自身后 我写下了如下的抢火车票的代码,不敢独享。

(娘的 看谁还给我抢 我用知识的力量鄙视你一下)

#!/bin/bash
export DISPLAY=:0
URL_58='http://www.58.com/huochepiao/beijing-wuhan/'

CURRENTDIR=$( pwd )
TMPFILE="$CURRENTDIR/$$"
WGET="wget --timeout 30 --tries=1 --limit-rate=50k"

usage()
{
{
echo "first you run $(basename $0) ready"
echo "then you run $(basename $0 ) run"
echo " bula bula~~"
} >&2

}

get_ready()
{
rm -rf *.html
$WGET -O- "$URL_58" | grep -oE '[0-9]{13}' > 2.html
echo "ready"
}
get_our_tickets()
{

echo "ticket" >> test.txt
$WGET -O- "$URL_58" | grep -E -A 1 '[0-9]{13}'
| sed -e 's/--//g' -e 's/ //g' -e '/^$/d' > 3.html
cat 3.html | grep -oE '[0-9]{13}' > 4.html
cat 3.html | grep -oE '[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}'
> 5.html
paste 4.html 5.html >6.html
while read line;do
id=$( echo $line | awk '{ print $1 }' )
day=$( echo $line | awk '{print $2 }' )
if grep -qF "$id" 2.html;then
continue
else
notify-send "you get a ticket" "$day"
echo $id >> 2.html
fi
done < 6.html

}

touch "$TMPFILE"

arg=$1

if [[ -n $arg ]];then
case $arg in
ready)
get_ready
;;
run)
get_our_tickets
;;
esac
fi

此代码在ubuntu 10.04 下运行良好 需要安装libnotify

然后 chmod +x the script

然后 crontab -e

然后 */1 * * * * the path to the script

每分钟执行一次 每当有人转让票的时候 就会提醒你啦

你就等着去抢票吧 hum ~~ it's good >>>>>

源代码在这里http://code.google.com/p/notedit-code/downloads/detail?name=buyticket.sh

发表在 shell | 标签为 | 评论关闭

Python Webkit DOM Bindings

最近研究了下pythonwebkit,于是就想如果可以在python中运行js 还有操作dom 那该多好啊,于是google了一翻,发现了一些比较有用的资料(Python Webkit DOM Bindings),pythonwebkit 的开发版本已经支持dom的操作,不过需要你编译最新的源代码,这样的话你就可以获得一些额外的函数来进行dom的操作.

官方介绍的步骤:

1,git clone git://git.savannah.gnu.org/pythonwebkit.git
// 这个方法是获取pythonwebkit 的源代码,
//如果你已经安装了pythonwebkit可以先卸载掉
2,git checkout -b python_codegen
// checkout python_codegen 分支,请确定你一定要在python_codegen 分支上
//这个是以后你成功的关键 可以通过 git branch 来确定你在哪个分支上
3,git branch
//结果应该是这样的
// master
// * python_codegen
4, compile pythonwebkit
// mkdir build
// cd build
// ../autogen.sh
// ./configure
// make
// make install
5, git clone git://github.com/lkcl/pywebkitgtk.git
//获取pywebkitgtk
6, git checkout -b pythonwebkitgtk_1_1_8
// 这里非常重要,请记住一定要是 pythonwebkitgtk_1_1_8
// 我在这里浪费了很多的时间,it must be pythonwebkitgtk_1_1_8
7, compile pywebkitgtk
// ./autogen.sh, ./configure, make, make install

如果你一路很顺利的话,那祝福你,你真的是比较幸运的,去验证你是不是安装正确 :python -v ; import webkit ,如果没有错的这说明你安装正确了.但是我很不幸,花了三天才弄好.第一个错误出现是我没有获取到正确的pywebktgtk ,请记住一定要确保你用的是pythonwebkitgtk_1_1_8 分支, 第二个错误当我编译完pywebkitgtk 后,import webkit 的时候出现下面的错误


Traceback (most recent call last):
File "
", line 1, in
File "/usr/local/lib/python2.6/dist-packages/webkit/__init__.py",
line 21, in
import webkit
ImportError: /usr/local/lib/python2.6/dist-packages/webkit/webkit.so:
undefinedsymbol: webkit_init_pywebkit

之后去了他们的mail list,发现有一个叫jim 的和我遇到一样的错误,在他的帮助下解决了这个问题,问题的原因是pywebkitgtk 没有找到pythonwebkit 的包的位置

My guess is that pywebkitgtk is not looking in the right place
forthe pythonwebkit shared library.

解决办法就是在编译pywebkitgtk 之前

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig

然后重新编译一下pywebkitgtk 就可以了.

最后,谢谢 Luke 和 Jim 的帮忙,国外的一些大牛们很喜欢帮助人.

发表在 python | 标签为 , | 评论关闭

songtaste 多线程下载音乐-python 版

#!/usr/bin/python
#coding:utf-8
import re
import urllib
import urlparse
import httplib
import time
import sys
import os
from threading import Thread

songtype = {'7d99bb4c7bd4602c342e2bb826ee8777':'.wma',
           '25e4f07f5123910814d9b8f3958385ba':'.Wma',
            '51bbd020689d1ce1c845a484995c0cce':'.WMA',
            'b3a7a4e64bcd8aabe4cabe0e55b57af5':'.mp3',
        'd82029f73bcaf052be8930f6f4247184':'.MP3',
         '5fd91d90d9618feca4740ac1f2e7948f':'.Mp3'}

outputdir = '/home/pxiaohai/Music/'

class MultiThreadDown(Thread,urllib.FancyURLopener):

    def __init__(self,threadname,url,filename,ranges):
        Thread.__init__(self,name=threadname)
        urllib.FancyURLopener.__init__(self)
        self.name = threadname
        self.url = url
        self.filename = filename
        self.ranges = ranges
        self.downloaded = 0

    def run(self):

        try:
            self.downloaded = os.path.getsize(self.filename)
            print self.filename
        except OSError:
            self.downloaded = 0

        self.startpoint = self.ranges[0] + self.downloaded

        if self.startpoint >= self.ranges[1]:
            print 'part %s has been downloaded over.' % self.filename
            return
        self.oneTimeSize = 16384

        print 'task %s will download from %d to %d' %
                     (self.name,self.startpoint,self.ranges[1])
        self.addheader("Range","bytes=%d-%d" %
                          (self.ranges[0],self.ranges[1]))
        self.urlhandle = self.open(self.url)
        data = self.urlhandle.read(self.oneTimeSize)

        while data:

            filehandle = open(self.filename,'ab+')
            filehandle.write(data)
            filehandle.close()

            self.downloaded +=len(data)
            data = self.urlhandle.read(self.oneTimeSize)

class DownSong():
    def __init__(self,ms):
        self.ms = ms

    def down(self):
        global outputdir
        global songtype

        for song in ms:
            suburl = 'http://www.songtaste.com/song/%s/' % (song[0])
            songname = song[1]
            m = re.compile('playmedia1\((.+?)\)')
            f = urllib.urlopen(suburl)
            data = f.read()
            s = []
            s = m.findall(data)[-1].replace('\'','').replace('"','').replace(' ','').split(',')
            print s
            print s[1]
            songname = songname + songtype.get(s[5])
            songurl = s[6] + s[2] + songtype.get(s[5])

            try:
                url1=urllib.urlopen(songurl)
            except:
                show = u'\r %s can not be downloaded' % (songname)
                print show
                continue

            blocks = 4
            filesize = self.GetUrlFileSize(songurl)
            ranges = self.SpliteBlocks(filesize,blocks)

            print ranges

            threadname = ["thread_%d" % i for i in range(0,blocks)]
            filename =  ["tmpfiles_%d" % i for i in range(0,blocks)]

            tasks = []
            for i in range(0,blocks):
                task = MultiThreadDown(threadname[i],songurl,filename[i],ranges[i])
                task.setDaemon(True)
                task.start()
                tasks.append(task)
            time.sleep(1)
            songname = songname.decode('gb18030','replace').strip()
            songname = ''.join(songname.split())
            show = u'\r %s is starting download' % (songname)
            print show

            while self.islive(tasks):
                downloaded = sum([task.downloaded for task in tasks])
                process = downloaded/float(filesize)*100
                show = u'\rFilesize:%d Downloaded:%d Completed:%.2f%%'
                               % (filesize,downloaded,process)
                sys.stdout.write(show)
                sys.stdout.flush()
                time.sleep(0.5)

            show = u'\r %s  completed' % (songname)
            print show
            songname = outputdir + songname
            filehandle = open(songname,'wb+')
            for i in filename:
                f = open(i,'rb')
                filehandle.write(f.read())
                f.close()

                try:
                    os.remove(i)
                    print ""
                except:
                    pass

            filehandle.close()

    def GetUrlFileSize(self,url):
        urlHandler = urllib.urlopen(url)
        headers = urlHandler.info().headers
        length = 0
        for header in headers:
            if header.find('Length') != -1:
                length = header.split(':')[-1].strip()
                length = int(length)
        return length

    def SpliteBlocks(self,totalsize, blocknumber):
        blocksize = totalsize/blocknumber
        ranges = []
        for i in range(0, blocknumber-1):
            ranges.append((i*blocksize, i*blocksize +blocksize - 1))
        ranges.append(( blocksize*(blocknumber-1), totalsize -1 ))

        return ranges

    def islive(self,tasks):
        for task in tasks:
            if task.isAlive():
                return True
        return False

if __name__ == '__main__':
#    if len(sys.argv) < 1:
#        print u'please input the url'
#    else:
#        if not re.match("^https?://[^ ]+",sys.argv[0]):
#            print u'please input the url like
                       "http://www.songtaste.com/music/catsong/cat2/"'

    m = re.compile(r'(.+?)')
    #
    url = 'http://www.songtaste.com/music/catsong/cat2/'
    f = urllib.urlopen(url)
    data = f.read()

    ms = []
    ms = m.findall(data)
    ds = DownSong(ms)
    ds.down()

看一篇博文http://hi.baidu.com/volansw/blog/item/38b13ebf3da4080f18d81f71.html 自己也做一个多线程下载的例子

然后下载例子中给的那本pdf 的书 完全没问题

然后自己琢磨着做了一个 下载songtaste 上音乐的的多线程的例子

但是发现下载下来的 文件不对

具体的出错是这样的 比如 总共文件大小为 4

正常的结果应该是tmpfile_0 为 1 tmpfile_1 为1 tmpfile_2 为 1 tmpfile_3 为1

但现在的结果是

正常的结果应该是tmpfile_0 为 1 tmpfile_1 为2 tmpfile_2 为 3 tmpfile_3 为4

为什么会这样呢

于是我找来上述连接中给的例子 把url 换成 sogntaset 上的音乐url

http://224.cachefile28.rayfile.com/59f9/zh-cn/download/c4f83b63ace9a140cacf6c7273417f0a/preview.mp3

试了一下 发现用那个的例子 也会出现和我一样的错误

纠结了一个下午 和一个晚上 实在想不出是什么原因

发表在 python | 标签为 , , | 评论关闭

小sehll

取当天日期为`date +%Y%m%d`

取昨天日期为`date -d yesterday +%Y%m%d`

取两天前日期为`date -d -2day +%Y%m%d`

取上个月时间 date –date=’1 months ago’ +%Y%m

判断是不是root (( UID != 0 ))

查看文件大小 du -h –max-depth=1 或者 ls -sh

不停的显示文件的最后10行,可以用来跟踪另一个进程的输出 tail -f filename

还有一些cron程序 我们只想让在同一时间执行一个实例 可以这样实现

#!/bin/bash
if fuser $0 2> /dev/null | sed “s/\\<$$\\>//” | grep -q ‘[0-9]‘; then
echo ‘[error] Already running, exit’ 1>&2
exit 1
fi

# do something
sleep 60
还有一种方法是通过临时文件夹

#! /bin/bash

lock_file=/tmp/.$( basename $0 )
[[ -f $lock_file ]] || touch $lock_file
echo “using lock file $lock_file …”

lock()
{
exec 9< “$lock_file” } is_locked() { fuser “$lock_file” &> /dev/null
}

if is_locked; then
echo “$0 may be already running …”
exit 1
else
echo “ok, now lock and running …”
lock
sleep 30
fi
其实还是完全可以用grep -c 来实现的 具体的自己试着写吧 ~~~

发表在 shell | 评论关闭