Mage from hell

2010年8月2日 星期一

介绍一个Vim的插件:Slime.vim

http://technotales.wordpress.com/2007/10/03/like-slime-for-vim/

Slime不是Vim的原生插件,它的创意来自于玩Emacs的阶级兄弟。它的功能很简单,就是将Vim的一个寄存器中的内容传送到screen的一个window中去执行。

这东西有什么用呢?最有用的地方在于简化REPL(read-eval-print loop)这个循环,降低程序员调试代码的时间成本。对于Python这个问题其实不是那么突出,因为Python的命令行程序对于以文件形式作为输入的 python代码很友好。但是其他语言的命令行程序似乎不是这样。Slime的动力就是Ruby和Lisp这样的语言。开一个Clojure的命令行交互界面麻烦的很,而且它的功能和Python命令行没得比,更不用说ipython了。所以Slime看起来就非常好用了。

2010年7月6日 星期二

读《Coders At Work》前三章

我水平比较低,看了前几章就感觉学到了许多,不由得想写一写,为自己总结一下。

人在不同的时间、环境和机遇下所渴望得到的知识是不同的,这些只是现在的我所体味到的好东西。但书中还讲了很多,但也许我以后再读才能品出其 中的滋味,也许你现在就能品出来。这也就是一本好书的魅力所在吧

第一章出场的大牛是Jamie Zawinski。对他的采访给我印象最深刻的地方是他的成长。他高中接触计算机之后通过参加地方性的 User Group聚会认识了卡耐基梅陇大学(CMU)的老师,然后屁颠屁颠的跑去“实习”。Jamie在CMU读了本科,然后仍然是依靠这位老师找到 了自己的第一份工作。之后他又遇到了Peter Norvig,并跑到Berkerly去了。他的学会的第一门计算机语言是Lisp,他写出了 XEmacs,他从不信任gdb,他是Netscape的主要开发者之一,我们每天看到的XScreenSaver也是他的习作。这需要怎样的天分、机遇 和努力,才能成就这一切呢?现在的Jamie是一家夜店的老板,已经不再写程序了。这似乎真的有一丝“哥已不在江湖,而江湖上还流传着哥的传说”的意味啊

第二位出场的大牛是Brad Fitzpatrick。对他的采访给我印象最深刻的地方是他的创业历程。LiveJournal从无到有,从小到大,都是他努力的成果。他可以今天写 Javascript,第二天却考虑Linux内核的网络算法。Things are always on fire! 今天经验不足的程序员时常会犯过 度设计的毛病,而那时的他却没有任何可以参考的东西。他也提到如果知道自己的网站能有这么大的发展和负载,肯定会好好的考虑架构啊算法啊之类的东西。但是 世界是没有如果的,他的网站很幸运的生存了下来。这就是一个产品是如何进化的。

第三位出场的大牛是Douglas Crockford。对他的采访给我音像最深刻的地方是他对代码阅读的强调。什么是好代码?在他看来,好 代码的第一要义就是可读性(readability),其次是正确性(currectness),最后才是效率(efficiency)。当然,这可能和 Javascript的特性有关,毕竟这的确是一门非常难以驾驭的语言。他谈到了他和他的同事们是如何进行code reading的,也谈到了他在面试 时对面试者有怎样的偏好。我在看这本书之前由于Javascript的缘故就看过几部Douglas的演讲,他是一个非常可爱的白胡子老头。即使你没有时间,至少也应该看看他的《The JSON Saga》 的分享,非常有意思。

这三篇采访都提到了一些共有的话题,其中一个是问你是如何设计一个系统的,是top-down,还是bottom-up,抑或是 middle-out?不同的人有不同的习惯。我对这么问题的印象这么深是因为在我注意到它之前,有人问过我一个类似的问题,而我的回答很悲剧。回头再读 此书看到这个问题,不由的生出“从前有一份答案摆在我的面前,而我没有珍惜……”的哀叹啊

2010年6月22日 星期二

Things I learned from python-list recently

1. 有了list comprehension还要map函数干什么?需要么?不需要么?

map函数只在一种情况下比list comprehension有优势,那就是它可以被当做一个参数被传递给其他函数。当然,一般来说大家也很少这么干……


2. ord() 只能产生无符号整数(unsigned char, 0-255),如果你在一个字节一个字节的读取流,那么把这每个自己转换成有符号整数的方法有很多:

(a) 用标准库里的 array 模块
(b) 用标准库里的 struct 模块
(c) 用 bytearray 数据类型
(d) signed = unsigned if unsigned <= 127 else unsigned - 256
     signed = (unsigned & 127) - (unsigned & 128)
     signed = (unsigned & 127) * 2 - unsigned
     signed - unsigned - 2 * (unsigned & 128)


3. evaluate 1^2+2^2+3^2-4^2-5^2+6^2+7^2+8^2-9^2-10^2+...-2010^2, where each three consecutive + must be followed by two - (^ meaning ** in this context)

>>> sum((1, 1, 1, -1, -1)[(x-1) % 5] * x**2 for x in xrange(1, 2011))


4. 应该小心的将反斜杠(\)作为行延续符(line continuation)使用。虽然我在pep8中和其他关于pythonic的文档中都看到了这一点,但我在维护现有代码的时候并没法更多的实践这一点,因为这些代码的作者甚至不知道pep8的存在,更不用说什么将每行长度限制在80个字符以内的狗屁龟腚了。

5. 函数也是可以被pickle的,但是不能被marshal

2010年6月5日 星期六

让url适应浏览器

今天看到一篇有趣的文章:http://benlast.livejournal.com/29164.html

起因是Firefox缓存哈希算法的一个bug,会使资源不能够充分的缓存。Google意识到了这一点,并且对自己的url做了一些相应的变化,以避开Firefox的这个bug,使更多的网站图片的到缓存,改善用户体验。

一般来说我们优化网站的思路都是从网站本身去找原因,缓存、js、静态页面等等,这是我第一次意识到不仅浏览器应该适应网站,网站也可以主动去适应浏览器。

当然,也只有像Firefox这样开源的项目才可以让你去适应,IE的bug你可能知道么?

当然,也只能敬佩Google中的强人能想到这个主意,哪个公司的工程师能在维护这样一个网站的同时还熟悉浏览器的各种缺陷?

2010年5月30日 星期日

[非技术] 战争进行时

现在三更半夜的,我在一台陌生的电脑上写这篇blog。女友加班,我来接她,但是看来事还没做完,所以我临时找了一台她同事的电脑上网。今天我也加了一天的班,任务就是让毒霸变得更好,让360去见鬼。
 
珠规院的工作环境真好啊,独立的大隔间工作台,24寸的显示器……我看到了遨游,点开开始上网。当然,我也不由自主的检查了一下xp右下角的图标们,发现了NOD32以及……360安全卫士……没有网盾……
 
当然,这是别人的电脑,我还是忍住了卸载360的冲动。
 
一分钟之后360提示升级,升级到第二步提示此电脑已经安装了网盾,显然我是不会让360把网盾卸掉的,于是我取消了这次升级,然后360很愤愤然的在右下角弹了一个框框,说金山网盾欺负他。我把这个弹窗关掉,30秒之后网盾也弹了一个框框,说360不是东西。你来我往,热闹得很。
 
作为一个Linux用户,这真是一次难得的机会,让我体验到了这场“战争”不是嘴上说的,是正在进行的。网盾的确有做的不好,或者说不对的地方,那就是和遨游捆绑在一起,而且是秘密的捆绑在一起。就像我刚才说了,右下角的图标中就没有网盾,但是进程管理器里面你可以看到kswebshield.exe这个进程在工作。这是在损害用户的知情权,是非常不好的用户体验。但360绝对不是什么好鸟,最可恶的就是用语言对用户进行恐吓和威胁,披着“安全”的皮推销私货。这是一种不那么容易被看出来的伎俩,但我想假以时日,众多的普通用户也迟早会发现这一点。

