Archive

Posts Tagged ‘python’

Get the current module in Python

December 31st, 2009 4 comments

It’s sometimes useful to do introspection on the module itself you are writing. But python doesn’t provide any direct way to support this. In fact a PEP about this feature has been rejected.

A little google find a solution to this problem. Here’s the code

import sys
# get the current module's name
modname = globals()['__name__']
# get the module
module = sys.modules[modname]
# or more simply as suggested by E.T
module = sys.modules[__name__]
Tags:

Reviewboard, PIL and virtualenv

December 2nd, 2009 No comments

I created a separate no site-packages virtualenv directory, and use easy_install to install Reviewboard.

However, easy_install installs PIL version 1.1.7 which does not work with the latest stable Reviewboard version 1.0.5.1.

Using easy_install PIL==1.1.6 doesn’t work because of build error. The workaround is to manually download PIL and install it.

To make reviewboard work under virtualenv, additional steps are needed, which in fact are steps for Django application to work under virtualenv.

  • If you use mod_python and apache, this article maybe useful. This works for Reviewboard.
  • For mod_wsgi, look here.

我也来推荐 bpython

November 12th, 2009 2 comments

在光华上看到 Zellux 推荐的,bpython

bpython 对输入的代码有高亮显示,输入代码同时自动补全,不需要按 tab,调用函数时打完左括号文档就自动出现。

现在只是刚刚试了一下,印象不错。

有些 ipython 支持的功能 bpython 里没有,现在发现的是不支持直接执行 shell 命令。(文件名补全的话当前目录的要用 ./ 以后才会出来。)

Tags:

Shell like data processing in Python — using decorators

October 3rd, 2009 1 comment

前面的文章展示了管道的好处,以及在 Python 程序中利用管道的思想。但是前面文章里的代码还有一点缺陷,看下面的 shell 脚本和 Python 代码的比较:

find logdir -name "access-log*" | \
xargs cat | \
grep '[^-]$' | \
awk '{ total += $NF } END { print total }'
lines = grep('[^-]$', cat(find('logdir', 'access-log*')))
col = (line.rsplit(None, 1)[1] for line in lines)
print sum(int(c) for c in col)

Python 中 find, cat, grep 的调用用一层层的括号嵌套起来进行调用,执行顺序是从最内部的括号开始,可读性没有 shell 脚本好。可不可能在 Python 脚本中用类似 shell 脚本里的语法来提高可读性?用运算符重载就可以做到了。最初的想法来自这里,这个例子用的方法是重载或运算符 “|”。新的 Python 代码如下,我把这样的代码称为 pipe syntax (抽取最后一列并求和的代码不变。)

lines = find('logdir', 'access-log*') | cat | grep('[^-]$')

用运算符重载的方法把一个函数放在管道中必须自己定义一个类,在 override 的 __ror__() 函数中完成实际的工作。每次都要定义一个类我觉得不够方便,因此我用 decorator 来进行简化。

简单来说 decorator 通过用其他对象替换原来的函数来改变函数的行为。对 decorator 的介绍可以参考 Bruce Eckel 的两篇文章,分别介绍了无参数有参数的 decorator 如何创建,无参数的那篇文章还介绍了 decoractor 的作用。Bruce Eckel 认为 decorator 就像是宏一样,可以改变函数的语义。他还认为 Python 的 decorator 就像 Lisp 的宏一样 powerful。对这一点我不太赞同,两者差别还是很大的,decorator 其实只是提供了在运行时动态地替换函数的功能,而 Lisp 的宏是在编译时生成代码,要说 powerful 肯定还是 Lisp 的宏更强,但 decorator 更简单而且也已经足够 powerful 了。

使用 decorator 来定义 cat 的代码如下,函数前的 “@” 是为了支持 decorator 而引入的新的语法:

@pipeable
def grep(iter, match):
    if callable(match):
        fun = match
    else:
        fun = re.compile(match).match
    return ifilter(fun, iter)
 
# Without @pipeable before the function definition of grep,
# we can use the following code to achieve the same effect.
grep = pipeable(grep)

pipeable 其实只是一个普通的 Python 对象,可以是一个函数,也可以是一个类。如果是函数,那么 grep 就是给它的参数;如果是类,grep 是给它的初始化函数的参数。pipeable(grep) 必须返回一个能够调用的对象(含有 __call__() 方法),除此之外没有其他要求。可见,decorator syntax 只是语法糖而已,但这个语法糖使得我们可以在函数之前加上修饰,而且从代码上一下就可以看出 grep 可以用在管道中。

下面首先描述 pipeable 修饰函数的要求和修饰后的行为,然后再看 pipeable 的实现。

可以用 pipeable 进行修饰的函数只有一个要求,第一个参数必须支持遍历操作。如果这个函数之后还有其他管道,那么函数的返回值也需要支持遍历。

被 pipeable 修饰之后,函数的行为如下:

  1. 可以像没有修饰过时一样调用,函数行为不变
  2. 调用时给定除了第一个参数以外的所有参数,当函数对象出现在 “|” 右侧时,将左侧对象作为函数的第一个参数,与之前的参数一起完成函数的调用。

用来实现 pipe syntax 的 decorator 如下(完整代码shelike.py)。

from functools import update_wrapper
class pipeable:
    def __init__(self, method):
        self.func = method
        self.args = []
        self.kwds = {}
        self.reqlen = 0
        # Since we need to allow classes to be used in pipe, there are cases that
        # method is not a function.
        if hasattr(self.func, 'func_code'):
            self.reqlen = self.func.func_code.co_argcount
        # makes the wrapper object looks like the wrapped function
        update_wrapper(self, method)
 
    def __call__(self, *args, **kwds):
        curlen = len(args)
        # An ugly hack to handle classes.
        if curlen == self.reqlen or 0 == self.reqlen:
            return self.func(*args, **kwds)
        elif curlen != self.reqlen - 1:
            raise TypeError('Arguments number wrong.')
 
        cpy = deepcopy(self)
        cpy.args = args
        cpy.kwds = kwds
        return cpy
 
    def __ror__(self, iter):
        return self.func(iter, *self.args, **self.kwds)

由于修改后的对象需要支持 “|” 运算符,pipeable 用类的形式实现更方便。(看了 Bruce Eckel 的文章后我也偏好用类来创建 decorator。)

  1. __init__() 接受要修饰的函数,保存起来以备后面调用,这个函数在函数被修饰时执行,实际上就是创建一个pipeable 对象,把原先函数名赋给这个对象。最后的 update_wrapper 把被修饰函数的 __name__, __doc__, __module__ 属性拷贝到创建的对象上,这样这个对象看起来就更加像原先的函数。(否则这些属性的值的都是这个对象的值。)
  2. __call__() 在被修饰函数/替换的对象被调用时执行,这里根据参数个数直接调用原函数或者把参数保存起来
  3. __ror__() 就是实现 pipe syntax 的关键,把 “|” 左边的序列和其他参数组合起来完成被修饰函数的调用。有些 ugly hack 是为了处理 class 作为初始化参数的情况

要把 shell 里的常用命令一个个用 Python 再实现一下也比较麻烦,因此我还实现了一个函数用来把 Python 里的数据转成字符串直接调用 shell 命令来处理,处理完再转成一行一行的 Python 字符串。比较下用 pipeable 和自己定义一个 class 的实现代码(现在的处理方式是把输入一次性全部转成字符串通过 OS 的管道传给外部程序,数据量大的话内存开销比较大,但我没有想到好的解决办法。)

@pipeable
def shell(iter, cmd=None):
    pipe = Popen(cmd, shell=True, stdin = PIPE, stdout = PIPE)
    return pipe.communicate(''.join(iter))[0].splitlines(True)
 
class shell:
    def __init__(self, cmd):
        self.cmd = cmd
    def __ror__(self, iter):
        pipe = Popen(self.cmd, shell=True, stdin = PIPE, stdout = PIPE)
        return pipe.communicate(''.join(iter))[0].splitlines(True)

有了这个管道,就可以把求和的任务直接用 awk 来完成了。

sumtmp = lines | shell("awk '{ total += $NF } END { print total }'") | aslist
print int(sumtmp[0])

当然,如果不想依赖外部程序,可以写一个提取字符串第 n 列/最后一列的函数,作为 tr 的参数放入管道(这个函数的名字不太好,其实就是 map,代码见shelike.py),最后转成整数以后再用 sum 求和即可。

print (lines | tr(last_column) | tr(int) | sum)

我很喜欢看这样的代码,有点函数式的味道,而且可读性也很好 :)

有兴趣的话 decorator modulePythonDecoratorLibrary 提供了更多使用 decorator 的例子,推荐。

Python 的 iterator protocol 和 generator

April 20th, 2009 3 comments

前一篇文章最后的代码效率很差。由于使用 list 保存临时的计算结果,所有文件内容会同时读入内存,其构建的管道类似下图所示:

list-pipe

list 中的元素是先通过一个过滤器,存起来,再通过下一个过滤器。这样做的坏处是

  1. 浪费内存
  2. 浪费时间,来回的在临时 list 中进行存取需要时间,另外临时 list 的内存分配,垃圾收集都会增加时间开销

我们希望的管道其实如下:

generator-pipe

list 中的每个元素应该一次性通过所有的 filter,只在最后存在一个 list 中。更理想的情况下,一开始的 list 也不应该出现。

对于像 Haskell 这样默认使用 lazy evaluation 的语言来说,前面的函数连续调用默认行为就是如此。只有当管道末端需要一个元素时才会从管道头读取一个元素,经过所有过滤器处理以后得到结果,如果没有读取第二个元素,则管道头的其他元素根本不会被处理。

要在程序中使用管道的风格又必须要避免临时数据的产生,然而 python 中没有直接对 lazy evaluation 提供支持,好在 python 有 iterator protocol 和 generator。

首先介绍 python 的 iterator protocol,了解它才知道 for 语句进行遍历时究竟做了些什么。iterator protocol 使得我们可以对不同的容器(其实是任何支持遍历操作的对象,不一定是用来存放数据的容器)使用相同的方式(for 语句)进行遍历。容器可以是 list, dictionary 或者是其他用户定义的数据结构,它只需要实现 __iter__()方法,返回一个 iterator object 即可。真正关心如何遍历的是 iterator object,它的两个方法构成了 iterator protocol

  1. __iter__() 返回自身
  2. next() 返回容器中的下一个对象,没有更多对象时应 raise
    StopIteration,一旦抛出此异常,后续的调用一定也必须抛出此异常。

偷懒起见,直接拿 Beazley 的幻灯片里的例子来说明:

 class countdown():
    "container"
    def __init__(self, start):
        self.count = start
    def __iter__(self):
        return countdown_iterator(self.count)
 
 class countdown_iterator(object):
    "iterator object"
    def __init__(self, start):
        self.count = start
    def __iter__(self):
        return self
    def next(self):
        if self.count >= 0:
            raise StopIteration
        r = self.count
        self.count -= 1
        return r

for 语句首先对要遍历的对象调用 __iter__() 方法得到 iterator object,然后对 iterator object 不停调用 next() 方法直到遇到 “StopIteration” 异常为止。由此, 下面代码中的 for 语句和 while 循环等价:

 cd = countdown(2)
 
 for i in cd:
    print i
 
 _iter = cd.__iter__(cd) # Get iterator object
while 1:
try:
    x = _iter.next() # Get next item
    print x
except StopIteration: # No more items
    break

由于 iterator object 的 __iter__() 返回自身,因此 iterator object 也可以用在 for 语句中。实际上可以把 for 语句看成是一种语法糖,但它是一种重要的语法糖,因为它大大简化了程序的编写,看看上面例子比较下就知道了。

为了使得容器支持遍历操作而要另外定义一个 iterator 类,这跟 C++ 的 STL 有些类似。使用 generator object 可以不需要另外定义 iterator 类从而更方便的实现 iteration protocol。具体做法如下:

 
 class countdown():
    "container"
    def __init__(self, start):
        self.count = start
    def __iter__(self):
        it = self.count
        while it > 0:
            yield it
            it -= 1

一旦在函数定义中使用 yield,这个函数就成为 generator function 而不是普通的函数。 generator function 被调用时会返回 generator iterator,简称 generator。调用 generator 的 next() 方法时,generator function 的函数体会执行,当遇到 yield 时,generator 的状态会冻结,而当前值被返回。

lazy evaluation, generator 的其他用处

利用 generator 可以模拟出 lazy evaluation 来。lazy evaluation 我觉得最有用的地方在于,

  1. 使用管道风格的代码处理数据时,避免产生大量临时的结果,提高性能
  2. 处理无限长度的数据
  3. 避免不必要的求值

但 generator 不光可以用来处理数据,Beazley 的幻灯片里提到 networking, threads, co-routines 等都可以用 generator 来处理。

编写 Unix 管道风格的 Python 代码

January 28th, 2009 1 comment

先推荐一份幻灯片,David Beazley ("Python essiential reference", PLY 的作者) 在 PyCon’2008 上报告的幻灯片,强烈推荐!!这篇文章的很多内容都来自或者受这份幻灯片的启发而来。

在上一篇文章里介绍了 Unix 管道的好处,那可不可以在写程序时也使用这样的思想呢?当然可以。看过 SICP 就知道,其实函数式编程中的 map, filter 都可以看作是管道思想的应用。但其实管道的思想不仅可以在函数式语言中使用,只要语言支持定义函数,有能够存放一组数据的数据结构,就可以使用管道的思想。

一个日志处理任务

这里直接以前面推荐的幻灯片里的例子来说明,应用场景如下:

  • 某个目录及子目录下有一些 web 服务器的日志文件,日志文件名以 access-log 开头
  • 日志格式如下

    81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238
    81.107.39.38 - ... "GET /ply HTTP/1.1" 304 -
    

    其中最后一列数字为发送的字节数,若为 ‘-’ 则表示没有发送数据

  • 目标是算出总共发送了多少字节的数据,实际上也就是要把日志记录的没一行的最后一列数值加起来

我不直接展示如何用 Unix 管道的风格来处理这个问题,而是先给出一些“不那么好”的代码,指出它们的问题,最后再展示管道风格的代码,并介绍如何使用 generator 来避免效率上的问题。想直接看管道风格的,点这里

问题并不复杂,几个 for 循环就能搞定:

sum = 0
for path, dirlist, filelist in os.walk(top):
    for name in fnmatch.filter(filelist, "access-log*"):
        # 对子目录中的每个日志文件进行处理
        with open(name) as f:
            for line in f:
                if line[-1] == '-':
                    continue
                else:
                    sum += int(line.rsplit(None, 1)[1])

利用 os.walk 这个问题解决起来很方便,由此也可以看出 python 的 for 语句做遍历是多么的方便,不需要额外控制循环次数的变量,省去了设置初始值、更新、判断循环结束条件等工作,相比 C/C++/Java 这样的语言真是太方便了。看起来一切都很美好。

然而,设想以后有了新的统计任务,比如:

  1. 统计某个特定页面的访问次数
  2. 处理另外的一些日志文件,日志文件名字以 error-log 开头

完成这些任务直接拿上面的代码过来改改就可以了,文件名的 pattern 改一下,处理每个文件的代码改一下。其实每次任务的处理中,找到特定名字为特定 pattern 的文件的代码是一样的,直接修改之前的代码其实就引入了重复。

如果重复的代码量很大,我们很自然的会注意到。然而 python 的 for 循环实在太方便了,像这里找文件的代码一共就两行,哪怕重写一遍也不会觉得太麻烦。for 循环的方便使得我们会忽略这样简单代码的重复。然而,再怎么方便好用,for 循环无法重用,只有把它放到函数中才能进行重用

(先考虑下是你会如何避免这里的代码的重复。下面马上出现的代码并不好,是“误导性”的代码,我会在之后再给出“更好”的代码。)

因此,我们把上面代码中不变的部分提取成一个通用的函数,可变的部分以参数的形式传入,得到下面的代码。

def generic_process(topdir, filepat, processfunc):
    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist, filepat):
            with open(name) f:
                processfunc(f)
 
