Python multiprocessing 使用手记[3] – 关于Queue

Posted on 2012年5月14日 16:05

继续讨论Python multiprocessing,这次讨论的主要内容是mp库的核心组件之一的Queue。

Queue是mp库当中用来提供多进程对象交换的方式。对象交换和上一部分当中提到的对象共享都是使多个进程访问同一个对象的方式,两者的区别就是,对象共享是多个进程访问同一个对象,对象交换则是将对象从一个进程传输的另一个进程。

multiprocessing当中的Queue使用方式和Python内置的threading.Queue对象很像,它支持一个put操作,将 对象放入Queue,也支持一个get操作,将对象从Queue当中读出。和threading.Queue不同的是,mp.Queue默认不支持 join()和task_done操作,这两个支持需要使用mp.JoinableQueue对象。

由于Queue对象负责进程之间的对象传输,因此第一个问题就是如何在两个进程之间共享这个Queue对象本身。在上一部分所言的三种共享方式当 中,Queue对象只能使用继承(inheritance)的方式共享。这是因为Queue本身基于unix的Pipe对象实现,而Pipe对象的共享需 要通过继承。因此,在一个典型的应用实现模型当中,应该是父进程创建Queue,然后创建子进程共享该Queue,由父进程和子进程分别读写。例如下面的 这个例子:

import multiprocessing
 
q = multiprocessing.Queue()
 
def reader_proc():
    print q.get()
 
reader = multiprocessing.Process(target=reader_proc)
reader.start()
 
q.put(100)
reader.join()

另一种实现方式是父进程创建Queue,创建多个子进程,有的子进程读Queue,有的子进程写Queue,例如:

import multiprocessing
 
q = multiprocessing.Queue()
 
def writer_proc():
    q.put(100)
 
def reader_proc():
    print q.get()
 
reader = multiprocessing.Process(target=reader_proc)
reader.start()
writer = multiprocessing.Process(target=writer_proc)
writer.start()
 
reader.join()
writer.join()

由于使用继承的方式共享Queue,因此代码当中并没有明显的传输Queue对象本身的代码,看起来似乎只要将multiprocessing当中 的对象换成threading当中的对象,程序仍然能够工作。反之,拿到一个现有的多线程程序,是不是将threading改成 multiprocessing就可以工作呢?也许可以,但是更可能的情况是你会遇到很多问题。

第一个问题就是mp的Queue需要考虑多进程之间的对象传输,因此所传输的对象必须是可以pickle的。否则,在Queue的put操作上会抛出PicklingError。

其他的一些差异表现在一些技术细节上,这些不是任何高层逻辑可以抽象掉的,不知道这些差异会导致一些潜在的错误,例如死锁。在总结这些潜在的犯错的 可能的同时,我们会简单看一下mp当中Queue的实现方式,以便能够方便的理解为什么会有这样的行为。这些实现问题仅仅针对Linux,Windows 上面的实现和出现的问题在这里不涉及。

mp.Queue建构在系统的Pipe之上,但是实际上进程并不是直接将对象写入到Pipe里面,而是先写入一个本地的buffer,再由一个专门 的feed线程将其放入Pipe当中。读取端则是直接从Pipe当中读出对象。之所以有这样一个feed线程,是为了能够提供Queue接口函数所需要的 put的超时控制。但是由于这个feed线程的存在,mp.Queue提供了几个额外的函数来控制它,一个函数close来停止该线程,以及 join_thread来join该线程。close同时负责把所有在buffer当中的对象刷新到Pipe当中。

但是这个feed线程也是个麻烦制造者,为了保证所有被放入Queue的东西最终都能够到达另外一端的进程,mp库注册了一个atexit的处理函 数,用来在进程退出的时候自动close并且join该feed线程。这个join动作带来了很多问题,比如潜在的死锁。考虑下面一种状况:一个父进程创 建了两个子进程,一个子进程读,另一个子进程写。当需要停止这些进程的时候,父进程如果先把读进程结束,但是同时写进程已经将太多的对象写入Queue, 导致后继的对象等待在buffer当中,则这个进程将无法终止,因为atexit的处理函数等待把所有buffer当中的对象放入Pipe,但是Pipe 已经满了,然后陷入了死锁。

有人可能会问,那只要保证总是按照数据流的顺序来停止进程不就行。问题是在很多复杂的系统流程当中,可能存在一个环形的数据流,这种情况下,无论按照什么顺序停止进程,终究有一个进程可能陷入这种情景当中。

幸运的是,Queue对象还提供了一个成员函数cancel_join_thread,这个函数可以使得在进程停止的时候不进行join操作,这样 可以避免死锁,代价就是这个时候尚未刷新到Pipe当中的对象都会丢失。鉴于即使调用了join_thread,残留在Pipe当中的对象仍然可能丢失, 所以一旦选择使用mp的Queue对象,就不要假设不会在流程当中丢对象了。

另外一个可能的方案是使用mp库当中的SimpleQueue对象。这个对象在文档当中没有提及,但是在multiprocessing.queue模块当中有定义。这个对象就是去掉了buffer的Queue对象,因此可能能够避免上面说的问题的。但是SimpleQueue没有提供put和get的超时处理,两个动作都是阻塞的。

除了使用multiprocessing.Queue,还可以使用multiprocessing.Pipe进行通信。mp.Pipe是Queue 的底层结构,但是没有feed线程和put/get的超时控制。一定程度上和SimpleQueue很像。需要注意的是Pipe带有一个参数 duplex,当设置为True(默认)的时候,Pipe并不是使用系统的pipe来实现,而是通过socketpair,即Unix Domain Socket来实现。这个和pipe相比有些微的性能差异。

另外一个使用Queue的方式不是mp库内置的。这种方式使用上一篇文章当中提到的server process的方式来共享一个Queue对象。这个Queue对象实际上在server process当中,所有的子进程通过socket连接到server process获取该Queue的代理对象进行操作。说到这有人会想起来mp库有一个内置的SyncManager对象, 可以通过multiprocess.Manager函数获取到,通过该对象的Queue方法可以获取一个Queue的代理对象。不幸的是,这个方法不是正 确的获取Queue的方式,原因正如上一篇文章所说,SyncManager.Queue方法的每次调用获取到的是一个新建对象的代理对象,而不是一个共 享对象。正确的使用server process当中的Queue的方式是:

共同部分:

import multiprocessing.managers as mpm
import Queue
 
class SharedQueueManager(mpm.BaseManager): pass
q = Queue.Queue()
SharedQueueManager.register('Queue', lambda: q)

服务进程:

mgr = SharedQueueManager(address=('', 12345))
server = mgr.get_server()
server.serve_forever()

客户进程:

mgr = SharedQueueManager(address=('localhost', 12345))
mgr.connect()
q = mgr.Queue() # 这里q就是共享的Queue对象的代理对象

这种方式比起mp库内置的Queue,有一些性能上的影响,因为毕竟牵涉到多次网络通讯,但是带来的好处是没有feed线程带来的一系列问题,而且 理论上不会存在丢数据的问题,除非server process崩溃。但是正如上一篇所说,server process本身就不是很靠谱的,因此这里也只是“理论上”不会丢数据而已。

说到性能,这里就列两个性能数据,以前在twitter上面提到过的(这两个连接无法访问的请联系我):

操作对象为 pickle后512字节的对象,通过proxy操作Queue的性能大约是7000次/秒(本机)或1100次/秒(多机),如果使用 multiprocessing.Queue,效率可达54000次/秒。

Python multiprocessing 使用手记[2] – 跨进程对象共享

Posted on 2012年5月14日 16:05

继续写关于Python multiprocessing的使用手记,继上次的进程模型之后,这次展开讨论一下multiprocessing当中的跨进程对象共享的问题。

在mp库当中,跨进程对象共享有三种方式,第一种仅适用于原生机器类型,即python.ctypes当中的类型,这种在mp库的文档当中称为shared memory方式,即通过共享内存共享对象;另外一种称之为server process,即有一个服务器进程负责维护所有的对象,而其他进程连接到该进程,通过代理对象操作服务器进程当中的对象;最后一种在mp文档当中没有单独提出,但是在其中多次提到,而且是mp库当中最重要的一种共享方式,称为inheritance,即继承,对象在父进程当中创建,然后在父进程是通过multiprocessing.Process创建子进程之后,子进程自动继承了父进程当中的对象,并且子进程对这些对象的操作都是反映到了同一个对象。

这三者共享方式各有特色,在这里进行一些简单的比较。

首先是共享方式所应对的对象类型,看这个表:

共享方式 支持的类型
Shared memory ctypes当中的类型,通过RawValue,RawArray等包装类提供
Inheritance 系统内核对象,以及基于这些对象实现的对象。包括Pipe, Queue, JoinableQueue, 同步对象(Semaphore, Lock, RLock, Condition, Event等等)
Server process 所有对象,可能需要自己手工提供代理对象(Proxy)

这个表总结了三种不同的共享方式所支持的类型,下面一个个展开讨论。

其中最单纯简单的就是shared memory这种方式,只有ctypes当中的数据类型可以通过这种方式共享。由于mp库本身缺少命名的机制,即在一个进程当中创建的对象,无法在另外一 个进程当中通过名字来引用,因此,这种共享方式依赖于继承,对象应该由父进程创建,然后由子进程引用。关于这种机制的例子,可以参见Python文档当中的例子 Synchronization types like locks, conditions and queues,参考其中的test_sharedvalues函数。

然后是继承方式。首先关于继承方式需要有说明,继承本质上并不是一种对象共享的机制,对象共享只是其副作用。子进程从父进程继承来的对象并不一定是共享的。继承本质上是父进程fork出的子进程自动继承父进程的内存状态和对象描述符。因此,实际上子进程复制了 一份父进程的对象,只不过,当这个对象包装了一些系统内核对象的描述符的时候,拷贝这个对象(及其包装的描述符)实现了对象的共享。因此,在上面的表当 中,只有系统内核对象,和基于这些对象实现的对象,才能够通过继承来共享。通过继承共享的对象在linux平台上没有任何限制,但是在Windows上面 由于没有fork的实现,因此有一些额外的限制条件,因此,在Windows上面,继承方式是几乎无法用的。

最后就是Server Process这种方式。这种方式可以支持的类型比另外两种都多,因为其模型是这样的:

server process模型

server process模型

在这个模型当中,有一个manager进程,负责管理实际的对象。真正的对象也是在manager进程的内存空间当中。所有需要访问该对象的进程都 需要先连接到该管理进程,然后获取到对象的一个代理对象(Proxy object),通常情况下,这个代理对象提供了实际对象的公共函数的 代理,将函数参数进行pickle,然后通过连接传送到管理进程当中,管理进程将参数unpickle之后,转发给相应的实际对象的函数,返回值(或者异 常)同样经过管理进程pickle之后,通过连接传回到客户进程,再由proxy对象进行unpickle,返回给调用者或者抛出异常。

很明显,这个模型是一个典型的RPC(远程过程调用)的模型。因为每个客户进程实际上都是在访问manager进程当中的对象,因此完全可以通过这个实现对象共享。

manager和proxy之间的连接可以是基于socket的网络连接,也可以是unix pipe。如果是使用基于socket的连接方式,在使用proxy之前,需要调用manager对象的connect函数与远程的manager进程建 立连接。由于manager进程会打开端口接收该连接,因此必要的身份验证是需要的,否则任何人都可以连上manager弄乱你的共享对象。mp库通过 authkey的方式来进行身份验证。

在实现当中,manager进程通过multiprocessing.Manager类或者BaseManager的子类实现。 BaseManager提供了函数register注册一个函数来获取共享对象的proxy。这个函数会被客户进程调用,然后在manager进程当中执 行。这个函数可以返回一个共享的对象(对所有的调用返回同一个对象),或者可以为每一个调用创建一个新的对象,通过前者就可以实现多个进程共享一个对象。 关于这个的用法可以参考Python文档当中的例子“Demonstration of how to create and use customized managers and proxies”。

典型的导出一个共享对象的代码是:

ObjectType object_
class ObjectManager(multiprocessing.managers.BaseManager): pass
ObjectManager.register("object", lambda: object_)

注意上面介绍proxy对象的时候,我提到的“公共函数”四个字。每个proxy对象只会导出实际对象的公共函数。这里面有两个含义,一个是“公 共”,即所有非下划线开头的成员,另一个是“函数”,即所有callable的成员。这就带来一些限制,一是无法导出属性,二是无法导出一些公共的特殊函 数,例如__get__, __next__等等。对于这个mp库有一套处理,即自定义proxy对象。首先是BaseManager的register可以提供一个 proxy_type作为第三个参数,这个参数指定了哪些成员需要被导出。详细的使用方法可以参见文档当中的第一个例子。

另外manager还有一些细节的问题需要注意。由于Proxy对象不是线程安全的,因此如果需要在一个多线程程序当中使用proxy,mp库会为 每个线程创建一个proxy对象,而每个proxy对象都会对server process创建一个连接,而manager那边对于每个连接都创建一个单独的线程来为其服务。这样带来的问题就是,如果客户进程有很多线程,很容易会 导致manager进程的fd数目达到ulimit的限制,即使没有达到限制,也会因为manager进程当中有太多线程而严重影响manager的性 能。解决方案可以是一个进程内cache,只有一个单独的线程可以创建proxy对象访问共享对象,其余线程只能访问该进程当中的cache。

一旦manager因为达到ulimit限制或者其他异常,manager会直接退出,遗憾的是,这时候已经建立的proxy会试图重新连接 manager – 但是它已经不存在了。这个会导致客户进程hang在对proxy的函数调用上,这个时候,目前除了杀掉进程没有找到别的办法。

另外proxy使用socket的方式比较tricky,因此和内置的socket库有很多冲突,比如socket.setdefaulttimeout(Python Issue 6056 )。在setdefaulttimeout调用了之后,进程当中所有通过socket模块建立的socket都是被设置为unblock模式的,但是mp 库并不知道这一点,而且它总是假设socket都是block模式的,于是,一旦调用了setdefaulttimeout,所有对于proxy的函数调 用都会抛出OSError,错误代码为11,错误原因是非常有误导性的“Resource temporarily unavailable”,实际上就是EAGAIN。这个错误可以通过我提供的一个patch来补救(这个patch当中还包含其他的一些修复,所以请自行查看并修改该patch)。

由于以上的一些原因,server process模式作为一个对象的共享模式,能够提供最为灵活的共享方式,但是也有最多的问题。这个在使用过程当中就靠自己去衡量了。目前我们的系统对于 数据可靠性方面要求不高,丢失数据是可以接受的,但是也只用这种模式来维护统计值,不敢用来维护更多的东西。

关于跨进程共享对象的问题就写到这里,后面内容待续……

Python multiprocessing库使用手记(引子)

Posted on 2012年5月14日 16:04

前段时间在做的一个Python项目,需要实现一个后台服务程序,程序流程比较复杂,而且可能经常变动,但是如果把整个流程切分成一些步骤,每个步骤有自己的输入输出和处理。只要将他们的输入输出接在一起,进行不同的组合就可以实现常见的流程变动。

使用多进程的原因是考虑到Python的全局解释器锁(Global Interceptor Lock, GIL)。由于GIL的存在,在CPU密集型的程序当中,使用多线程并不能有效地利用多核CPU的优势,因为一个解释器在同一时刻只会有一个线程在执行。要想尽可能利用多核CPU并发,多进程是必需的。

引入多进程就带来几个问题:首先是这种设计方案需要一个类似于UNIX Pipe的底层结构,说的更确切一点就是message queue,操作类似于Python的Queue.Queue。另一个问题就是需要一个进程间信息共享的基础设施。

先说Queue,这个Queue需要满足下面一些需求,优先级递减:

  • 轻量 – 不需要依赖数据库等比较重型的程序。代价就是丢点东西没关系;
  • 跨进程 – 读端和写端可以不在一个进程当中;
  • 支持多进多出 – 读端和写端都要支持多个进程;
  • 稳定和高效 – 这个就不用多说了吧;
  • 有Python的API,再不济要有C的API可以让我写Python的binding。

另外是进程间共享的设施,系统shm是迫不得已使用的,因为要自己处理Pickling和Unpickling,以及一些杂七杂八的竞争条件问题。有建构在上面的现成的库最好。

最终出于轻量的考虑,选定了Python的multiprocessing库,而没有用IPC MQ 或 RabbitMQ。这个库在Python2.6被引入到标准库当中,有为2.5提供的backport。因为我们的系统使用的是2.5版本,因此我们使用的其实是2.5的backport。这个实现除了一些细枝末节的地方之外,核心实现和2.6标准库当中是完全一样的。

在使用过程当中,遇到了挺多的问题,在这里一一记录。

Python调试技术

Posted on 2012年5月10日 15:25

使用 pdb 进行调试

pdb 是 python 自带的一个包,为 python 程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。pdb 提供了一些常用的调试命令,详情见表 1。


表 1. pdb 常用命令


命令 解释
break 或 b 设置断点 设置断点
continue 或 c 继续执行程序
list 或 l 查看当前行的代码段
step 或 s 进入函数
return 或 r 执行代码直到从当前函数返回
exit 或 q 中止并退出
next 或 n 执行下一行
pp 打印变量的值
help 帮助

下面结合具体的实例讲述如何使用 pdb 进行调试。


清单 1. 测试代码示例


				
import pdb 
 a = "aaa"
 pdb.set_trace() 
 b = "bbb"
 c = "ccc"
 final = a + b + c 
 print final
			

开始调试:直接运行脚本,会停留在 pdb.set_trace() 处,选择 n+enter 可以执行当前的 statement。在第一次按下了 n+enter 之后可以直接按 enter 表示重复执行上一条 debug 命令。


清单 2. 利用 pdb 调试


				
[root@rcc-pok-idg-2255 ~]#  python epdb1.py 
 > /root/epdb1.py(4)?() 
 -> b = "bbb"
 (Pdb) n 
 > /root/epdb1.py(5)?() 
 -> c = "ccc"
 (Pdb) 
 > /root/epdb1.py(6)?() 
 -> final = a + b + c 
 (Pdb) list 
  1     import pdb 
  2     a = "aaa"
  3     pdb.set_trace() 
  4     b = "bbb"
  5     c = "ccc"
  6  -> final = a + b + c 
  7     print final 
 [EOF] 
 (Pdb) 
 [EOF] 
 (Pdb) n 
 > /root/epdb1.py(7)?() 
 -> print final 
 (Pdb)
			

退出 debug:使用 quit 或者 q 可以退出当前的 debug,但是 quit 会以一种非常粗鲁的方式退出程序,其结果是直接 crash。


清单 3. 退出 debug


				
[root@rcc-pok-idg-2255 ~]#  python epdb1.py 
 > /root/epdb1.py(4)?() 
 -> b = "bbb"
 (Pdb) n 
 > /root/epdb1.py(5)?() 
 -> c = "ccc"
 (Pdb) q 
 Traceback (most recent call last): 
  File "epdb1.py", line 5, in ? 
    c = "ccc"
  File "epdb1.py", line 5, in ? 
    c = "ccc"
  File "/usr/lib64/python2.4/bdb.py", line 48, in trace_dispatch 
    return self.dispatch_line(frame) 
  File "/usr/lib64/python2.4/bdb.py", line 67, in dispatch_line 
    if self.quitting: raise BdbQuit 
 bdb.BdbQuit
			

打印变量的值:如果需要在调试过程中打印变量的值,可以直接使用 p 加上变量名,但是需要注意的是打印仅仅在当前的 statement 已经被执行了之后才能看到具体的值,否则会报 NameError: < exceptions.NameError … ....> 错误。


清单 4. debug 过程中打印变量


				
[root@rcc-pok-idg-2255 ~]#  python epdb1.py 
 > /root/epdb1.py(4)?() 
 -> b = "bbb"
 (Pdb) n 
 > /root/epdb1.py(5)?() 
 -> c = "ccc"
 (Pdb) p b 
'bbb'
 (Pdb) 
'bbb'
 (Pdb) n 
 > /root/epdb1.py(6)?() 
 -> final = a + b + c 
 (Pdb) p c 
'ccc'
 (Pdb) p final 
 *** NameError: <exceptions.NameError instance at 0x1551b710 > 
 (Pdb) n 
 > /root/epdb1.py(7)?() 
 -> print final 
 (Pdb) p final 
'aaabbbccc'
 (Pdb)
			

使用 c 可以停止当前的 debug 使程序继续执行。如果在下面的程序中继续有 set_statement() 的申明,则又会重新进入到 debug 的状态,读者可以在代码 print final 之前再加上 set_trace() 验证。


清单 5. 停止 debug 继续执行程序


				
[root@rcc-pok-idg-2255 ~]#  python epdb1.py 
 > /root/epdb1.py(4)?() 
 -> b = "bbb"
 (Pdb) n 
 > /root/epdb1.py(5)?() 
 -> c = "ccc"
 (Pdb) c 
 aaabbbccc
			

显示代码:在 debug 的时候不一定能记住当前的代码块,如要要查看具体的代码块,则可以通过使用 list 或者 l 命令显示。list 会用箭头 -> 指向当前 debug 的语句。


清单 6. debug 过程中显示代码


				
[root@rcc-pok-idg-2255 ~]#  python epdb1.py 
 > /root/epdb1.py(4)?() 
 -> b = "bbb"
 (Pdb) list 
  1     import pdb 
  2     a = "aaa"
  3     pdb.set_trace() 
  4  -> b = "bbb"
  5     c = "ccc"
  6     final = a + b + c 
  7     pdb.set_trace() 
  8     print final 
 [EOF] 
 (Pdb) c 
 > /root/epdb1.py(8)?() 
 -> print final 
 (Pdb) list 
  3     pdb.set_trace() 
  4     b = "bbb"
  5     c = "ccc"
  6     final = a + b + c 
  7     pdb.set_trace() 
  8  -> print final 
 [EOF] 
 (Pdb)
			

在使用函数的情况下进行 debug


清单 7. 使用函数的例子


				
import pdb 
 def combine(s1,s2):      # define subroutine combine, which... 
    s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
    s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
    return s3            # and returns it. 
 a = "aaa"
 pdb.set_trace() 
 b = "bbb"
 c = "ccc"
 final = combine(a,b) 
 print final
			

如果直接使用 n 进行 debug 则到 final=combine(a,b) 这句的时候会将其当做普通的赋值语句处理,进入到 print final。如果想要对函数进行 debug 如何处理呢 ? 可以直接使用 s 进入函数块。函数里面的单步调试与上面的介绍类似。如果不想在函数里单步调试可以在断点处直接按 r 退出到调用的地方。


清单 8. 对函数进行 debug


				
[root@rcc-pok-idg-2255 ~]# python epdb2.py 
 > /root/epdb2.py(10)?() 
 -> b = "bbb"
 (Pdb) n 
 > /root/epdb2.py(11)?() 
 -> c = "ccc"
 (Pdb) n 
 > /root/epdb2.py(12)?() 
 -> final = combine(a,b) 
 (Pdb) s 
 --Call-- 
 > /root/epdb2.py(3)combine() 
 -> def combine(s1,s2):      # define subroutine combine, which... 
 (Pdb) n 
 > /root/epdb2.py(4)combine() 
 -> s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
 (Pdb) list 
  1     import pdb 
  2 
  3     def combine(s1,s2):      # define subroutine combine, which... 
  4  ->     s3 = s1 + s2 + s1    # sandwiches s2 between copies of s1, ... 
  5         s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
  6         return s3            # and returns it. 
  7 
  8     a = "aaa"
  9     pdb.set_trace() 
 10     b = "bbb"
 11     c = "ccc"
 (Pdb) n 
 > /root/epdb2.py(5)combine() 
 -> s3 = '"' + s3 +'"'   # encloses it in double quotes,... 
 (Pdb) n 
 > /root/epdb2.py(6)combine() 
 -> return s3            # and returns it. 
 (Pdb) n 
 --Return-- 
 > /root/epdb2.py(6)combine()->'"aaabbbaaa"'
 -> return s3            # and returns it. 
 (Pdb) n 
 > /root/epdb2.py(13)?() 
 -> print final 
 (Pdb)
			

在调试的时候动态改变值 。在调试的时候可以动态改变变量的值,具体如下实例。需要注意的是下面有个错误,原因是 b 已经被赋值了,如果想重新改变 b 的赋值,则应该使用! B。


清单 9. 在调试的时候动态改变值


				
[root@rcc-pok-idg-2255 ~]# python epdb2.py 
 > /root/epdb2.py(10)?() 
 -> b = "bbb"
 (Pdb) var = "1234"
 (Pdb) b = "avfe"
 *** The specified object '= "avfe"' is not a function 
 or was not found along sys.path. 
 (Pdb) !b="afdfd"
 (Pdb)
			

pdb 调试有个明显的缺陷就是对于多线程,远程调试等支持得不够好,同时没有较为直观的界面显示,不太适合大型的 python 项目。而在较大的 python 项目中,这些调试需求比较常见,因此需要使用更为高级的调试工具。接下来将介绍 PyCharm IDE 的调试方法 .

使用 PyCharm 进行调试

PyCharm 是由 JetBrains 打造的一款 Python IDE,具有语法高亮、Project 管理、代码跳转、智能提示、自动完成、单元测试、版本控制等功能,同时提供了对 Django 开发以及 Google App Engine 的支持。分为个人独立版和商业版,需要 license 支持,也可以获取免费的 30 天试用。试用版本的 Pycharm 可以在官网上下载,下载地址为:http://www.jetbrains.com/pycharm/download/index.html。 PyCharm 同时提供了较为完善的调试功能,支持多线程,远程调试等,可以支持断点设置,单步模式,表达式求值,变量查看等一系列功能。PyCharm IDE 的调试窗口布局如图 1 所示。