这两天我也会关注一下新浪微博上关于“金山”和“360”两个关键字的评论。双方都有硬伤。对金山有利的证据包括上个星期网友贴出的网盾和360安全卫士可以共存的视频,以及可牛论坛上的那篇360升级文件分析的帖子;对周鸿祎有利的证据就是今天,或者说昨天,网盾的升级包出了一个bug,使得网盾真的不能兼容360了。呵呵,真是忙中出错。周拿这一点大说特说,也说明在此之前,360安全卫士和金山网盾完全是可以共存的么,360的这次蓄谋已久的进攻的理由完全就是借口。
 
这是一场迟早要打的战争,只是我没想到会来的这么快。我也不想去翻那些周鸿祎的旧账,我只是觉得我周围毒霸的开发同事都很单纯,考虑界面、功能、用户体验等等,都是从如何做好自己出发,而并不是时刻想着怎么打击别人。
 
今天我可以拒绝360一次,这台电脑的主人会拒绝下一次么?我们在24小时之内又会失去一位用户了么……

2010年5月27日 星期四

RLE 压缩算法


RLE 压缩算法是一个很简单的压缩字符串的算法,不知道的自己百度一下哈。前一段时间有人考我,让我快速实现它,于是我写了一个非常naive的函数。debug了之后它长的这个样子:

def rle_compress(input_str):
    """ RLE Compression
    >>> rle_compress('get uuuuuuuuuup'):
    '1g1e1t1 10u1p'
    """
    if len(input_str) == 0:
        return ''

    if len(input_str) == 1:
        return '1' + input_str

    tmp, counter, output = input_str[0], 0, ''
    for i in xrange(len(input_str)):
        if input_str[i] == tmp:
            counter += 1
        elif counter > 0:
            output += (str(counter) + tmp)
            tmp = input_str[i]
            counter = 1
    output += (str(counter) + tmp)

    return output

其实这个真的不够Pythonic哦。Pythonic的方式应该是这样的,灰常nb的one-liner:

from itertools import groupby

def rle_compress_iter(input_str):
    """ RLE Compression, using *groupby* from itertools
    >>> rle_compress('get uuuuuuuuuup'):
    '1g1e1t1 10u1p'
    """
    return ''.join([str(len(list(group))) + name for name, group in

        groupby(input_str)])

当然,我是写不出这么强大的函数的,思路来自于《Expert Python Programming》一书。(打个广告,这本书非常好,您得备一本!)

问题来了,虽然使用python的iterator/generator绝对是每一个python程序员应该实践的best practice,但这两个函数的性能差距还真是有点大(反复执行1000次的总用时,以下计时皆是):

manual compress: 0.663447s
original calls: 2.723927s

测试用的文本是wikipedia上关于Issac Assimov的一段介绍(为了能让RLE算法有点成就感,我自己往里面加了一些重复的字符):

TEXT = """
The Foooooooooooooooooooooooooundation Series is a science fiction series by Isaac Asimov which covers a
span of about 500 years. It ccccccccccconsists of seven volumes that are closely linked to
each other, alttttttttttthough they can be read separately. The term "Foundation Series"
is often used more generaaally to include the Robot Series and Empire Series,
which are set in the same fictional universe, but in earlier time periods. In
total, there are fifteen nnnnnnnnnnnnnnnnnovels and dozens of short stories written by Asimov,
and six novels written by other authors after his death, expanding the
timeeeeeeeeeeeeeeeeee
spanned by more than nnnnnnnnnnnnnnnnnnnnnnntwenty thousand years. The series is highly acclaimed,
winninggggggggggggggggggggggggg the one-time Hugo Award for "Best All-Time Series" in 1966.[1]
"""

我XX,怎么会这样……
好吧,也许不是itertools.groupby的错,我写这个函数的时候只是希望尽快的能够通过doctest。让我们来优化一下吧。