sum = 0
# 很遗憾,python 对 closure 中的变量不能进行赋值操作,
# 因此这里只能使用全局变量
def add_count(f):
    global sum
    for line in f:
        if line[-1] == '-':
            continue
        else:
            sum += int(line.rsplit(None, 1)[1])
 
generic_process('logdir', 'access-log*', add_count)

看起来不变和可变的部分分开了,然而 generic_process 的设计并不好。它除了寻找文件以外还调用了日志文件处理函数,因此在其他任务中很可能就无法使用。另外 add_count 的参数必须是 file like object,因此测试时不能简单的直接使用字符串。

管道风格的程序

下面考虑用 Unix 的工具和管道我们会如何完成这个任务:

find logdir -name "access-log*" | \
xargs cat | \
grep '[^-]$' | \
awk '{ total += $NF } END { print total }'

find 根据文件名 pattern 找到文件,cat 把所有文件内容合并输出到 stdout,grep 从 stdin 读入,过滤掉行末为 ‘-’ 的行,awk 提取每行最后一列,将数值相加,最后打印出结果。(省掉 cat 是可以的,但这样一来 grep 就需要直接读文件而不是只从标准输入读。)

我们可以在 python 代码中模拟这些工具,Unix 的工具通过文本来传递结果,在 python 中可以使用 list。

def find(topdir, filepat, processfunc):
    files = []
    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist, filepat):
            files.append(name)
    return files
 
 def cat(files):
    lines = []
    for file in files:
        with open(file) as f:
            for line in f:
                lines.append(line)
    return lines
 
 def grep(pattern, lines):
    result = []
    import re
    pat = re.compile(pattern)
    for line in lines:
        if pat.search(line):
            result.append(line)
    resurn result
 
lines = grep('[^-]$', cat(find('logdir', 'access-log*')))
col = (line.rsplit(None, 1)[1] for line in lines)
print sum(int(c) for c in col)

有了 find, cat, grep 这三个函数,只需要连续调用就可以像 Unix 的管道一样将这些函数组合起来。数据在管道中的变化如下图(简洁起见,过滤器直接标在箭头上 ):

data-pipe

看起来现在的代码行数比最初直接用 for 循环的代码要多,但现在的代码就像 Unix 的那些小工具一样,每一个都更加可能被用到。我们可以把更多常用的 Unix 工具用 Python 来模拟,从而在 Python 代码中以 Unix 管道的风格来编写代码。

不过上面的代码性能很差,多个临时的 list 被创建。解决的办法是用 generator,因为篇幅比较长,具体做法放到下一篇文章中。

Tags: ,

对 Python 和 Ruby 的一点看法

February 27th, 2008 15 comments

还是从我在 Ruby 和 Python 上的经历说起吧。

大二暑假的时候学了 Ruby,但是到现在为止只用 Ruby 做过文本处理,还有一次做 Linux 网络编程作业的时候用 Ruby 实现了一个服务器的原型,还稍微尝试过一下 RoR,不过因为我对 Web 开发没什么兴趣,所以是浅尝辄止而已。平时自己机器上能够用到的 Ruby 程序也只有 deplate 而已。

今年寒假的时候开始考虑毕设的事情,考虑实现语言的时候也曾考虑过 Ruby。但是我要用的 dazuko 的 Ruby 绑定可能存在内存泄漏的问题,引起内存泄漏的原因应该在 Ruby 的实现上。考虑到 Python 的实现更成熟,对于要求稳定的系统程序来说可能更为合适而开始考虑 Python。于是开始看 Dive Into Python,被这本书吸引而继续学下去。我之前的介绍过这本书,很欣赏它。

最经开始更多的实际使用 Python 了,很自然的会想把它跟 Ruby 做些比较。因为我对 Ruby 和 Python 的语法和特性的了解只是一般而已,所以对语法的比较我说不出太多的东西。在我看来语法上来看它们相似的地方多于不同的地方,在它们中进行切换是很方便的。我很喜欢 Ruby 的 block 语法,但 Python 有 list comprehension 和 generator,很多 block 的用法都可以用它们来完成,所以我并不会在使用 Python 的时候非常的渴望 Python 也有 block 语法(仅有的时候是文件打开完成任务以后自动关闭这种地方,Python 里还没有很好的解决)。就语言本身来说我更喜欢 Ruby,个人觉得用 Ruby 写程序更为舒服,而且通常完成同样任务的 Ruby 程序也比 Python 更短。但是 Python 的优秀的社区使我选择 Python 而不是 Ruby。

