Sunday, August 15, 2010

python2.4与2.5的异常基类变化

工作中, 有一服务需要从freeBSD迁移到centOS上。其中有一python脚本有些鬼异。
在freeBSD上可以正常, 在centOS上不能正常。最后目标定位在脚本最后的代码上, 大概如下:

try:                           
    if 测试条件:           
        do_someting()
        sys.exit(0)       
    else:                     
        sys.exit(1)       
except Exception:  
        sys.exit(1)       

centOS上, python的版本是2.4的。 freeBSD下安装的是2.5.
旧版本的Exception是基类: 因此在2.4上脚本永远是返回1的。
到了新的版本BaseException才是基类: 因此SystemExit和Exception是同一级。脚本可以返回0

这是迁移后的主要问题。 其实, 应该是作者不知道sys.exit的后果。作者把sys.exit当成os._exit了。
如下是APUE里的一幅图:
标准C里的exit函数在调用后, 不是马上退出程序, 而是运行一些收尾工作。比如把buffer里的数据flush到磁盘等等工作。然后才是调用真正的退出函数:_exit

python的sys.exit是抛出一个异常:SystemExit, python会默认会catch这个异常, 大意为:

try:
    我们的python代码
exception SystemExit:
    做一些收尾工作, 如flush之类的



递归, 循环, 迭代

工作中, 需要有历遍目录进行一些操作. python自带的os.walk很强大, 但是没有maxdepth这种层数参数.

os.walk是原本是一个递归. 下面是它的代码:
273     try:
274         # Note that listdir and error are globals in this module due
275         # to earlier import-*.
276         names = listdir(top)
277     except error, err:
278         if onerror is not None:
279             onerror(err)
280         return
281
282     dirs, nondirs = [], []
283     for name in names:
284         if isdir(join(top, name)):
285             dirs.append(name)
286         else:
287             nondirs.append(name)
288
289     if topdown:
290         yield top, dirs, nondirs
291     for name in dirs:
292         path = join(top, name)
293         if followlinks or not islink(path):
294             for x in walk(path, topdown, onerror, followlinks):
295                 yield x
296     if not topdown:
297         yield top, dirs, nondirs

BTW:: 这里需要注意*nux的ENAMETOOLONG 错误.

递归无法按"层"历遍目录. 它只能一条一条路径走尽.

朋友说使用循环也可以实现递归, 这种代码从来没有写过. 也一直在迷惑。
但是受 http://www.devshed.com/c/a/Python/Basic-Threading-in-Python/2/ 的启发.
突然明白循环是怎么做到递归的。 于是自己使用循环自己写了一个历遍的函数:

def loop_walk(top, n):
    stack = Queue.Queue()
    sub_stack = Queue.Queue()
    dirs = []
    files = []
    error = None
    stack.put([top])
    
    while True:
        if stack.empty():
            # stack被取完了, 下一层队列
            stack, sub_stack = sub_stack, stack
            if n <= 1:
                yield [], [], None
                return
            else:
                n -= 1
            continue
        else:
            try:
                top_list = stack.get(False)
            except Queue.Empty:
                yield [], [], None
                return

        for top in top_list:
            try:
                for item in os.listdir(top):
                    item = os.path.join(top, item)
                    if os.path.isdir(item):
                        dirs.append(item)
                    else:
                        files.append(item)
            except:
                # 出错, 比如没有权限
                #traceback.print_exc()
                error = top
            yield dirs, files, error
            if dirs:
                sub_stack.put(dirs)
            dirs = []
            files = []
            error = None

代码长度double了.........