Archive

Archive for the ‘programming’ Category

推荐个 C 库 sglib

April 8th, 2010 2 comments

SGLIB – A Simple Generic Library for C 完全用宏实现,只有一个头文件。提供了如下功能:

  • sorting arrays
  • manipulating linked lists
  • manipulating sorted linked lists
  • manipulating double linked lists
  • manipulating red-black trees
  • manipulating hashed containers

网页提供的下载链接地址不对,提供个sourceforge的下载地址。下载的 tarball 里有 sample 和 doc,看下就知道怎么用了。

一个我比较喜欢的特点是数据结构完全由用户自己定义,sglib 帮你生成操作数据结构的宏,函数声明以及定义。这样做的好处是不需要因为使用库而对数据结构做修改。相比 queue.h,我觉得 sglib 的更容易使用和理解,而且有更多功能。

Tags: ,

Ch — 一个 C/C++ 解释器

November 5th, 2009 No comments

动态语言很重要的一个功能就是支持交互式的开发,用惯了 Python 有时候非常希望 C 也能有一个解释器来用,尤其是忘了 C 的某些语法想写个简单的例子来测试的时候。

很久以前就搜过 C 的解释器,搜到过 Ch,不记得当时为什么没有试用过。今天下了个免费版本的用了下,很不错,支持 C90 和 C99 的主要功能,C++ 支持不完全(不过 C++ 我基本不关心)。

以前想要测试 C 的某个语法功能时会写个文件,int main 什么的搞一堆,然后用 tcc (Tiny C Compiler) 来测试。tcc 可以把 C 代码的编译和执行放在一步完成,执行 tcc -run foo.c 就可以看到效果了,还算方便。

用 Ch 就更方便了。ch 命令出来个交互式的 shell,输入 C 代码马上执行,调 printf 直接看到效果,输入变量就可以看到它的值(struct 的话可以看到每个成员的值),做点小的测试就不需要写 int main 之类的了。另外 ch 还有函数名补全。

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 的例子,推荐。

编写 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: ,

一份介绍 Unicode 和国际化的幻灯片

June 27th, 2008 5 comments

很久没有更新博客了。做毕设的时候是没时间,做完以后是没了动力。六月马上就要过去了,再不写点这个月就没有文章了。

这次就偷个懒,发一份去年在 Linux 程序设计课上做演讲时所做的幻灯片吧,点这里下载。内容是关于 Unicode 和国际化的。制作这份幻灯片时参考了 Tim Bray 的几篇文章,UTF-8/UTF-16 的 RFC 文档,还有 Arnold Robbins 的 “Linux Programming by Exmaple“。主要内容有:

  • 国际化的相关概念
  • 字符集、字符集编码、语言等概念的澄清
  • 使用标准 C 的 wchar_t 和 locale 来解决国际化问题
  • Unicode 的历史,一些基本概念。如 Unicode 和 ISO 10646 的关系,Unicode 的划分。
  • UTF-16, UTF-8 的编码方法,各自优点和缺点
  • gettext 的使用

最早开始接触编码的问题是自己写文本编辑器的时候,为了支持中文才去了解这方面的知识。这份幻灯片准备的还是蛮用心的,花了一个多星期的时间。准备这份幻灯片的时候对编码之类的问题算是搞的比较清楚了,自己写的文本编辑器支持 UTF-8 编码了,后来碰到数据库链接、网页乱码之类的问题都可以猜出问题大概出在什么地方,看别人的解决办法也不会觉得不明不白。Unicode 的知识对于写程序来说还是很重要的,可惜老师不会讲这种东西(或许有些老师自己也不是很明白),只好自己花时间去弄明白了。希望这份幻灯片能给希望了解 Unicode 和国际化的人提供一些帮助。请原谅,幻灯片里中英混杂比较厉害。

PS:

北大楼

毕业快一个星期了,不过最近还在南京待着。出去转了几个学校,除了南农以外,其他从中央大学分出去的学校都去逛了一下。南师的随园很不错,不过还是更喜欢金陵大学的北大楼。看过东南以后觉得当初南大把中央大学的四牌楼校区留给工学院,文理学院搬去金陵大学也不是太遗憾。不知当初设计出金陵大学北大楼的是何许人也,实在是佩服!唯一的遗憾是北大楼后面现在出现了两座高楼,大煞风景,真是可惜!