图 1. PyCharm IDE 窗口布局
图片示例


下面结合实例讲述如何利用 PyCharm 进行多线程调试。具体调试所用的代码实例见清单 10。


清单 10. PyCharm 调试代码实例


				
__author__ = 'zhangying'
 #!/usr/bin/python 
 import thread 
 import time 
 # Define a function for the thread 
 def print_time( threadName, delay): 
    count = 0 
    while count <  5: 
        count += 1 
        print "%s: %s" % ( threadName, time.ctime(time.time()) ) 
 def check_sum(threadName,valueA,valueB): 
    print "to calculate the sum of two number her"
    result=sum(valueA,valueB) 
    print "the result is" ,result; 
 def sum(valueA,valueB): 
    if valueA >0 and valueB>0: 
        return valueA+valueB 
 def readFile(threadName, filename): 
    file = open(filename) 
    for line in file.xreadlines(): 
        print line 
 try: 
    thread.start_new_thread( print_time, ("Thread-1", 2, ) ) 
    thread.start_new_thread( check_sum, ("Thread-2", 4,5, ) ) 
    thread.start_new_thread( readFile, ("Thread-3","test.txt",)) 
 except: 
    print "Error: unable to start thread"
 while 1: 
 # 	 print "end"
    pass
			

在调试之前通常需要设置断点,断点可以设置在循环或者条件判断的表达式处或者程序的关键点。设置断点的方法非常简单:在代码编辑框中将光标移 动到需要设置断点的行,然后直接按 Ctrl+F8 或者选择菜单"Run"->"Toggle Line Break Point",更为直接的方法是双击代码编辑处左侧边缘,可以看到出现红色的小圆点(如图 2)。当调试开始的时候,当前正在执行的代码会直接显示为蓝色。下图中设置了三个断点,蓝色高亮显示的为正在执行的代码。


图 2. 断点设置
图片示例 2


表达式求值:在调试过程中有的时候需要追踪一些表达式的值来发现程序中的问题,Pycharm 支持表达式求值,可以通过选中该表达式,然后选择“Run”->”Evaluate Expression”,在出现的窗口中直接选择 Evaluate 便可以查看。

Pychar 同时提供了 Variables 和 Watches 窗口,其中调试步骤中所涉及的具体变量的值可以直接在 variable 一栏中查看。


图 3. 变量查看
图片示例 3


如果要动态的监测某个变量可以直接选中该变量并选择菜单”Run”->”Add Watch”添加到 watches 栏中。当调试进行到该变量所在的语句时,在该窗口中可以直接看到该变量的具体值。


图 4. 监测变量
图片示例 4


对于多线程程序来说,通常会有多个线程,当需要 debug 的断点分别设置在不同线程对应的线程体中的时候,通常需要 IDE 有良好的多线程调试功能的支持。 Pycharm 中在主线程启动子线程的时候会自动产生一个 Dummy 开头的名字的虚拟线程,每一个 frame 对应各自的调试帧。如图 5,本实例中一共有四个线程,其中主线程生成了三个线程,分别为 Dummy-4,Dummy-5,Dummy-6. 其中 Dummy-4 对应线程 1,其余分别对应线程 2 和线程 3。


图 5. 多线程窗口
图片示例 5


当调试进入到各个线程的子程序时,Frame 会自动切换到其所对应的 frame,相应的变量栏中也会显示与该过程对应的相关变量,如图 6,直接控制调试按钮,如 setp in,step over 便可以方便的进行调试。


图 6. 子线程调试
图片示例 6


查看大图

使用 PyDev 进行调试

PyDev 是一个开源的的 plugin,它可以方便的和 Eclipse 集成,提供方便强大的调试功能。同时作为一个优秀的 Python IDE 还提供语法错误提示、源代码编辑助手、Quick Outline、Globals Browser、Hierarchy View、运行等强大功能。下面讲述如何将 PyDev 和 Eclipse 集成。在安装 PyDev 之前,需要先安装 Java 1.4 或更高版本、Eclipse 以及 Python。 第一步:启动 Eclipse,在 Eclipse 菜单栏中找到 Help 栏,选择 Help > Install New Software,并选择 Add button,添加 Ptdev 的下载站点 http://pydev.org/updates。选择 PyDev 之后完成余下的步骤便可以安装 PyDev。


图 7. 安装 PyDev
图片示例 7


安装完成之后需要配置 Python 解释器,在 Eclipse 菜单栏中,选择 Window > Preferences > Pydev > Interpreter – Python。Python 安装在 C:\Python27 路径下。单击 New,选择 Python 解释器 python.exe,打开后显示出一个包含很多复选框的窗口,选择需要加入系统 PYTHONPATH 的路径,单击 OK。


图 8. 配置 PyDev
图片示例 8


在配置完 Pydev 之后,可以通过在 Eclipse 菜单栏中,选择 File > New > Project > Pydev >Pydev Project,单击 Next 创建 Python 项目,下面的内容假设 python 项目已经创建,并且有个需要调试的脚本 remote.py(具体内容如下),它是一个登陆到远程机器上去执行一些命令的脚本,在运行的时候需要传入一些参数,下面将详细讲述如何在调试过程中传 入参数 .


清单 11. Pydev 调试示例代码


				
 #!/usr/bin/env python 
 import os 	 
 def telnetdo(HOST=None, USER=None, PASS=None, COMMAND=None): #define a function 
	 import telnetlib, sys 
	 if not HOST: 
		 try: 
			 HOST = sys.argv[1] 
			 USER = sys.argv[2] 
			 PASS = sys.argv[3] 
			 COMMAND = sys.argv[4] 
		 except: 
			 print "Usage: remote.py host user pass command"
			 return 
	 tn = telnetlib.Telnet() # 
	 try: 
		 tn.open(HOST) 
	 except: 
		 print "Cannot open host"
		 return 
	 tn.read_until("login:") 
	 tn.write(USER + '\n') 
	 if PASS: 
		 tn.read_until("Password:") 
		 tn.write(PASS + '\n') 
		 tn.write(COMMAND + '\n') 
		 tn.write("exit\n") 
		 tmp = tn.read_all() 
		 tn.close() 
		 del tn 
		 return tmp 
		
 if __name__ == '__main__': 
	 print telnetdo()
			

在调试的时候有些情况需要传入一些参数,在调试之前需要进行相应的配置以便接收所需要的参数,选择需要调试的程序(本例 remote.py),该脚本在 debug 的过程中需要输入四个参数:host,user,password 以及命令。在 eclipse 的工程目录下选择需要 debug 的程序,单击右键,选择“Debug As”->“Debug Configurations”,在 Arguments Tab 页中选择“Variables”。如下 图 9 所示 .


图 9. 配置变量
图片示例 9


在窗口”Select Variable”之后选择“Edit Varuables” ,出现如下窗口,在下图中选择”New” 并在弹出的窗口中输入对应的变量名和值。特别需要注意的是在值的后面一定要有空格,不然所有的参数都会被当做第一个参数读入。


图 10. 添加具体变量
图片示例 10


按照以上方式依次配置完所有参数,然后在”select variable“窗口中安装参数所需要的顺序依次选择对应的变量。配置完成之后状态如下图 11 所示。


图 11. 完成配置
图片示例 11


选择 Debug 便可以开始程序的调试,调试方法与 eclipse 内置的调试功能的使用相似,并且支持多线程的 debug,这方面的文章已经有很多,读者可以自行搜索阅读,或者参考”使用 Eclipse 平台进行调试“一文。

使用日志功能达到调试的目的

日志信息是软件开发过程中进行调试的一种非常有用的方式,特别是在大型软件开发过程需要很多相关人员进行协作的情况下。开发人员通过在代码中 加入一些特定的能够记录软件运行过程中的各种事件信息能够有利于甄别代码中存在的问题。这些信息可能包括时间,描述信息以及错误或者异常发生时候的特定上 下文信息。 最原始的 debug 方法是通过在代码中嵌入 print 语句,通过输出一些相关的信息来定位程序的问题。但这种方法有一定的缺陷,正常的程序输出和 debug 信息混合在一起,给分析带来一定困难,当程序调试结束不再需要 debug 输出的时候,通常没有很简单的方法将 print 的信息屏蔽掉或者定位到文件。python 中自带的 logging 模块可以比较方便的解决这些问题,它提供日志功能,将 logger 的 level 分为五个级别,可以通过 Logger.setLevel(lvl) 来设置。默认的级别为 warning。