在 Python 和 Ruby 中我只想选一个,用它来完成一些 quick and dirty 的任务,我希望一切越简单越好,最好就是别人已经帮我完成了,直接拿来用就可以。随着越来越多的使用 Python,看到 Python 社区所完成的工作,我觉得 Python 才是这一任务更好的选择。Guido 在一次访谈中说:

对于杀手锏应用(killer application),我个人并不十分迷信。如果你太看重杀手锏应用的话,实际上你可能会把焦点放错地方,或者你可能太专注于某一个方面。

以前对这话觉得不屑,觉得是酸葡萄心里而已,现在越来越觉得 Guido 是对的。Python 现在已经进入很多领域,在科研领域,已经出现像 NumPy, SciPy 这样优秀的软件。我也看到生物信息学研究者写的用 Python 编程来处理生物信息学问题的文章。在图形处理方面也有了优秀的软件,如 PIL。而 NASA, Google 等组织也在大量使用 Python。而在 Web 开发框架上,Python 有不止一个,另外还有像 twisted 这样高层的互联网应用程序开发框架。实现上 CPython 比 CRuby 也要成熟,两者都有许多实现,以前很看好 Rubinius,但现在我知道了 PyPy,如果 Rubinius 能够使得 Ruby 在性能上有很大的提升,那 PyPy 肯定也能,事实上 PyPy 现在应该比 Rubinius 更成熟。而我现在自己常用的 VCS 工具 Mercurial 就是用 Python 实现的。

从使用的广泛程度来说 Python 目前远超过 Ruby,记得 2008 年 1 月的程序设计语言使用广泛度统计中 Python 排名首次超过 Perl,而 Ruby 的排名反而下降了。不知道是因为 Python 有大量优秀的软件导致大量的使用,还是因为大量的使用导致有如此多优秀的软件,但现在的社区已经使得这两个方面可以互相促进,形成一个良性循环了。如果 Python 社区的注意力都集中在 Web 开发上,或许它可以出一个类似 RoR 的 killer application,但它就不会产生如此之多不同领域的优秀软件,也会使得 Python 的使用达不到如此广泛的程度。在我看来这是得不偿失的。

即使我不喜欢 Python 中一些 Guido 强加给我的限制,但是只要它能够让我把那些 quick and dirty work 以最快的速度最小的精力做完,我就不在乎,而那些限制在构建大型软件上可能反而能够带来好处。如果哪天 Ruby 能够像 Python 社区这样成熟,提供更多的优秀的软件,那我在解决个人的 quick and dirty work 时就会选择使用 Ruby。但是现在,我选 Python。

我仍然会花时间去学 Common Lisp,但 CL 现在不擅长跟 OS 和其他基础设施交互,所以我不会用它来完成日常的工作。我目前只打算想把它作为一个研究性质的语言,满足我对程序设计语言本身的兴趣。一方面,SLIME 提供的交互式的开发环境更加合适探索性的任务(ipython 虽然不错,但是跟 SLIME 和 CL 提供的环境比起来还是差了点);另一方面,正因为有许多工作在 Common Lisp 中还没有完成,我可以尝试自己来研究解决,在其中得到锻炼。我也希望在真正领悟 Common Lisp 的时候我能够成为一个更好的程序员。

语言的选择并不是至关重要的,但是我希望能够使用一个使得编程更加有趣的语言。以前在语言上花了太多的时间,现在决定把语言逐渐固定下来,把注意力放到更加重要的问题上,为我以后的研究做准备。

PS:
现在在浦口每天的时间都可以自己支配,太爽了 :-)

不过想看的书太多了宏观经济学(微观已经看完了),算法导论,线性代数,Ansi Common Lisp,还要每天在毕设上投入一点时间(现在还在准备阶段),再加上要利用本科最后这个学期好好练小提琴,有点安排不过来了。为了集中精力,有些书看了一点又要停下来了。