南大、东南、南师的老校区进校门都是非常相似的,都是两排高大的梧桐树,南林、河海的校区也有不少的梧桐。要说我对这几个学校校园环境的喜欢程度,南大为首,南师其次,东南和南林(学校里有不少漂亮的树林)并列第三,河海虽然为末,但也不错。

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:

Weak Alias

November 29th, 2007 2 comments

Weak Alias 跟 Weak Reference 完全没有任何关系,不过是我在看到 Weak Reference 的时候想到的而已。

Weak Alias 是 gcc 扩展里的东西,实际上是函数的属性。这个东西在库的实现里面可能会经常用到,比如 glibc 里面就用了不少。抄录一段 gcc 手册里面的话解释下函数属性是干啥的,

In GNU C, you declare certain things about functions called in your program which help the compiler optimize function calls and check your code more carefully.

先上代码,看看 weak alias 怎么写。第一个文件 dummy.c 内容,

#include <stdio .h>
 
/* Do some thing. */
int __foo() {
    puts("I do no thing.");
}
 
int foo() __attribute__ ((weak, alias("__foo")));
</stdio>

weak 和 alias 分别是两个属性。weak 使得 foo 这个符号在目标文件中作为 weak symbol 而不是 global symbol。用 nm 命令查看编译 dummy.c 生成的目标文件可用看到 foo 是一个 weak symbol,它前面的标记是 W。

00000000 T __foo
00000000 W foo
         U puts

而 alias 则使 foo__foo 的一个别名,__foo 和 foo 必须在同一个编译单元中定义,否则会编译出错。

那么这个东西的用处是?看第二个文件,func.c,

#include <stdio .h>
 
int foo() {
    puts("I do something.");
}
</stdio>

这里有一个函数名字是 foo。如果我们编译 func.c 和 dummy.c 得到两个目标文件,当我们同时使用 func.o 和 dummy.o 和其他目标文件进行链接时,如果其他目标文件里面引用符号 foo,最终使用到的是 func.c 中定义的函数,而不是 __foo,虽然它有一个别名 foo。也就是说,我们最终使用到的函数会是“实际做事”的那个函数。当然,单独使用 dummy.o 链接的话使用的是那个“不做事”的函数。如果 dummy.o 中的 foo 不是 weak symbol 的话,在链接时会产生冲突,这就是我们要使用 weak 的原因。

glibc 的实现里面经常用 weak alias。比如它的 socket 函数,在 C 文件里面你会看到一个 __socket 函数,它几乎什么都没有做,只是设置了一些错误代码,返回些东西而已。在同一个 C 文件里面会再声明一个 __socket 的 weak alias 别名 socket。实际完成工作的代码通过汇编来实现,在另外的汇编文件里面会有设置系统调用号,执行 sysenter 或者 int 等动作去请求系统调用。以前看 glibc 里面系统调用的实现的时候郁闷过很久,就是那个时候才知道了 weak alias 这个东西。

Tags:

Weak Reference

November 29th, 2007 No comments

看到了这篇 blog 文章,Understanding Weak Reference。作者说很多 Java 程序员都不知道什么是 weak reference,他的评论至少对我来说是对的。不过这也证明了 Java 的一点好处就是即使你不是很懂 Java 也能用 Java 写程序,不过写的比较烂而已。有人说 Java 程序通常都是一团糟,其实是因为用 Java 写程序的人有许多都不能算合格的 Java 程序员,从这一点上来说这又是 Java 的缺点。扯远了,总之,我觉得我的 Java 不合格,学 Java 还从来没有完整看过哪怕一本书,或许看过了的话就不会连这个在 Java 里有了近一个年代的东西都完全不知道……

回到正题上来。Weak Reference 是什么?其实是跟垃圾收集相关的东西。我做个原文摘要,有兴趣的看原文吧。

有 Weak Reference 当然也有 Strong Reference。其实我们常用的引用就是 strong reference,比如下面代码就会创建一个 strong reference。

Object foo = new Object();

执行上面代码的时候将创建一个对象,变量 foo 中存放了这个对象的 Strong Reference,利用它可以访问到该对象。

记住一点,只要有 Strong Reference 指向对象,这个对象就不会被垃圾收集器回收。Weak Reference 同样用来引用对象,但是 Weak Reference 指向的对象是可能被回收的,比如当没有 Strong Reference 指向它的时候。