表 2. 日志的级别


Level 使用情形
DEBUG 详细的信息,在追踪问题的时候使用
INFO 正常的信息
WARNING 一些不可预见的问题发生,或者将要发生,如磁盘空间低等,但不影响程序的运行
ERROR 由于某些严重的问题,程序中的一些功能受到影响
CRITICAL 严重的错误,或者程序本身不能够继续运行

logging lib 包含 4 个主要对象

  • logger:logger 是程序信息输出的接口。它分散在不同的代码中使得程序可以在运行的时候记录相应的信息,并根据设置的日志级别或 filter 来决定哪些信息需要输出并将这些信息分发到其关联的 handler。常用的方法有 Logger.setLevel(),Logger.addHandler() ,Logger.removeHandler() ,Logger.addFilter() ,Logger.debug(), Logger.info(), Logger.warning(), Logger.error(),getLogger() 等。logger 支持层次继承关系,子 logger 的名称通常是父 logger.name 的方式。如果不创建 logger 的实例,则使用默认的 root logger,通过 logging.getLogger() 或者 logging.getLogger("") 得到 root logger 实例。
  • Handler:Handler 用来处理信息的输出,可以将信息输出到控制台,文件或者网络。可以通过 Logger.addHandler() 来给 logger 对象添加 handler,常用的 handler 有 StreamHandler 和 FileHandler 类。StreamHandler 发送错误信息到流,而 FileHandler 类用于向文件输出日志信息,这两个 handler 定义在 logging 的核心模块中。其他的 hander 定义在 logging.handles 模块中,如 HTTPHandler,SocketHandler。
  • Formatter:Formatter 则决定了 log 信息的格式 , 格式使用类似于 %(< dictionary key >)s 的形式来定义,如'%(asctime)s - %(levelname)s - %(message)s',支持的 key 可以在 python 自带的文档 LogRecord attributes 中查看。
  • Filter:Filter 用来决定哪些信息需要输出。可以被 handler 和 logger 使用,支持层次关系,比如如果设置了 filter 为名称为 A.B 的 logger,则该 logger 和其子 logger 的信息会被输出,如 A.B,A.B.C.

清单 12. 日志使用示例


				
import logging 
 LOG1=logging.getLogger('b.c') 
 LOG2=logging.getLogger('d.e') 
 filehandler = logging.FileHandler('test.log','a') 
 formatter = logging.Formatter('%(name)s %(asctime)s %(levelname)s %(message)s') 
 filehandler.setFormatter(formatter) 
 filter=logging.Filter('b') 
 filehandler.addFilter(filter) 
 LOG1.addHandler(filehandler) 
 LOG2.addHandler(filehandler) 
 LOG1.setLevel(logging.INFO) 
 LOG2.setLevel(logging.DEBUG) 
 LOG1.debug('it is a debug info for log1') 
 LOG1.info('normal infor for log1') 
 LOG1.warning('warning info for log1:b.c') 
 LOG1.error('error info for log1:abcd') 
 LOG1.critical('critical info for log1:not worked') 
 LOG2.debug('debug info for log2') 
 LOG2.info('normal info for log2') 
 LOG2.warning('warning info for log2') 
 LOG2.error('error:b.c') 
 LOG2.critical('critical')
			

上例设置了 filter b,则 b.c 为 b 的子 logger,因此满足过滤条件该 logger 相关的日志信息会 被输出,而其他不满足条件的 logger(这里是 d.e)会被过滤掉。


清单 13. 输出结果


				
b.c 2011-11-25 11:07:29,733 INFO normal infor for log1 
 b.c 2011-11-25 11:07:29,733 WARNING warning info for log1:b.c 
 b.c 2011-11-25 11:07:29,733 ERROR error info for log1:abcd 
 b.c 2011-11-25 11:07:29,733 CRITICAL critical info for log1:not worked
			

logging 的使用非常简单,同时它是线程安全的,下面结合多线程的例子讲述如何使用 logging 进行 debug。


清单 14. 多线程使用 logging


				
logging.conf 
 [loggers] 
 keys=root,simpleExample 

 [handlers] 
 keys=consoleHandler 

 [formatters] 
 keys=simpleFormatter 

 [logger_root] 
 level=DEBUG 
 handlers=consoleHandler 

 [logger_simpleExample] 
 level=DEBUG 
 handlers=consoleHandler 
 qualname=simpleExample 
 propagate=0 

 [handler_consoleHandler] 
 class=StreamHandler 
 level=DEBUG 
 formatter=simpleFormatter 
 args=(sys.stdout,) 

 [formatter_simpleFormatter] 
 format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 
 datefmt= 

 code example: 
 #!/usr/bin/python 
 import thread 
 import time 
 import logging 
 import logging.config 
 logging.config.fileConfig('logging.conf') 
 # create logger 
 logger = logging.getLogger('simpleExample') 
 # Define a function for the thread 
 def print_time( threadName, delay): 
	 logger.debug('thread 1 call print_time function body') 
	 count = 0 
	 logger.debug('count:%s',count)
			

总结

全文介绍了 python 中 debug 的几种不同的方式,包括 pdb 模块、利用 PyDev 和 Eclipse 集成进行调试、PyCharm 以及 Debug 日志进行调试,希望能给相关 python 使用者一点参考。更多关于 python debugger 的资料可以参见参考资料。

让PHP更快的提供文件下载

Posted on 2012年5月08日 16:39

 

一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.

但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.

  1. <?php
  2.     $file = "/tmp/dummy.tar.gz";
  3.     header("Content-type: application/octet-stream");
  4.     header('Content-Disposition: attachment; filename="' . basename($file) . '"');
  5.     header("Content-Length: ". filesize($file));
  6.     readfile($file);

但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.

于是, 我们做一下修改(参考: :

  1. <?php
  2.     $file = "/tmp/中文名.tar.gz";
  3.  
  4.     $filename = basename($file);
  5.  
  6.     header("Content-type: application/octet-stream");
  7.  
  8.     //处理中文文件名
  9.     $ua = $_SERVER["HTTP_USER_AGENT"];
  10.     $encoded_filename = urlencode($filename);
  11.     $encoded_filename = str_replace("+", "%20", $encoded_filename);
  12.     if (preg_match("/MSIE/", $ua)) {
  13.      header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
  14.     } else if (preg_match("/Firefox/", $ua)) {
  15.      header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
  16.     } else {
  17.      header('Content-Disposition: attachment; filename="' . $filename . '"');
  18.     }
  19.  
  20.     header('Content-Disposition: attachment; filename="' . $filename . '"');
  21.     header("Content-Length: ". filesize($file));
  22.     readfile($file);

恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.

输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.

那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?

今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.

我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:

  1. <?php
  2.     $file = "/tmp/中文名.tar.gz";
  3.  
  4.     $filename = basename($file);
  5.  
  6.     header("Content-type: application/octet-stream");
  7.  
  8.     //处理中文文件名
  9.     $ua = $_SERVER["HTTP_USER_AGENT"];
  10.     $encoded_filename = urlencode($filename);
  11.     $encoded_filename = str_replace("+", "%20", $encoded_filename);
  12.     if (preg_match("/MSIE/", $ua)) {
  13.      header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
  14.     } else if (preg_match("/Firefox/", $ua)) {
  15.      header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
  16.     } else {
  17.      header('Content-Disposition: attachment; filename="' . $filename . '"');
  18.     }
  19.  
  20.     header('Content-Disposition: attachment; filename="' . basename($file) . '"');
  21.  
  22.     //让Xsendfile发送文件
  23.     header("X-Sendfile: $file");

X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.

Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看 :)

memcache 启动参数

Posted on 2012年5月03日 17:38

启动方式:

参数 说明
-d 以守护程序(daemon)方式运行
-u root 指定用户,如果当前为 root ,需要使用此参数指定用户
-P /tmp/a.pid 保存PID到指定文件

内存设置:

参数 说明
-m 1024 数据内存数量,不包含memcached本身占用,单位为 MB
-M 内存不够时禁止LRU,报错
-n 48 初始chunk=key+suffix+value+32结构体,默认48字节
-f 1.25 增长因子,默认1.25
-L 启用大内存页,可以降低内存浪费,改进性能

连接设置:

参数 说明
-l 127.0.0.1 监听的 IP 地址,本机可以不设置此参数
-p 11211 TCP端口,默认为11211,可以不设置
-U 11211 UDP端口,默认为11211,0为关闭

并发设置:

参数 说明
-c 1024 最大并发连接数,默认1024,最好是200
-t 4 线程数,默认4。由于memcached采用NIO,所以更多线程没有太多作用
-R 20 每个event连接最大并发数,默认20
-C 禁用CAS命令(可以禁止版本计数,减少开销)

检查正在运行的配置

$ echo "stats settings" | nc localhost 11211
STAT maxbytes 67108864
STAT maxconns 1024
STAT tcpport 11211
STAT udpport 11211
STAT inter NULL
STAT verbosity 0
STAT oldest 0
STAT evictions on
STAT domain_socket NULL
STAT umask 700
STAT growth_factor 1.25
STAT chunk_size 48
STAT num_threads 4
STAT stat_key_prefix :
STAT detail_enabled no
STAT reqs_per_event 20
STAT cas_enabled yes
STAT tcp_backlog 1024
STAT binding_protocol auto-negotiate
STAT auth_enabled_sasl no
STAT item_size_max 1048576
END

官网中描述

Running Memcached on Webservers
An easy layout is to use spare memory on webservers or compute nodes that you may have. If you buy a webserver with 4G of RAM, but your app and OS only use 2G of RAM at most, you could assign 1.5G or more to memcached instances.

This has a good tradeoff of spreading memory out more thinly, so losing any one webserver will not cause as much pain.

谷歌翻译后

运行Web服务器的Memcached
一个简单的布局是Web服务器或计算节点,您可能使用备用内存。如果你买了4G的RAM的Web服务器,但您的应用程序和操作系统只能使用2G的RAM,你可以分配1.5G或多个memcached实例。

这有一个较好的折中传播更多薄内存,所以失去任何一个Web服务器,不会造成多大的痛苦。

2. 参考资料

http://code.google.com/p/memcached/wiki/NewStart

mysql install

Posted on 2012年4月22日 02:45

简介:

 

两种安装方式:二进制文件安装和编译安装(本文使用二进制安装方式安装,有兴趣的童鞋自己尝试编译安装)

 

环境


ubuntu-11-10 32


准备

 

0.获取mysql-5.5.20-linux2.6-i686.tar.gz二进制安装文件 

 

具体步骤:

     mysql官网下载页面选择Linux-Generic下的Linux - Generic 2.6 (x86,32-bit), Compressed (位数适你的linux的位数而定。    我这里用的是32位的,所以我选择32位版本的mysql)

   你可以在ubuntu 中使用 wget 命令获取,或者任何一种下载工具都可以,只要有 mysql 的 tar.gz 安装文件就行。

 

  1. 1. wget http://ftp.jaist.ac.jp/pub/mysql/Downloads/MySQL-5.5/mysql-5.5.20-linux2.6-i686.tar.gz  

 

1.安装 mysql所使用的依赖 

        
  1. 1. sudo apt-get install libaio1  
 
安装过程

安装步骤参考mysql官方安装文档 
# 表示当前是 root 用户 
$ 表示当前普通用户 
pwd 表示当前所在目录 


 

 

0.添加mysql用户组

  1. 1. # groupadd mysql  
 
 

1.添加msyql用户到mysql用户组  

  1. 1. # useradd -r -g mysql mysql  
 

2.解压mysql-5.5.20-linux2.6-i686.tar.gz/usr/local进入/usr/local 

 

  1. 1. # cd /usr/local    

我的 tar.gz文件路径是/home/user/mysql-5.5.20-linux2.6-i686.tar.gz,你根据你的mysql安装文件路径调整命令参数
pwd: /usr/local

  1. 1. # tar zxvf /home/user/mysql-5.5.20-linux2.6-i686.tar.gz    

3./usr/local下添加名为mysql指向解压的文件夹的软连接

pwd: /usr/local

  1. 1. # ln -s mysql-5.5.20-linux2.6-i686 mysql    

4.进入mysql目录  

pwd:/usr/local 

  1. 1. # cd mysql    

5.设置mysql目录的拥有者和所属的用户组

pwd:/usr/local/mysql 

  1. 1. # chown -R mysql .    
  1. 2. # chgrp -R mysql .    

6.执行mysql安装脚本

pwd:/usr/local/mysql

  1. 1. # scripts/mysql_install_db --user=mysql   

7.再次设置mysql目录的拥有者 

pwd:/usr/local/mysql


  1. 1. # chown -R root .  

8.设置data目录的拥有者

pwd:/usr/local/mysql


  1. 1. # chown -R mysql data  

 


安装后配置

 

0.复制 mysql 配置文件

 

pwd:/usr/local/mysql 

 

  1. 1. # cp support-files/my-medium.cnf /etc/my.cnf  

1.启动mysql

pwd: /usr/local/mysql 

  1. 1. # bin/mysqld_safe --user=mysql &    


 

2.初始化root用户密码

pwd:/usr/local/mysql

  1. 1. # bin/mysqladmin -u root password 'new_password'    

3.复制mysql.server脚本  

pwd:/usr/local/mysql 

 

  1. 1. # cp support-files/mysql.server /etc/init.d/mysql.server  



好了,mysql配置基本完成
可以查看mysql的状态,结果可能是"MySQL is runnin"或者是"MySQL is not running" 

 

  1. 1. $ sudo /etc/init.d/mysql.server status   

 

如果一切顺利,mysql是正常运行的,而且输入刚才设置的new_password可以通过mysql客户端,进入mysql 

  1. 1. $ /usr/local/mysql/bin/mysql -u root -p    

可以使用命令:

mysql停止运行  

  1. 1. $ sudo /etc/init.d/mysql.server stop  

mysql启动

  1. 1. $ sudo /etc/init.d/mysql.server start   

查看状态  

  1. 1. $ sudo /etc/init.d/mysql.server status  

或者使用service命令,让mysql启动,停止  

 
  1. 1. $ sudo service mysql.server [status|start|stop]    

可选配置

 

0.mysql开机自己启动 

  1. 1. $ sudo update-rc.d -f mysql.server defaults    

 

如果不想让mysql开机自己启动,可以使用 

  1. 1. $ sudo update-rc.d -f mysql.server remove  

1./usr/local/mysql/bin/mysql命令加到用户命令中,这样就不用每次都加mysql命令的路径 

  1. 1. $ sudo ln -s /usr/local/mysql/bin/mysql /usr/local/bin/mysql    

 

现在就直接可以使用 mysql命令了 

 

  1. 1. $ mysql -u root -p    

 

本文档参考于:http://bastengao.iteye.com/blog/1161732

Python3 smtp 发送邮件

Posted on 2012年3月30日 20:14

文件形式的邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.text import MIMEText  
  5. from email.header import Header  
  6.   
  7. sender = '***'  
  8. receiver = '***'  
  9. subject = 'python email test'  
  10. smtpserver = 'smtp.163.com'  
  11. username = '***'  
  12. password = '***'  
  13.   
  14. msg = MIMEText('你好','text','utf-8')#中文需参数‘utf-8’,单字节字符不需要  
  15. msg['Subject'] = Header(subject, 'utf-8')  
  16.   
  17. smtp = smtplib.SMTP()  
  18. smtp.connect('smtp.163.com')  
  19. smtp.login(username, password)  
  20. smtp.sendmail(sender, receiver, msg.as_string())  
  21. smtp.quit()  

HTML形式的邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.text import MIMEText  
  5.   
  6. sender = '***'  
  7. receiver = '***'  
  8. subject = 'python email test'  
  9. smtpserver = 'smtp.163.com'  
  10. username = '***'  
  11. password = '***'  
  12.   
  13. msg = MIMEText('<html><h1>你好</h1></html>','html','utf-8')  
  14.   
  15. msg['Subject'] = subject  
  16.   
  17. smtp = smtplib.SMTP()  
  18. smtp.connect('smtp.163.com')  
  19. smtp.login(username, password)  
  20. smtp.sendmail(sender, receiver, msg.as_string())  
  21. smtp.quit()  

带图片的HTML邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.multipart import MIMEMultipart  
  5. from email.mime.text import MIMEText  
  6. from email.mime.image import MIMEImage  
  7.   
  8. sender = '***'  
  9. receiver = '***'  
  10. subject = 'python email test'  
  11. smtpserver = 'smtp.163.com'  
  12. username = '***'  
  13. password = '***'  
  14.   
  15. msgRoot = MIMEMultipart('related')  
  16. msgRoot['Subject'] = 'test message'  
  17.   
  18. msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>good!','html','utf-8')  
  19. msgRoot.attach(msgText)  
  20.   
  21. fp = open('h:\\python\\1.jpg''rb')  
  22. msgImage = MIMEImage(fp.read())  
  23. fp.close()  
  24.   
  25. msgImage.add_header('Content-ID''<image1>')  
  26. msgRoot.attach(msgImage)  
  27.   
  28. smtp = smtplib.SMTP()  
  29. smtp.connect('smtp.163.com')  
  30. smtp.login(username, password)  
  31. smtp.sendmail(sender, receiver, msgRoot.as_string())  
  32. smtp.quit()  