第一次尝试:
用加号来连接name和group的长度?用 '%d%s' % (....) 如何?

return ''.join(['%d%s' % (len(list(group)), name) for name, group
        in groupby(input_str)])

事实证明,结果很悲惨:

strformat calls: 3.933721s

第二次尝试:
group本身也是一个iterator,用 len(list(group)) 是完全没有优化过的第一想法,应该可以有更好的办法来求出一个iterator的长度,比如这样:

def rle_compress_iter_leniter(input_str):
    def leniter(iterator):
        l = 0
        for _ in iterator:
            l += 1
        return l
    return ''.join([str(leniter(group)) + name for name, group in groupby(input_str)])

哈,虽然这样看起来很丑,但是进步不少哦:

leniter calls: 0.928560s

需要说一下的是iterator本身是肯定不会有 len() 函数可以使用的,iterator 可能是无穷的。

第三次尝试:

说过了,上一种方法太丑了,我想让它看起来更优雅一点,用 list comprehension 吧:、

return ''.join([str(sum([1 for _ in group])) + name for name, group
        in groupby(input_str)])

用到了内置的sum函数来求出iterator的长度,但是看起来效果不是很好:

strsum calls: 1.262965s

第四次尝试:
再看一次代码,发现用sum函数求和其实没必要,这个list的长度就等于iterator的长度,所以用len来替换sum也许会更好一些:

return ''.join([str(len([1 for _ in group])) + name for name,
        group in groupby(input_str)]

结果是的确改进了一些,但是还是没有leniter函数快:

strlen calls: 1.059268s

第五次尝试:
用str()来把整数转化为字符串也许还可以改进,要把一个对象转化为字符串,我们还可以用repr()来试试:

return ''.join([repr(len([1 for _ in group])) + name for name,
        group in groupby(input_str)])

OK,这次真的有提高哦,真的很高哦,超过了leniter:

repr calls: 0.840248s

第六次尝试:
上上个星期网易的大神沈崴来金山帮我们对一个程序进行了一番改造。他的代码中有一些“很奇怪”的地方,其实就是我们这些python的新手没怎么接触过的“历史用法”。用'`'(backtick) 来代替 repr() 就是一例:

return ''.join([`len([1 for _ in group])` + name for name, group in
        groupby(input_str)])

事实证明backtick比直接调用repr()还要快!

backtick calls: 0.784968s

---------------------------

真的很接近最初的naive算法了,但我有点黔驴技穷了。如果有谁还有更好的办法,请一定发邮件告诉我。

one-liner真的让你想用lambda来代替def来定义这个函数,但其实lambda会更慢……

虽然naive的代码很傻很天真,但是pythonic的函数始终没能追上它。(至少我这种臭水平是没辙了)下次在写对性能有要求的python代码时,一定要记得放弃那些花花招式,先从最简单开始。

Python的struct.pack函数的一点小小需要注意的地方

struct包是用来对数据和二进制字符串进行相互转换的,如果不讲求效率,这东西还是蛮好用的。上个星期在摆弄的时候,忽然发现了一个有趣的地方:

>>> import struct
>>> struct.pack('B', 1)
'\x01'
>>> struct.pack('H', 200)
'\xc8\x00'
>>> struct.pack('BH',1, 200)
'\x01\x00\xc8\x00'
>>> struct.calcsize('BH')
4

可以看到,struct.pack('B', 1) 与 struct.pack('H', 200) 的结果之和和 struct.pack('BH', 1, 200) 的输出不一样。后者的结果中多了一个 '\x00'。想到我们一般都是用的CPython,这就好理解了。C的struct中存在内存对齐的做法,这样可以得到更好的性能。Python的struct包在默认情况下是不会干涉这一点的,所以就出现了上面的情况。想要去掉这个 '\x00' 也很简单,在格式字符串之前加上 '=' 即可,像这样:

>>> struct.pack('=BH',1, 200)
'\x01\xc8\x00'