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.

更新 zsh 的 command hash table

November 14th, 2009 4 comments

zsh 下新装了一个软件,tab 补全时新装软件的命令不会出现。以前的解决办法是执行 export PATH=$PATH 让 zsh 去更新缓存。

今天在 Pylons 的 activate 脚本里看到了一条 builtin 命令 hash (man zshbuiltins),这条命令可以直接修改 command hash table。$PATH 路径下的内容发生变化时可以用 hash -r 来更新。

Tags: ,

screen & LD_LIBRARY_PATH

November 13th, 2009 No comments

这篇 post 居然是在今年 1 月份的时候放到 draft 里,到现在才 publish……

因为用 intel 的编译器,所以设置了 LD_LIBRARY_PATH 这个环境变量,但是每次启动 screen 后这个环境本来都会被 unset。google 到的结果

screen 可执行文件是 setuid 的(为了 share session,debian 的 screen 安装时默认没有 setuid),glibc 对这样的可执行文件会把那些“危险”的环境变量去掉,所以出现了上面的情况

解决办法两个

1. 把 setuid 位去掉,当然这样 screen 的 share session 就不能工作了,还好一般用不到
2. 把 LD_LIBRARY_PATH 的设置放到 .zshrc/.bashrc 之类的文件里去,这样每次启动 shell 的时候自然会把这个环境变量读入的

if [ -z $LD_LIBRARY_PATH ]; then
    export $LD_LIBRARY_PATH=XXX
else
    export $LD_LIBRARY_PATH=$LD_LIBRARY_PATH:XXX
fi

LD_LIBRARY_PATH 这个环境变量设置起来还真的挺麻烦的,系统的库目录和自己的库目录下有同名不同版本的库的时候,不是自己的程序有问题就是系统的程序有问题。

Tags: , , ,

zsh + screen

November 12th, 2009 No comments

screen 提供多个 shell 来回切换是很方便,不过有时会忘记应该切换到哪个 window。如果可以根据执行的命令和当前目录来动态设置 window title 的话来回切换时就会可以方便的找到目标 window。其实 zsh-lovers 里就有说明。

screen 可以通过 echo 特殊的字符来设置 window 的 title,而 zsh 有两个特殊的函数 preexec 和 precmd,前者在用户输入命令按下回车但 zsh 还未执行命令前被调用,后者在 zsh 更新 prompt 前被调用(具体说明见 man zshmisc)。把 zsh 和 screen 的功能结合起来就可以在执行命令时把 screen window 的标题设置成当前执行的命令,而在 zsh 等待用户命令时,将 title 设置成当前目录

先贴 zsh 的相关配置,PS1 的配置不喜欢可以去掉。

autoload colors
colors

case $TERM in
    screen*)
        function sctitle() { print -Pn "\ek$1\e\\"}
        function precmd() { sctitle "%20< ..<%~%<<" }
        function preexec() { sctitle "%20>..>$1%< <" }
        export PS1="%{${fg[cyan]}%}[%D{%H:%M} %20<..<%~%<<]%{$reset_color%} "
    ;;
    *)
        export PS1="%{${fg[cyan]}%}[%D{%H:%M} %n@%m:%20<..<%~%<<]%{$reset_color%} "
    ;;
esac

有时命令或者当前目录很长,screen 的 status bar 宽度会不够,zsh 有内置的截短字符串的功能,用 %20< ..<%~%<< 限制目录最长为 20 个字符,把左边的多余字符去掉,截短后用两个点表示,%~ 表示当前目录,如果包含 HOME 目录则用波浪号表示,%<< 标志截短操作的结束。%20>..>$1%< < 类似,preexec 的第一个参数是完整的命令行输入,同样截短成 20 个字符,不过是去掉右边的多余字符。sctitle 就是打印特殊字符来设置 screen window title 的函数。

接下来是 .screenrc。

startup_message off
# use visual bell
vbell off
# replace Ctrl-A by `
escape ``
# set a big scrolling buffer
defscrollback 5000
# Set the caption on the bottom line
caption always '%{= kg}[%{G}%H%{g}][%= %{= kw}%?%-Lw%?%{+b r}(%{y}%n %t%?(%u)%?%{r})%{= w}%?%+Lw%?%?%= %{g}][%{B} %d/%m %{W}%c %{g}]'
# %{= kG} first set default color (=), back ground black (k), foreground green (G)
# [%{G}%H%{g}] color bright green, host name (%H), color green
# [%= ...] padding (%=), window left to the current focus window if exists,
#      (current focus window with color yellow), window to the right of the
#      focus window
# [%{B} %m/%d %{W}%c %{g}] color bright blue, month/date (%m%d), color bright white,
#      current time(%c), color green

# open several terminals at startup
screen 5
screen 4
screen 3
screen 2
screen 1

因为我用 vim,而且我觉得切换 window 时要按数字键,所以我把 escape 设置成 back tick。这个配置最关键的就是 caption 了,忘记从哪里 copy 过来的了(用 hardstatus 的话 screen 的消息也是在 hardstatus 上显示,所以我喜欢用 caption)。我直接在配置文件里加了点注释,语法很恶心,我都看晕了。关于 caption/hardstatus 里的转义字符说明见 man screen 的 string escape 一节。

试试看吧,这个配置还是很炫而且也蛮实用的。

Tags: ,

我也来推荐 bpython

November 12th, 2009 2 comments

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

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

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

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

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

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