带附件的邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.multipart import MIMEMultipart  
  5. from email.mime.text import MIMEText  
  6. from email.mime.image import MIMEImage  
  7.   
  8. sender = '***'  
  9. receiver = '***'  
  10. subject = 'python email test'  
  11. smtpserver = 'smtp.163.com'  
  12. username = '***'  
  13. password = '***'  
  14.   
  15. msgRoot = MIMEMultipart('related')  
  16. msgRoot['Subject'] = 'test message'  
  17.   
  18. #构造附件  
  19. att = MIMEText(open('h:\\python\\1.jpg''rb').read(), 'base64''utf-8')  
  20. att["Content-Type"] = 'application/octet-stream'  
  21. att["Content-Disposition"] = 'attachment; filename="1.jpg"'  
  22. msgRoot.attach(att)  
  23.           
  24. smtp = smtplib.SMTP()  
  25. smtp.connect('smtp.163.com')  
  26. smtp.login(username, password)  
  27. smtp.sendmail(sender, receiver, msgRoot.as_string())  
  28. smtp.quit()  

群邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.text import MIMEText  
  5.   
  6. sender = '***'  
  7. receiver = ['***','****',……]  
  8. subject = 'python email test'  
  9. smtpserver = 'smtp.163.com'  
  10. username = '***'  
  11. password = '***'  
  12.   
  13. msg = MIMEText('你好','text','utf-8')  
  14.   
  15. msg['Subject'] = subject  
  16.   
  17. smtp = smtplib.SMTP()  
  18. smtp.connect('smtp.163.com')  
  19. smtp.login(username, password)  
  20. smtp.sendmail(sender, receiver, msg.as_string())  
  21. smtp.quit()  

各种元素都包含的邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.multipart import MIMEMultipart  
  5. from email.mime.text import MIMEText  
  6. from email.mime.image import MIMEImage  
  7.   
  8. sender = '***'  
  9. receiver = '***'  
  10. subject = 'python email test'  
  11. smtpserver = 'smtp.163.com'  
  12. username = '***'  
  13. password = '***'  
  14.   
  15. # Create message container - the correct MIME type is multipart/alternative.  
  16. msg = MIMEMultipart('alternative')  
  17. msg['Subject'] = "Link"  
  18.   
  19. # Create the body of the message (a plain-text and an HTML version).  
  20. text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.python.org"  
  21. html = """\ 
  22. <html> 
  23.   <head></head> 
  24.   <body> 
  25.     <p>Hi!<br> 
  26.        How are you?<br> 
  27.        Here is the <a href="http://www.python.org">link</a> you wanted. 
  28.     </p> 
  29.   </body> 
  30. </html> 
  31. """  
  32.   
  33. # Record the MIME types of both parts - text/plain and text/html.  
  34. part1 = MIMEText(text, 'plain')  
  35. part2 = MIMEText(html, 'html')  
  36.   
  37. # Attach parts into message container.  
  38. # According to RFC 2046, the last part of a multipart message, in this case  
  39. # the HTML message, is best and preferred.  
  40. msg.attach(part1)  
  41. msg.attach(part2)  
  42. #构造附件  
  43. att = MIMEText(open('h:\\python\\1.jpg''rb').read(), 'base64''utf-8')  
  44. att["Content-Type"] = 'application/octet-stream'  
  45. att["Content-Disposition"] = 'attachment; filename="1.jpg"'  
  46. msg.attach(att)  
  47.      
  48. smtp = smtplib.SMTP()  
  49. smtp.connect('smtp.163.com')  
  50. smtp.login(username, password)  
  51. smtp.sendmail(sender, receiver, msg.as_string())  
  52. smtp.quit()  

基于SSL的邮件

[python] view plaincopy
  1. #!/usr/bin/env python3  
  2. #coding: utf-8  
  3. import smtplib  
  4. from email.mime.text import MIMEText  
  5. from email.header import Header  
  6. sender = '***'  
  7. receiver = '***'  
  8. subject = 'python email test'  
  9. smtpserver = 'smtp.163.com'  
  10. username = '***'  
  11. password = '***'  
  12.   
  13. msg = MIMEText('你好','text','utf-8')#中文需参数‘utf-8’,单字节字符不需要  
  14. msg['Subject'] = Header(subject, 'utf-8')  
  15.   
  16. smtp = smtplib.SMTP()  
  17. smtp.connect('smtp.163.com')  
  18. smtp.ehlo()  
  19. smtp.starttls()  
  20. smtp.ehlo()  
  21. smtp.set_debuglevel(1)  
  22. smtp.login(username, password)  
  23. smtp.sendmail(sender, receiver, msg.as_string())  
  24. smtp.quit() 

 

PHP命名空间

Posted on 2012年3月22日 15:46

究竟什么是"命名空间"呢? 看看中文手册是怎么说的:

命名空间概述

什么是命名空间?从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。

例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。

具体举个例子,文件 foo.txt 可以同时在目录/home/greg 和 /home/other 中存在,但在同一个目录中

不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以

及目录分隔符放在文件名之前得到 /home/greg/foo.txt。

这个原理应用到程序设计领域就是命名空间的概念。

在PHP中,命名空间用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:

1) 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。 

2) 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。 

看完概述之后有了初步的概念,用生活中例子描述:
甲有一台iphone5,乙也有一个iphone5,但我们能区分清楚,因为他们分别属于两个不同的人。 

命名空间的定义和规则就不多说了,可以看手册介绍。

看看代码是怎么操作的:
创建三个文件分别是file1.ph、file2.php、file3.php、file4.php 他们同属于一个目录级里面。

  1) file1.php

<?php
//这是其中一种定义命名空间的方式
namespace App\Lib1;

const MYCONST = 'App\Lib1\MYCONST';  

function MyFunction() {  
    return __FUNCTION__;  
}  

class MyClass
{
    static function WhoAmI() {  
	return __METHOD__;  
    }  
}
?>

 2) file2.php

<?php
//这是另外一种使用代码块的方式定义命名空间,这方式可以区分开同一个文件里两个不同的命名空间的定义(但是不建议做)。
namespace App\Lib2
{
	const MYCONST = 'App\Lib2\MYCONST';  

	function MyFunction() {  
		return __FUNCTION__;  
	}  

	class MyClass
	{
		static function WhoAmI() {  
			return __METHOD__; 
		}  
	}
} 
?>

file4.php

<?php
//普通的PHP文件,并未定义命名空间
const MYCONST = 'App\Lib4\MYCONST';  

function MyFunction() {  
	return __FUNCTION__;  
}  

class MyClass
{
	static function WhoAmI() {  
		return __METHOD__; 
	}  
}
?>

到这里file1.php和file2.php内容几乎一样, 只有命名空间的名字不同。

另外还要补充一下关于命名空间的一些相关术语:

完全限定名称(Fully-qualified name):

任何PHP代码都可以引用完全限定名称,它是一个以命名空间反斜线开头的标识符,如\App\Lib1\MYCONST,\App\Lib2\MyFunction( )等。

完全限定名称是没有任何歧义的,开头的反斜线和文件路径的作用有点类似,它表示“根”全局空间,如果我们在全局空间中实现了一个不同的MyFunction( ),可以使用\MyFunction( )从lib1.php或lib2.php调用它。

完全限定名称对一次性函数调用或对象初始化非常有用,但当你产生了大量的调用时它们就没有实用价值了,在下面的讨论中我们将会看到,PHP提供了其它选项以解除我们为命名空间打字的烦恼。

限定名称(Qualified name):
至少有一个命名空间分隔符的标识符,如Lib1\MyFunction( )。

非限定名称(Unqualified name):

没有命名空间分隔符的标识符,如MyFunction( )。

看看file3.php是如何使用的命名空间

<?php
/**
 * 若当前PHP文件没有定义命名空间,当require_once('file4.php')时就会发生冲突
 * Fatal error: Cannot redeclare MyFunction()
 */
namespace App\Lib3;

/* 这里下面是file3.php命名空间的一些定义 */
const MYCONST = 'App\Lib3\MYCONST';

function MyFunction() {
	return __FUNCTION__;
}

class MyClass
{
	static function WhoAmI() {  
		return __METHOD__; 
	}
}

