Jul 6, 2011

programming python, note

windows 不支持 fork
newpid = os.fork()
父进程: newpid = 子进程的 pid
子进程: newpid =0; os.getpid() 可以得到当前进程(子进程)的pid

光有fork子进程只能执行python函数
fork配合 exec* 可以在子进程执行其他程序
os.execv
os.execl
os.execvp
os.execlp
os.execvpe
os.execlpe
exec* 是用目标程序代替(overlay)当前进程,当前进程的信息就没有了。exec* 也不会返回,意味着它后面的代码不会被执行,除非exec* 失败

结合 fork 和 exec*

多线程

  • thread module(_thread in 3.x)。使用thread模块,当主线程结束后,程序结束,不管子线程是否结束。
  • threading module
推荐使用 threading

thread = threading.Thread(target=consumer, args=(,))
thread.daemon = True/False
thread.start()
thread.join()
另一种使用方法是定义新类,继承threading.Thread,编写类函数 run

使用threading模块,要注意程序退出方式。线程分为daemon线程和非daemon线程,当程序只剩下daemon线程时,就结束。主线程是非daemon线程,并且子线程会继承父线程的这一性质,因此如果要使子线程为daemon,要手动改变。
daemon线程还没结束,程序可以结束。
从这个意义上来说,consumer 线程可以设为daemon,如此主线程退出可以结束程序。否则如果有comsumer计算量很大,用时很长,主线程主动退出,还不能结束程序。

线程安全,有两种方法
#in main thread
lock = threading.Lock()

# in worker thread
with lock:
    do stuff
注意,stdout, stderr 也是资源,也要注意线程安全,使用 lock

producer/consumer + Queue (queue in 3.0)
queue 本身就是线程安全的
#main thread
dataQueue = Queue.Queue()

#producer
dataQueue.put(...)

#consumer
dataQueue.get(block=True/False)  block=True没有问题,不会使得其他线程 get 受到影响(指抛出异常)
如果用 False,要配合
while True:
  try:
  except Queue.Empty:
    time.sleep(0.5) 
  else:
GUI 和线程
The main thread handles all GUI updates and runs a timer-based loop that wakes up periodically to check for new data on the queue to be displayed on-screen. 

The child threads don’t do anything GUI-related. They just produce data and put it on the queue to be picked up by the main thread. Alternatively, child threads can place a callback function on the queue, to be picked up and run by the main thread. 

进程间通信

pipe
shared memory
queue

multiprocess

接口和 threading 一样
# main process
from multiprocessing import Process, Lock
p = Process(target=whoami, args=('spawned child', lock))
p.start()
p.join()

# spawned process
with lock:
    print(msg % (label, __name__, os.getpid()))

另一个用法是继承Process类,覆盖_init__, run

On Windows, the main process’s logic should generally be nested under a __name__ == '__main__' test as done here when using this module, so it can be imported freely by a new interpreter without side effects. 因为multiprocessing在windows的实现机制是pickle process object,传送给另一个interpreter。而unpickle的时候,会导入包含process的module。使用 __name__ == '__main__' 避免import把把主进程的工作又执行一遍。

multiprocessing 也有 daemon 机制

multiprocessing 也只支持 function call 子进程,如果要运行新程序,要在子进程中使用 exec*

multiprocess 提供的进程间通信工具
待续
multiprocessing.Pipe 双向的
shared memory
multiprocessing.Queue 这是个IPC工具,用法和 Queue 相同,但是和线程没有关系

进程退出

如果consumer 只做一次任务,不用设置 daemon
如果是循环等待任务,要设置 daemon,使得主进程退出时,子进程也退出

sys.exit, os._exit 都不会结束子进程

windows下测试
一个主进程,两个子进程,共4个python进程
子进程循环等待,主进程退出,还剩3个Python

sys.exit(0) 只影响主进程,还剩3个Python
os._exit(0) 影响主进程和一个未知进程,还剩2个Python

pickle

file = open(filename, 'wb')
pickle.dump(object, file) # pickle to binary file
file.close() # any file-like object will do