最近回到浦口真开心,爱死浦口的食堂了 :-) 我现在的日子跟查师兄去年的比真是太舒服了,这两天有点堕落,明天起要鞭策自己努力了。

Python 中 for 循环语句的 else 从句

January 29th, 2008 2 comments

昨天用 python 写 insertion sort 练练手,在用 for 循环的时候遇到了一点小问题。

Dive Into Python 这本书的确不适合初学程序设计的人,我看完第 6 章都没有看到 python 中的 while 语句,所以最初我用了两个 for 循环来实现,xrange 也是我看别人的程序的加上查文档才知道怎么用的。对有经验的人来说这样也没什么不好,python 的语言和类库参考都不错,直接查也很方便。

最初的实现出错了,代码如下:

def insertion_sort(list):
    for i in xrange(1, len(list)):
        key = list[i]
        for j in xrange(i - 1, -1, -1):
            if key < list[j]:
                list[j + 1] = list[j]
            else:
                break
        list[j + 1] = key
    return list

随便试了几个就错了,很奇怪为什么会出错。后来才意识到使用 for 遍历的是一个列表,如果没有 break,遍历完之后 j 是列表中的最后一个元素 0,而不是 -1。如果因为列表遍历完成而非 break 中断循环,则最后应该执行 list[0] = key,但实际执行的是 list[1] = key。

这样就有个问题,如何判断 for 循环是正常遍历结束还是被 break 中断的?在 C 中写 for (j = i - 1; j > -1; j--) 最后循环结束的时候 j 为 -1,所以检查 j 的值就可以了。但在 python 里面,j 最小只可能到 0,循环结束时 j 为 0 可能是因为 key >= list[j] 而 break,也可能因为 0 是最后一个元素而终止循环,所以通过检查 j 的值是没法做到的。(因为 python 中 for 的作用是遍历列表等容器,所以不能让 for 与 C 中有相同的行为。)

查了下语言参考,发现 for 语句可以跟一个 else 块,觉得有点奇怪,还没有见过哪个语言有这样的语法的。文档说 else 后面的块在循环条件为假时执行一次,但如果 for 后面的块中因为 break 导致循环结束则这个块不执行。一开始觉得这个 else 没什么用,除非死循环,否则条件总是会变为假,那直接把代码放在 for 外面不就可以了。后来想到这个特性正好可以用来判断循环终止的原因,用个条件变量即可,在 else 代码中改变这个条件变量,然后在后面的代码中检查条件变量的值即可。

最后的代码如下:

def insertion_sort(list):
    for i in xrange(1, len(list)):
        key = list[i]
        outbound = False
        for j in xrange(i - 1, -1, -1):
            if key < list[j]:
                list[j + 1] = list[j]
            else:
                break
        else:
            list[0] = key
            outbound = True
        if not outbound:
            list[j + 1] = key
    return list

当然用一个 while 循环就可以不用这么麻烦了,不过我就是想知道用 for 该怎么解决这个问题。

PS:
wordpress 有没有什么贴代码用的插件?

Tags:

Dive Into Python

January 26th, 2008 1 comment

我觉得很不错的一本讲 Python 的书。Dive Into Python,适合有经验的程序员,GNU Free Documentation License 下发布。

每一章先上一段代码,通过这些代码解释语言的特性,没有什么废话,很快就涉及语言里比较高级的特性(第 4 章就开始讲 introspection 了。Dave Thomas 的 Programming Ruby 这本书就感觉太罗嗦,因为没有耐心看最后几章,Ruby 的 introspection 特性我都不怎么了解。)

去年就学了 Ruby,不过没有用它做过实际的项目,只写过一些小的文本处理的脚本而已。毕设我不打算用 C,开发效率太低了,Java 已经腻了,而 Ruby 糟糕的实现始终有点让人担心,对 Common Lisp 还是没有把握。看到今年的语言流行度,加上毕设可能要用的一个重要的类库只有 Python 的绑定,还是决定学下 Python 了。初步印象还不错,不过还是很喜欢 Ruby 里面的 block。

Tags: ,