/**
 * 把file1.php、file2.php、file4.php包含进来file3.php之后,尝试输出 echo MYCONST; 结果会是App\Lib3\MYCONST,
 * 为什么不是file4.php里面定义的MYCONST常量呢?因为file4.php是属于"根"的全局空间,并且输出时没有指定使用哪个命名
 * 空间(非限定名称),所以默认是当前文件的MYCONST常量,若要输出file4.php的常量,可以这样 echo \MYCONST;
 */
require_once('file1.php');
require_once('file2.php');
require_once('file4.php');

/**
 * 光是把定义了命名空间的文件包含进来,还不能使用各自的定义,需要使用 use 操作符导入命名空间
 * 格式: use 命名空间; ,另外对于很长的命名空间定义还能够使用别名,类似SQL的as, 格式: use 命名空间 as 别名;
 */
use App\Lib1;
use App\Lib2 as B;

/* 开始享受命名空间带来的便利了 */
#完全限定名称
#类似于绝对路劲,从“根”开始.
echo \App\Lib1\MYCONST . "<br/>";
echo \App\Lib1\MyFunction() . "<br/>";
echo \App\Lib1\MyClass::WhoAmI() . "<br/>";

#限定名称
#类似于相对路劲,让系统自己去搜索.
echo B\MYCONST . "<br/>";
echo B\MyFunction() . "<br/>";  
echo B\MyClass::WhoAmI() . "<br/>";

#非限定名称
echo MYCONST . "<br/>";
echo MyFunction() . "<br/>";  
echo MyClass::WhoAmI() . "<br/>";
?>

到这里已经对命名空间有一定的认识,下面再看看一些高级应用:

__NAMESPACE__常量

__NAMESPACE__是一个PHP字符串,它总是返回当前命名空间的名称,在全局空间(file4.php就是属于全局空间)中它是一个空字符串。

 

< ?php  
namespace App\Lib1;  
echo __NAMESPACE__; // outputs: App\Lib1  
?> 

这个值在调试时非常有用,它也可由于动态生成一个完全限定类名,如:

 

< ?php  
namespace App\Lib1;  
 
class MyClass {  
    public function WhoAmI() {  
        return __METHOD__;  
    }  
}  

$c = __NAMESPACE__ . '\\MyClass';  
$m = new $c;  
echo $m->WhoAmI(); // outputs: App\Lib1\MyClass::WhoAmI  
?>  

namespace关键字

namespace关键字可以用于明确引用一个当前命名空间或子命名空间中的项目,它等价于类中的self命名空间:

更多详细的介绍在手册里面,非常多例子。

 

< ?php  
namespace App\Lib1;  
 
class MyClass {  
    public function WhoAmI() {  
        return __METHOD__;  
    }  
}  
 
$m = new namespace\MyClass;  
echo $m->WhoAmI(); // outputs: App\Lib1\MyClass::WhoAmI  
?>  

自动载入命名空间类

PHP 5中最省时省力的特性是自动载入,在全局(非命名空间)PHP代码中,可以写一个标准自动载入函数:

< ?php  
$obj= new MyClass1(); // classes/MyClass1.php is auto-loaded  
$obj= new MyClass2(); // classes/MyClass2.php is auto-loaded  
 
// autoload function  
function __autoload($class_name) {  
    require_once("classes/$class_name.php");  
}  
?>  

在PHP 5.3中,你可以创建一个命名空间类的实例,在这种情况下,完全限定命名空间和类名传递给__autoload函数,例如,$class_name的值可 能是App\Lib1\MyClass。你可以在相同的文件夹下放置所有的PHP类文件,从字符串中提取命名空间,但那样会导致文件名冲突。

另外,你的类文件层次结构会按照命名空间的结构重新组织,例如,MyClass.php文件可以创建在/classes/App/Lib1文件夹下:

/classes/App/Lib1/MyClass.php

< ?php
namespace App\Lib1;  
 
class MyClass {  
    public function WhoAmI() {  
        return __METHOD__;  
    }  
}  
?>  

在根文件夹下的文件就使用下面的代码了:

myapp.php

< ?php
use App\Lib1\MyClass as MC;  
 
$obj = new MC();  
echo $obj->WhoAmI();  
 
// autoload function  
function __autoload($class) {  
 // convert namespace to full file path  
 $class = 'classes/' . str_replace('\\', '/', $class) . '.php';  
 require_once($class);  
}  
?>  

解释:

1.类App\Lib1\MyClass的别名是MC;

2. new MC( )在编译时被翻译成new App\Lib1\MyClass( );

3.字符串App\Lib1\MyClass被传递给__autoload函数,使用文件路径正斜线替换所有命名空间中的反斜线,然后修改字符串,classes\App\Lib1\MyClass.php文件被自动载入;

 

本文努力转自http://my.oschina.net/Jacker/blog/32943

 

 

 

ubuntu 打造vim python编辑器

Posted on 2012年3月21日 00:06
" All system-wide defaults are set in $VIMRUNTIME/debian.vim (usually just
" /usr/share/vim/vimcurrent/debian.vim) and sourced by the call to :runtime
" you can find below.  If you wish to change any of those settings, you should
" do it in this file (/etc/vim/vimrc), since debian.vim will be overwritten
" everytime an upgrade of the vim packages is performed.  It is recommended to
" make changes after sourcing debian.vim since it alters the value of the
" 'compatible' option.

" This line should not be removed as it ensures that various options are
" properly set to work with the Vim-related packages available in Debian.
runtime! debian.vim

" Uncomment the next line to make Vim more Vi-compatible
" NOTE: debian.vim sets 'nocompatible'.  Setting 'compatible' changes numerous
" options, so any other options should be set AFTER setting 'compatible'.
"set compatible

" Vim5 and later versions support syntax highlighting. Uncommenting the next
" line enables syntax highlighting by default.
" 高亮显示
syntax on

" If using a dark background within the editing area and syntax highlighting
" turn on this option as well
" set background=dark

" Uncomment the following to have Vim jump to the last position when
" reopening a file
"if has("autocmd")
"  au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$")
"    \| exe "normal g'\"" | endif
"endif

" Uncomment the following to have Vim load indentation rules according to the
" detected filetype. Per default Debian Vim only load filetype specific
" plugins.
"if has("autocmd")
"  filetype indent on
"endif

" The following are commented out as they cause vim to behave a lot
" differently from regular Vi. They are highly recommended though.
"set showcmd                " Show (partial) command in status line.
"set showmatch                " Show matching brackets.
"set ignorecase                " Do case insensitive matching
"set smartcase                " Do smart case matching
"set incsearch                " Incremental search
"set autowrite                " Automatically save before commands like :next and :make
"set hidden             " Hide buffers when they are abandoned
"set mouse=a                " Enable mouse usage (all modes) in terminals

" Source a global configuration file if available
" XXX Deprecated, please move your changes here in /etc/vim/vimrc
if filereadable("/etc/vim/vimrc.local")
  source /etc/vim/vimrc.local
endif


if has("autocmd")

  " 自动检测文件类型并加载相应的设置
  filetype plugin indent on

  " Python 文件的一般设置,比如不要 tab 等
  autocmd FileType python setlocal et | setlocal sta | setlocal sw=4

  " Python Unittest 的一些设置
  " 可以让我们在编写 Python 代码及 unittest 测试时不需要离开 vim
  " 键入 :make 或者点击 gvim 工具条上的 make 按钮就自动执行测试用例
  autocmd FileType python compiler pyunit
  autocmd FileType python setlocal makeprg=python\ ./alltests.py
  autocmd BufNewFile,BufRead test*.py setlocal makeprg=python\ %

  " 自动使用新文件模板
  autocmd BufNewFile test*.py 0r ~/.vim/skeleton/test.py
  autocmd BufNewFile alltests.py 0r ~/.vim/skeleton/alltests.py
  autocmd BufNewFile *.py 0r ~/.vim/skeleton/skeleton.py

endif

" python auto-complete code
" Typing the following (in insert mode):
"   os.lis<Ctrl-n>
" will expand to:
"   os.listdir(
" Python 自动补全功能,只需要反覆按 Ctrl-N 就行了
if has("autocmd")
      autocmd FileType python set complete+=k~/.vim/tools/pydiction
endif


"设置文件类型为python语言
set filetype=python

"显示行号
set nu

"自动缩进
set autoindent

"背景显示颜色(我最喜欢的颜色)
color torte

"高亮搜索
set hlsearch

"输入字符串就显示匹配点
set incsearch

"输入的命令显示出来,看的清楚些
set showcmd

"自动补全命令时候使用菜单式起配列表
set wildmenu

"快速缩进?
set smartindent