那么什么时候可能用到 Weak Reference?看看下面几个场景。

  1. 你想给对象附加一些信息,于是你用一个 Hashtable 把对象和附加信息关联起来。你不停的把对象和附加信息放入 Hashtable 中,但是当对象用完的时候,你不得不把对象再从 Hashtable 中移除,否则它占用的内存变不会释放。万一你忘记了,那么没有从 Hashtable 中移除的对象也可以算作是内存泄漏。理想的状况应该是当对象用完时,Hashtable 中的对象会自动被垃圾收集器回收,不然你就是在做垃圾回收的工作了。
  2. 你想实现一个图片缓存,因为加载图片的开销比较大。你将图片对象的引用放入这个缓存,以便以后能够重新使用这个对象。但是你必须决定缓存中的哪些图片不再需要了,从而将引用从缓存中移除。不管你使用什么管理缓存的算法,你实际上都在处理垃圾收集的工作,更简单的办法(除非你有特殊的需求,这也应该是最好的办法)是让垃圾收集器来处理,由它来决定回收哪个对象。

Weak Reference 这时候就能派上用场了。把对象的 weak reference 放入 Hashtable 或者缓存中,当没有 strong reference 指向他们的时候,对象就可以被垃圾收集器回收了。实际上,有一个 WeakHashMap 就是专门做这个事的。

那么怎样创建对象的 weak reference 呢?
很简单,Java 标准库中有个类 WeakReference,

WeakReference weakref = new WeakReference(ref);

这样 weakref 就是 ref 指向对象的一个 weak reference。要引用这个 weak reference 指向的对象可以用 get 方法。

Object ref = weakref.get();

注意,get 可能返回 null,这时原先指向的对象就不可用了,它可能已经被回收了。

除了 Weak Reference 以外还有 Soft Reference, Phantom references。更多内容除了参考那篇 blog 原文,也可以参考 JDK 文档的 java.lang.ref 包和相关类的文档。我对对象什么情况下可用被回收的解释可能不是完全正确,那个 WeakReference 的文档的解释实在是……如发现错误请纠正。

Tags:

Squeak — 很卡通的 Samlltalk 开发环境

November 6th, 2007 No comments

最近没有怎么看 Common Lisp 方面的东西,时间用来看书和研究 Rubinius 的代码了。(当然,上网也浪费了不少时间 :-( )之前玩了下 Squeak,发现 Smalltalk 跟 Lisp 很像,尤其是他们完全交互式的开发环境,也看到 Ruby 从 Smalltalk 吸收到的东西,比如 block 的语法就跟 Smalltalk 的语法很像,还有对象系统。Squeak 的开发环境好卡通(自己试试看就知道卡通是什么意思了),看了下 image file 包里面的 Welcome 文件,发现这么几行:

Portions of Squeak are:

Copyright (c) 1996 Apple Computer, Inc.
Copyright (c) 1997-2001 Walt Disney Company, and/or
Copyrighted works of many other contributors.
All rights reserved.

Walt Disney!完全没有想到,这是我第一次在软件中看到迪斯尼公司的名字。不过这个倒也解释了为什么 Squeak 的吉祥物是只小老鼠 :-)。

Smalltalk 的开发环境很特别,看起来像是一个完整的桌面系统,有文件浏览器,编辑器,桌面等等,很特别。当然,对这样的环境不是很习惯,因为没有 vim 等工具可以使用了,窗口切换之类的也得用鼠标来(有快捷键的话暂时也不知道)。在现在的图形环境下让一个语言包含这样完整的图形系统可能意义不大,但是 Smalltalk 诞生在 Macintosh 之前,它在那个年代就创建了这样的图形环境,是它影响了 Macintosh,Windows。

这里是一篇 Squeak 的不错的 tutorial,抄一段它对 Smalltalk 历史的介绍:

Smalltalk is undoubtedly one of the most influential programming languages ever developed. It was created way back in the ‘70s and early ’80s at a time when many programmers were still entering code at a command prompt rather than into a half-way decent text editor.

Smalltalk was to change all that. Unlike other languages, Smalltalk had its own dedicated user interface. Its programming environment included a multi-font editor, a graphical debugger, overlapping windows and pop-up mouse menus. Indeed, it was this environment that provided the inspiration both for the Macintosh and, later on, for Windows.

现在看 Smalltalk 的语法总觉得有点诡异,但它作为对 OO 产生重要影响的一个非常有影响力的语言,很多的程序员都认为它是有必要学习一下的。不过时间有限,我没有打算认真学 Smalltalk,只准备做点简单的了解。