file = open(filename, 'rb')
object = pickle.load(file) # unpickle from binary file
file.close() # re-creates object in memory

pickle 支持大部分python对象,
数字,字符,list, dict 等等。
归根结底,pickle 只支持数据(数字,字符,二进制数据),以及数据的组合(list,tuple,dict, etc),以及类实例(只包含数据部分)
pickle 不支持

  • 代码:函数对象,类对象(类也是对象),类函数
  • 不按照 class importability rules 的类实例
  • C 编写的类的实例
pickle 如何保存实例以及 class importability rules
pickle 只保存实例的数据,以及实例的类的信息(名称,位置),以保证在load时候,python能找到该类,import该类,建立一个空实例并填进去数据。

class importability rules 即保证 pickled 数据和类的兼容性。
dump 实例后,可以修改类,但是要保证修改向后兼容。这是一个trick,可以不改变数据,而只修改类的行为

shelve

shelve 是一个保存 pickle object 的工具
import shelve
dbase = shelve.open("mydbase")
dbase['key'] = object # store object
value = dbase['key'] # fetch object
len(dbase) # number of items stored
dbase.keys() # stored item key index iterable

货架,很好理解,相当于一个dict,value 是pickle object。限制 key 只能为字符串

input

2.7 word = input(['hint']) 相当于 eval(raw_input(['hint']))
所以输入 int('1') 得到 1
3.0 input 相当于 2.7 的 raw_input,如果要计算表达式,你要加 eval

3.x window 版,还有个bug,\r\n 只被去掉 \n,因此输入 q回车,会得到 q\r
因此你不能判断 word == 'q'

shell command 指 命令和参数
os.system('shell command') 最简单的方法
Execute the command (a string) in a subshell.
缺点:输入输出都在 python shell,你无法在script文件中得到输出

os.popen(command [, mode='r' [, bufsize]]) -> pipe
Runs a shell command and connects to its input or output streams
mode='r'
执行命令,将其输出导入pipe,使用pipe.read readlines
mode='w'
执行命令,将其输入连到pipe,使用pipe.write (2.7 必须使用 raw_input)

os.popen 只能连接ouput 或者Input

subprocess 提供了更多的控制
C:\...\PP4E\System\Streams> python
>>> from subprocess import Popen, PIPE, call
>>> X = call('python hello-out.py') # convenience
Hello shell world
>>> X
0
>>> pipe = Popen('python hello-out.py', stdout=PIPE)
>>> pipe.communicate()[0] # (stdout, stderr)
b'Hello shell world\r\n'
>>> pipe.returncode # exit status
0
>>> pipe = Popen('python hello-out.py', stdout=PIPE)
>>> pipe.stdout.read()
b'Hello shell world\r\n'
>>> pipe.wait() # exit status
0

>>> pipe = Popen('python hello-in.py', stdin=PIPE)
>>> pipe.stdin.write(b'Pokey\n')
6
>>> pipe.stdin.close()
>>> pipe.wait()
0
>>> open('hello-in.txt').read() # output sent to a file
'Hello Pokey\n'

>>> pipe = Popen('python reader.py', stdin=PIPE, stdout=PIPE)
>>> pipe.stdin.write(b'Lumberjack\n')
11
>>> pipe.stdin.write(b'12\n')
3
>>> pipe.stdin.close()
>>> output = pipe.stdout.read()
>>> pipe.wait()
0
>>> output
b'Got this: "Lumberjack"\r\nThe meaning of life is 12 24\r\n'

C:\...\PP4E\System\Streams> python writer.py | python reader.py
Got this: "Help! Help! I'm being repressed!"
The meaning of life is 42 84
C:\...\PP4E\System\Streams> python
>>> from subprocess import Popen, PIPE
>>> p1 = Popen('python writer.py', stdout=PIPE)
>>> p2 = Popen('python reader.py', stdin=p1.stdout, stdout=PIPE)
>>> output = p2.communicate()[0]
>>> output
b'Got this: "Help! Help! I\'m being repressed!"\r\nThe meaning of life is 42 84\r\n'
>>> p2.returncode
0

0 comments: