Thursday, May 22, 2008

MoinMoin......

这些天在做一个Research,就是接下来的一个项目的技术选型,最终这些东西都汇总到一个openoffice的文档中。但是我越来越不喜欢这种写文档的方式,首先这些office的文档都是二进制的,对搜索引擎不友好,更重要的是,我觉得写文档和软件开发一定程度上是由共性的,就是说是一个迭代和增量的过程,需要不断的反馈,需要集体的协作。我想要的写文档的方式应该是多个人一起完成初稿,然后所有的相关人员都可以来浏览,来编辑,来提意见,就是wikipedia的方式,当然一个专职的文档维护人员(或者一个维护委员会)是必须的,由他来最终确定哪些修改是好的,哪些意见是应该被抛弃的。最后,文档应该是公开的,易于查看,相关文档应该彼此链接,不应该藏在svn的某个深深的角落。
WikiWiki可以解决这个问题,我觉得可以一个主要编写人员首先列出提纲,然后每个部分都有专业的人员来编写,而这个文档随时任何相关人员都可以查看,可以提意见,最终依靠集体的力量完成文档。传统的方式都是一个人完成文档,然后组织大家一起来讨论,然后继续修改,继续讨论。这种方式的效率不高,这要求审核人员必须在特定的时间段里去阅读文档,进行讨论,而审核人员到底付出多大的精力去阅读这篇文档很重要。就目前的经历来看,审核人员通常都不会在这件事情上花多少力气,所以审核的质量非常低。通过wiki的方式,大家可以更深刻的参与到文档的编写工作中来,每个人都随时可以查看文档的进度,可以添加注释,可以直接修改。
下面就说说wiki系统的安装,因为我决心要学习python,所以专门找了一个python的wiki系统,MoinMoin,很多大型网站都采用了这个wiki系统,尤其开源网站,它们更有动力采用wiki来促进社区的交流。

操作系统:Ubuntu 7.10 Desktop, linux kernel 2.6.22-14-generic
Python : 2.5.1
WebServer: lighttpd 1.4.18 (ssl)
MoinMoin: 1.6.3

::安装过程可以查看MoinMoin发布包中自带的Install.html
1. 确认python和lighttpd都已经安装成功。 一般来说ubuntu系统已经自带了python,而安装lighttpd也非常简单,执行'sudo apt-get install lighttpd'就可以了。
  • 在控制台执行'python', 出现python提示符'>>>'就表示python已经安装成功了。
  • 执行'lighttpd -D -f /etc/lighttpd/lighttpd.conf', 然后在浏览器中访问'http://localhost',看到html页面就表示lighttpd安装成功了。
2.下载MoinMoin, 解压缩发布包之后,进入安装文件目录,在控制太执行安装命令:
>>>python setup.py install --prefix='/usr/local' --record=install.log
其中, --prefix表示安装目标目录,如果不指定,那么会安装到默认目录,通常是/usr/lib/python2.5/site-pakcage/moin和/usr/share/moin; --record表示安装日志,可以从中看出哪些文件被copy到哪个目录了。
NOTE:更多信息可以参考moinmoin的install.html
3. ....关于moin的配置请参考moinmon的install.html
4. 配置FasgCGI。如果CGI方式,那么每个请求相应的cgi文件都会被重新load,这样会极大的影响的性能, 而FastCGI方式只需要在第一次请求的时候load脚本,然后这些脚本就会被缓存下来,这样可以大大提高性能,此外,FasgCGI是一个单独运行的网关进程,就算webserver崩溃也不会影响到它。在多核的环境下,FastCGI是个不错的选择。mod_python是运行在webserver中的一个线程,在超线程的环境下可能表现更好。lighttpd不支持mod_python。
lighttpd是一个高性能的,轻型的(相较与apache),高度模块化的webserver,它需要在配置文件lighttpd.conf中指定装载fcgi模块,并且配置fcgi相应的参数。
note: 可以参考install.html中的'deploying in lighttpd'
  • 设定装入fcgi模块:
    server.modules              = (
    "mod_access",
    "mod_rewrite", # <--- IMPORTANT! "mod_status", "mod_fastcgi", # <--- IMPORTANT! "mod_accesslog", "mod_redirect", "mod_auth", "mod_expire", )
  • 配置fasgcgi模块,
    $HTTP["host"] =~ "^127.0.0.1" {
    url.rewrite-once = (
    "^/robots.txt" => "/robots.txt",
    "^/favicon.ico" => "/favicon.ico",
    "^/moin_static163/(.*)" => "/$1", #所有的media文件url都是以moin_static163开头的
    "^/(.*)" => "/wiki-engine/$1" #除了media文件和robots,favicon外的请求都送到fastcgi server
    )
    server.document-root = "/usr/share/moin/htdocs/"
    #url以wiki-engine开始的都送到fastcgi。 url会被rewrite模块重写,可以看前面的定义url.rewrite-once
    $HTTP["url"] =~ "^/wiki-engine/" {
    fastcgi.server = ( "/wiki-engine" =>
    (( "docroot" => "/",
    "min-procs" => 10,
    "max-procs" => 10,
    "max-load-per-proc" => 2,
    # allocate successive port numbers for each process, starting with "port"
    "bin-path" => "/opt/mylib/moin-1.6.3/moin.fcg", #如果由lighttpd来启动fastcgi,那么需要指定这个值
    "host" => "127.0.0.1",
    "port" => 3060,
    "check-local" => "disable",
    ))
    )
    }
    }
一般来说,由于指定了bin-path参数,所以lighttpd会自动启动一个fastcgi进程,不需要额外的在外面手动启动一个fastcgi进程。 如果没有指定,那么需要手动在外面启动fastcgi进程,同时保证指定的host,port与fastcgi server的定义是一致的。 lighttpd和fastcgi server之间通过tcp socket通信,也可以直接通过unix-domain socket(IPC socket,进程间通信)通信,这个需要指定fastcgi.server的socket参数,host+port和socket只能采用一种。
5.这里反过来再提一下MoinMoin的配置。我的MoinMoin是安装到默认目录,即/usr/lib/python2.5/site-package/moin和/usr/share/moin,前面一个目录放置的是moinmoin的python源代码, /usr/share/moin放置的是模板代码(html,img,css等)和数据文件(保存用户数据,比如page, comments,user等)。然后我又把/usr/share/moin中的data, underlay,wikiconfig.py, moin.fcg复制到/opt/mylib/moin目录,这个目录叫做MoinMoin的一个实例。就是说你可以同时运行多个实例,它们分别有自己的名称空间,彼此不会影响,但是共用htdocs。
此外,需要注意的是权限问题,由于lighttpd是以www-data的用户运行,而fastcgi程也是它启动的,即fastcgi server也是以www-data的用户运行的,那么www-data必须具有/opt/mylib/moin目录读写执行权限。 总之要注意权限问题。但是如果fastcgi是通过手动单独在外面启动的,那么启动这个进程的用户必须具有读写执行/opt/mylib/moin的权限,这样的话,它是独立与lighttpd的权限系统之外的,可以另外设置权限规则。

Thursday, May 15, 2008

我的兴趣真是广泛......咳咳

对于我的职业规划,我一直是想做一名架构师,因为本身有对于技术的持续的热情和学习动力。而且兴趣广泛对于一名架构师来说应该算是优势,作为架构师保持宽阔的眼界应该是很重要的。 但是有时候在这些兴趣之间会有些迷失,而且热点变化的过快,导致了贪多嚼不烂。 比如这段时间, 由于工作上相对比较清闲,所以可以去看很多东西,包括document software architecture, agile development(scrum), python(jython), osgi, maven, flex,跨度可以说是相当的大,的确是开阔了眼界,但是沉淀的不多, 也许面试的时候到可以满口的胡说八道,让人目眩神迷。
我想是需要计划了, 应该给这些东西排个优先级别,然后各个击破,当然不是每个都要精通,比如flex(个人来说,我觉得这是比ajax更有前途的东西),就是所谓的divide and conquer。下面就列一下吧, 优先级从1-5,1便是优先级最高:
  1. document software architecture: 最切合职业规划.....P1
  2. agile development: 敏捷开发,其实还包括up,xp等......P2
  3. python:作为一个程序员,至少得会两门语言,这是我的要求.....P2
  4. flex: ria技术,相信比ajax更有前途.....P3
虽然flex的有限级别最低,但是我刚刚下载了flex sdk, 怎么说呢,现在等不及要尝试一下了。
.......
经过短暂的狂热,并且下载了flex_sdk_3,阅读了一些开发文档之后,狂热劲似乎慢慢消逝了。怎么说呢,flex框架包含了大量的漂亮的ui组件,而且对开放标准的支持也不错,比如使用css来定义组件外观,用mxml语言来定义界面,actionscript来定义逻辑,这种设计对程序员来说更加友好,毕竟让一个程序员来进行那种基于时间帧的设计有点牵强。我相信可以使用flex构建出炫目的应用,但是有一点,我觉得非常不足,就是flex与浏览器的整合太弱了,这里说的整合不是说浏览器是否都安装了flash插件这个问题,而是说flash与浏览器之间的互操作性,以及与html的dom模型之间的互操作性, flash虽然是运行在浏览器中,但是它们显然是两个世界的东西, 这让我觉得不太舒服, 可能我们以前使用浏览器的操作习惯在面对flash的时候都变的不一样,这可能会让人们困惑,从这个角度讲,我开始更倾向于ajax了。

Friday, May 09, 2008

读书笔记:Document software architecture

在读一本书,不是教你如何做架构设计,而是如果将系统架构记录下来,但这对架构设计还是很有帮助的,因为你知道了如何文档化一个系统架构,就说明你知道了架构应该包含哪些元素,应该从什么角度来描述架构,这真的很重要,是一种thinking层次的东西,比仅仅会技术还要带劲。
这篇博客是用来作为我的读书笔记, 英文文档已经看完了,但是有些部分太晦涩,用了太多不认识的单词,看的头晕,现在又弄了中文版来对照看。 中文版翻译的一般,有些地方我需要回过去看英文。

[architecture]
什么是架构???
A software architecture for a system is a structure of the system, which comprise elements, relation among them, and the external visible properties of those elements and relations.


[view]

[viewtype]

[style]

[如何描述架构?]
Architects need to
think about their software in three ways simultaneously(同时,一起):
1. How it is structured as a set of implementation units (module viewtype)
2. How it is structured as a set of elements that have runtime behavior and interactions(component-and-connector viewtype)
3. How it relates to nonsoftware structures in its environment (allocation viewtype)

咳咳,先做个记号,有空才写!

Thursday, May 08, 2008

关于字符(characters),字符集(character sets), 编码(encodings)

在程序中我们经常要处理字符的编码转换,比如两个系统之间传输包含中文字符的信息时候,或者向数据库存储/读取中文数据的时候,你都会碰到这个问题。对于缺乏经验的程序员,这是个烦恼和郁闷的问题。 起码我是经受过这样的反复折磨,每个项目都会碰到这个问题,而每次解决了之后还是觉得若有所示,因为我不清楚为什么这样就解决了,虽然我一直是这样解决这个问题的。 所以我期望把这个问题搞清楚,我希望知道为什么错,有为什么对。。。。

[字符,字符集,编码]

字符, 呵呵,就是字符,比如a,b,c,或者汉字的'我'.
字符集(比如unicode)定义了一种编码规范支持哪些字符,每个字符都会被分配一个编码(或者说整数,以免和后面要说的编码混淆,后面说的编码应该是一个动词),通过这个编码就可以映射到相应的字符. 在不同的字符集中,同一个字符的编码一般是不一样的.
而编码(比如utf-8)是定义了如何将这些字符保存在内存或者文件等。 GBK字符集包括2万多个汉字,每个字符都有一个码,编码就是如何将这些码保存下来。 unicode有些不同,因为unicode为每个字符定义了code point,编码标准,比如utf-8, ucs-2,定义了如何将code point保存下来。

[存在的编码方法]

这里说的编码方式不仅仅只中文,还包括其他,相信俄文,印度文等等都会有自己的类似gbk一类的编码标准。下面只列出我知道的,比较常用的编码方式:
  • ASCII: American Standard Code for Information Interchange, 这是英文字符的编码,使用了单字节,ASCII的最高bit位都为0,从0-127。
    ASCII码是7位编码,编码范围是0x00-0x7F。ASCII字符集包括英文字母、阿拉伯数字和标点符号等字符。其中0x00-0x20和0x7F共33个控制字符。

    只支持ASCII码的系统会忽略每个字节的最高位,只认为低7位是有效位。HZ字符编码就是早期为了在只支持7位ASCII系统中传输中文而设计的编码。早期很多邮件系统也只支持ASCII编码,为了传输中文邮件必须使用BASE64或者其他编码方式。
  • Latin-1(ISO-8859-1): 这是欧洲的编码,因为西欧语言中包含很多字符,比如á这种字符。它是ASCII的超集,那些古怪字符用的是128-255的范围,利用单字节存储。
  • Unicode(www.unicode.org):这是一个想要大一统的编码标准,要为世界上每种语言的每个字符都定义一个编码。它又是Latin-1的超集,用两个字节存储。当然不可能是所有字符,其实是语言的常用字符,比如汉字编码的范围是4e00-9fcf,大概2万个左右,不会包含古老的甲骨文汉字。 unicode是一个字符集,它有许多不同的编码方式,比如UCS-2是直接用两个自己来存储,而utf-16也是用两个自己存储,而utf-8是变长的,对0-127之间的用单个字节,128以上的用两个,三个甚至6个字节来表示。(CHECK:??字节的最高位第八位用来表示这个字符是单字节还是双字节
  • GBK:这是中国定义的汉字编码标准,最早是GB2312,只定义了常用汉字,大概2万个左右(unicode主要包含gb2312中的汉字),后来的gbk是对gb2312的扩展,加入了很多不常用的汉字,同时也支持了繁体字。中文字符的每个字节的最高位都为1。 在中国大陆使用的计算机上,它们的本地编码(ANSI编码)大多都是gbk。在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个字符了,而是另个那个国家的一个字符或乱码。(http://www.btinternet.com/~jlonline/back/GBK.htm)
    GB2312是基于区位码设计的,区位码把编码表分为94个区,每个区对应94个位,每个字符的区号和位号组合起来就是该汉字的区位码。区位码一般 用10进制数来表示,如1601就表示16区1位,对应的字符是“啊”。在区位码的区号和位号上分别加上0xA0就得到了GB2312编码。

  • UTF-8: 首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII 字节前插入三个 0x00.

    在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '\0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.

    在 ISO 10646-1 Annex R 和 RFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.

    UTF-8 有以下特性:

    *UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.
    *所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分.
    *表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0x80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.
    *可以编入所有可能的 231个 UCS 代码
    *UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.
    *Bigendian UCS-4 字节串的排列顺序是预定的.
    *字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.

    下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.
    U-00000000 - U-0000007F: 0xxxxxxx
    U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
    U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
    U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

    xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.

    例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

    11000010 10101001 = 0xC2 0xA9

    而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:

    11100010 10001001 10100000 = 0xE2 0x89 0xA0

    这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.

[编码转换]
实际上不存在什么编码转换地概念, 当你用什么编码方式写出的时候, 读入就应该用同样的编码方式,否则乱码就来找你了.先看下面一个例子:


String input = "S茅n茅gal";
System.out.println(HexCoder.bufferToHex(input.getBytes("GBK")));
>>53c3a96ec3a967616c
System.out.println(HexCoder.bufferToHex(input.getBytes("UTF-8")));
>>53e88c856ee88c8567616c
String utf8 = new String(input.getBytes("GBK"), "UTF-8");
System.out.println(utf8);
>>Sénégal

可以看到GBK和UTF8得到的字节数组是不一样的,有趣的是最有的变量'utf8',它的内容是法文'Sénégal', 而初始的变量'input'才是乱码.这个转换其实容易理解, input.getBytes('GBK')会得到'53c3a96ec3a967616c', 而用utf-8的方式来解码的话,就得到了'Sénégal'.这个例子让我们觉得乱码和原文之间可以互相转换, 某种程度上说是这样的.
但是, 再看看下面的例子:


String input = "我";
System.out.println(HexCoder.bufferToHex(input.getBytes("GBK")));
>>ced2
System.out.println(HexCoder.bufferToHex(input.getBytes("UTF-8")));
>>e68891
String utf8 = new String(input.getBytes("GBK"), "UTF-8");
System.out.println(utf8);
>>??
System.out.println(HexCoder.bufferToHex(utf8.getBytes("GBK")));
>>3f3f
System.out.println(HexCoder.bufferToHex(utf8.getBytes("UTF-16")));
>>fefffffdfffd
* feff is Unicode Byte Order Mark.

这个例子和第一个例子只有变量input的值不同, 但是可以看到变量utf8.getBytes("GBK")最后打印出来是??, 为什么? input.getBytes("GBK")后得到了ced2, 当用UTF8来解码的时候,utf8完全无法识别这个编码,所以用?(\ufffd, 不是问号\u003f)表示. 而最后一句'utf8.getBytes("GBK")', 打印出来的也就是3f3f(3f就是ascii中的?, 因为gbk的字符集中没有和\ufffd对应的字符)了.

还有一个问题,java中的字串是unicode, 这句话到底什么意思? 看下面的例子:
System.out.println("\u0048\u0065\u006C\u006C\u006F");
>>Hello
这里0048是字符H的unicode的code point,和编码(就是如何保存这个字串在内存/磁盘或者在网络中传输)没有关系.

It does not make sense to have a string without knowing what encoding it uses
[内码外码]
所谓内码就是指的gbk,unicode的编码,而外码指的是输入法中定义的字码,外码是可以随便定义,而内码是不能变的,输入法会映射外码到内码。

这里还是要强调需要搞清楚字符,字符集,编码的区别, 可以认为GBK的字符集和编码定义的值都是一样的.
看看在java中的表现.
char c = '甜';
System.out.println(Integer.toHexString((int)c));
// print out: 751c, 打印出来'甜'的unicode的codepoint(就是说在jvm内部是以codepoint(unicode字符集定义了每个字符的codepoint)来表示一个uncode字符。但是当需要和其他系统交换这个字符的时候,就需要采用编码规则来将字符编码成字节), 这
//也说明为什么我们总是说java中的字符都是unicode的. 也就是说,JVM内存中, 所有的字符都是以unicode的UTF16编码形式表示的
byte[] output = src.getBytes("UTF-8");
for (byte o : output){
System.out.println(Integer.toHexString(o));
}
// print out:
// ffffffe7
// ffffff94
// ffffff9c

output = src.getBytes("GBK");
for (byte o : output){
System.out.println(Integer.toHexString(o));
}
// print out:
// ffffffcc
// fffffff0
// 为什么能得GBK的编码值呢? java提供了一个CharsetDecoder的类用来执行这种转换(每一个charset都会有一个
// 对应的子类), 估计应该是首先从UTF16的编码得到unicode字符集的code point, 然后在unicode的字符集和
// gbk的字符集之间有一个映射(估计操作系统会管理这个映射,或者JVM自己管理,这个不是重点),最后对这个GBK的code point进行GBK编码.
// 就是说,字符集是不同编码之间进行转换的桥梁.

output = src.getBytes("UTF-16");
for (byte o : output){
System.out.println(Integer.toHexString(o));
}
// print out:
// fffffffe
// ffffffff
// 75
// 1c
// Java2平台内对unicode采用UTF16的编码方式, 这种编码方式GBK的特点, 编码出来的值和code point是一样的
// (只是指unicode的BMP部分:U+0000 - U+FFFF). 对大于U+FFFF的字符会用四个字节来进行编码,具体的可以参考
// UTF16编码规范. 为什么'tian'在utf16编码后会有四个字节呢????

更多信息可以参考:
  • 字符,字节和编码:http://www.regexlab.com/zh/encoding.htm
  • GBK, http://www.btinternet.com/~jlonline/back/GBK.htm
  • 字符编码笔记(

    http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html)
  • The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (http://www.joelonsoftware.com/articles/Unicode.html)

Tuesday, May 06, 2008

跳进python, Django

一直都想学习java以外的一门新语言,在学习java以前看过perl, php, 但是粗略的看一看,又没有地方来用,最终就淡忘了。 后来开始学python,也是一样的,有空看看书,但是用的机会不多,慢慢又模糊了, 真是浪费生命。 再以后开始用python写一些小工具来辅助开发,比如写了一个破解我老婆博客密码的小东东, 一个根据diff文件构建升级包的工具, 实践是最好的老师,一些模糊的记忆又慢慢开始清晰了。 java世界开始变得开放,比如jython的出现,这样python代码可以直接在java虚拟机上跑了。 更好的事情是grinder,一个开源的java性能测试工具, 它的测试脚本支持用jython来写, 于是就在项目里面小用了一下,很有意思, python可以在日常工作中发挥更大的作用了。
不过这里要写这篇blog是用来作为我学习python的笔记, 一些自己在使用python的过程经常出错的东西都要在这里记录下来,免得同一个石头绊我三次。

[内置的数据类型]
dictionary: key必须是常量 ,key大小写敏感,而且不能重复。 其中的元素是没有秩序的。
list::相当于php中的数组,可以保存不同类型的数据。 其中元素是有秩序的
tuple: 不可变的list, 可以用tuple()来将一个list转化为tuple; 反过来可以用list()将一个tuple转化为list

python是一种强类型的语言,就是说一旦将给一个变量赋值之后,这个值的类型是不能变化的,比如进行显式的强制转换,比如: int('1'), str(1); 对于弱类型的语言,这种转换可以自动完成,比如‘1’ + 2在python中会抛出异常,但是javascript中可以返回‘12’, 或者3,取决于你怎么使用。
python中有一个内置函数isinstance()可以检验某个值的数据类型(int, long, str, dict, list, tuple, float, Class etc),比如: isinstance(1, int) = True, isinstance('1', int) = False

python中有个types模块,其中定义了很多内置的数据类型:
>>> import types
>>> dir(types)

[内置函数]
[getattr]
使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。
>>> li = ["Larry", "Curly"]
>>> li.pop

>>> getattr(li, "pop")

>>> getattr(li, "append")("Moe")
>>> li
["Larry", "Curly", "Moe"]
[isinstance]
是否是某个类型的实例, isinstance(1, int) = true
[callable]
接收任何对象作为参数,如果参数对象是可调用的,返回 True;否则返回 False
[range ]
内置的 range 函数返回一个元素为整数的 list。这个函数的简化调用形式是接收一个上限值,然后返回一个初始值从 0 开始的 list,它依次递增,直到但不包含上限值。(如果您愿意,您可以传入其它的参数来指定一个非 0 的初始值和非 1 的步长。也可以使用 print range.__doc__ 来了解更多的细节。)
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

[filter]

Python 有一个内建 filter 函数,它接受两个参数:一个函数和一个列表,返回一个列表。作为第一个参数传递给 filter 的函数本身应接受一个参数,filter 返回的列表将会包含被传入列表参数传递给 filter 所有可以令函数返回真 (true) 的元素。

>>> def odd(n):                 1
... return n % 2
...
>>> li = [1, 2, 3, 5, 9, 10, 256, -3]
>>> filter(odd, li) 2
[1, 3, 5, 9, -3]

[map]

使用内建 map 函数。它的工作机理和 filter 函数类似。

>>> def double(n):
... return n*2
...
>>> li = [1, 2, 3, 5, 9, 10, 256, -3]
>>> map(double, li) 1
[2, 4, 6, 10, 18, 20, 512, -6]

[other...]

获得定长字符串:

>>>s = 'xiix'

>>>s.ljust(8, '0')

'xiix0000'

>>>s.zfill(8)

'0000xiix'

[映射list]

Python 的强大特性之一是其对 list 的解析,它提供一种紧凑的方法,可以通过对 list 中的每个元素应用一个函数,从而将一个 list 映射为另一个 list。

>>> li = [1, 9, 8, 4]
>>> [elem*2 for elem in li] 1
[2, 18, 16, 8]
[过滤列表]
这种能力同过滤机制结合使用,使列表中的有些元素被映射的同时跳过另外一些元素。
[mapping-expression for element in source-list if filter-expression]
[使用lamba函数]
Python 支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做 lambda 的函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。
>>> (lambda x: x*2)(3) 2
6
lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。lambda 函数不能包含命令,包含的表达式不能超过一个。

[多变量赋值]
>>>t = (1, 2, 3)
>>>(x, y, z) = t
x = 1, y =2, z =3
>>>l = [1, 2]
>>>[x,y] = l
x = 1, y = 2

[类的定义]
  • 当从你的类中调用一个父类的一个方法时,你必须包括 self 参数。
  • __init__ 方法是可选的,但是一旦你定义了,就必须记得显示调用父类的 __init__ 方法 (如果它定义了的话)。这样更是正确的:无论何时子类想扩展父类的行为,后代方法必须在适当的时机,使用适当的参数,显式调用父类方法。
class FileInfo(UserDict):
    "store file metadata"
def __init__(self, filename=None):
UserDict.__init__(self) 1
self["name"] = filename
2
  • 要在类的内部引用一个数据属性,我们使用 self 作为限定符。习惯上,所有的数据属性都在 __init__ 方法中初始化为有意义的值。然而,这并不是必须的,因为数据属性,像局部变量一样,当你首次赋给它值的时候突然产生
  • 专用类方法。Python 有一个与 __getitem__ 类似的 __setitem__ 专用方法,比如f['name']这个看上去就像你用来得到一个字典值的语法,事实上它返回你期望的值。下面是隐藏起来的一个环节:暗地里,Python 已经将这个语法转化为 f.__getitem__("name") 的方法调用。这就是为什么 __getitem__ 是一个专用类方法的原因,不仅仅是你可以自已调用它,还可以通过使用正确的语法让 Python 来替你调用。
  • 类属性和数据属性。 数据属性在__init__方法中通过'self.XXX'来定义,是一个特定类实例所拥有的变量;类属性是在class中直接定义, 是由类拥有的属性,当然所有该类的实例都拥有该属性。
[高级专用类方法]
  • __repr__ 是一个专用的方法,在当调用 repr(instance) 时被调用。repr 函数是一个内置函数,它返回一个对象的字符串表示。它可以用在任何对象上,不仅仅是类的实例。你已经对 repr 相当熟悉了,尽管你不知道它。在交互式窗口中,当你只敲入一个变量名,接着按ENTER,Python 使用 repr 来显示变量的值。自已用一些数据来创建一个字典 d ,然后用 print repr(d) 来看一看吧。(java.lang.Object#toString())
  • __cmp__ 在比较类实例时被调用。通常,你可以通过使用 == 比较任意两个 Python 对象,不只是类实例。有一些规则,定义了何时内置数据类型被认为是相等的,例如,字典在有着全部相同的关键字和值时是相等的。对于类实例,你可以定义 __cmp__ 方法,自已编写比较逻辑,然后你可以使用 == 来比较你的类,Python 将会替你调用你的 __cmp__ 专用方法。(java.lang.Object#equals(o, o)
  • __len__ 在调用 len(instance) 时被调用。len 是一个内置函数,可以返回一个对象的长度。它可以用于任何被认为理应有长度的对象。字符串的 len 是它的字符个数;字典的 len 是它的关键字的个数;列表或序列的 len 是元素的个数。对于类实例,定义 __len__ 方法,接着自已编写长度的计算,然后调用 len(instance),Python 将替你调用你的 __len__ 专用方法。
  • __delitem__ 在调用 del instance[key] 时调用 ,你可能记得它作为从字典中删除单个元素的方法。当你在类实例中使用 del 时,Python 替你调用 __delitem__ 专用方法。
这些其实就和java中的Object类相似, 因为java中所有类都是从Object继承的,那些方法都定义在Object中,而python没有这样的基类,通过这些专用方法来达到类似的效果。或者有些象设计模式中的模板方法(template method)

[xml解析]
  • 如果你打算在你的 Python 代码中保存非 ASCII 字符串,你需要在每个文件的顶端加入编码声明来指定每个 .py 文件的编码。这个声明定义了 .py 文件的编码为 UTF-8:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

  • 通过sys.getdefaultencoding()得到当前的默认编码形式, 在python启动以后就不能调用sys.setdefaultencoding('latin-1')来设定编码。 这里介绍一些简单的历史。ASCIi是英语字符的编码,只需要0-127, 但是欧洲,尤其是西欧有很多古怪的字符,比如西班牙语里有字符在n上面加一个波浪线,为了增加对这些字符的支持,出现了ISO-8859-1也就是Latin-1标准,所有那些字符都在128-255之间。 而unicode是一个想要统一天下所有字符的编码标准,地球上所有语言的任何一个字符都可以找到一个唯一的编码,使用两个字节,从0到65535(仅仅中日韩就不止65535个字符了吧,不够用)。 utf-8是对unicode进行的演化,因为对于很多iso-8859-1标准内定义的字符,它们用一个字符就可以表示了,如果用unicode来表示的话就相当于浪费一个字节,所以utf-8对ASCII等单字节的字符仍然只用一个字节表示,对其他的还是双字节。 而utf-16就是对所有字符都使用双字节。
  • iso-8859-1是ascii的超集,unicode是iso-8859-1的超集。 而中文又存在GBK和GB2312两个标准, gbk是gb2312的超集,其中增加了对繁体字,以及一些没有收录到gb2312中的汉字字符。 gb是中国的标准,在一定程度上,比如0-127段的定义和ascii是一致的。 更多信息可以参考维基(http://en.wikipedia.org/wiki/GBK)
[动态导入]
>>> sys = __import__('sys')           1
>>> os = __import__('os')
>>> re = __import__('re')
>>> unittest = __import__('unittest')
>>> sys 2
>>>
>>> os
>>>
1 内建 __import__ 函数与 import 语句的既定目标相同,但它是一个真正的函数,并接受一个字符串参数。
2 变量 sys 现在是 sys 模块,和 import sys 的结果完全相同。变量 os 现在是一个 os 模块,等等。

因此 __import__ 导入一个模块,但是是通过一个字符串参数来做到的。依此处讲,你用以导入的仅仅是一个硬编码性的字符串,但它可以是一个变量,或者一个函数调用的结果。并且你指向模块的变量也不必与模块名匹配。你可以导入一系列模块并把它们指派给一个列表。


[参考资源]

Monday, May 05, 2008

关于http协议中定义的缓存机制与网站性能

一直以来大家都在用http协议,但是有几个人真正的了解http协议?也许你会说,我只需要会用就可以了, 干吗要去读http协议规范? 某些情况下,我觉得这样说是对的, 甚至绝大多数情况下可能都是对的,在于你是要做什么。 但是, 我还是坚持,在你急急忙忙的完成了一个和http有关的功能之后,还是应该好好想想,你为什么要这样使用http协议,这个时候你应该静下心来好好读读http的协议规范, 作为一个真正的程序员, 你应该这么做。

这里有点搬起石头砸自己脚的意思,因为我自己也只是看了http协议规范中和cache有关的一部分,而且不敢保证是所有相关的部分都阅读过了,所以,下面说的东西仅供参考。

[caching mechanisms]
cache机制是分几个层次的,他们包括服务器端缓存,isp缓存,客户端缓存。
  • 服务端缓存,比如squid,还有本身很多web开发框架支持页面缓存, 这个缓存会影响所有访问这个网站的用户。
  • isp缓存,isp可能缓存网站的内容,这个缓存会影响所有通过这个isp接入的用户,可能所有通过这个isp接入,而且访问某个被缓存网站的用户的请求直接在isp就被返回了,不会到达请求的网站。
  • 客户端缓存,一般指浏览器缓存,这个缓存只影响单个用户, 这样用户直接从本地装载内容,或者只需要发送查询请求(if-modified-since)。
所有这些缓存机制可以极大的提高对用户的响应速度,同时也极大的降低了服务器的负荷,因为大量的请求还没有到达网站服务器就已经被处理了。

[Cache-Control]
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
cache-control是http1.1中定义的头,在http1.1之前(http1.0)是通过expire都来指定cache策略。就是说在http的request/response中指定的这些关于cache策略的头是用来影响前面提到的cache机制, 这些cache机制根据cache头来处理其中的cache内容。
具体的cache-control的写法就不详细说,可以自己参考http规范, 这里就说说几个比较常用的值。
[cache-control: public; max-age=21600]
public/private: public 表示这个响应可以被任何cache机制缓存下来,如果是private表示响应绝对不能被共享缓存(shared cache)cache下来,比如用户的登录进某个网站后看到的用户管理页面,如果这个页面被shared cache缓存下来,假设是被isp缓存下来了,那么所有通过这个isp访问该网页的用户都会得到某个其他用户的管理页面,这是绝对不允许的,这样泄漏了用户隐私信息。
max-age: 单位为秒,表示这个响应可以被缓存多长时间。 如果同时也指定了expire头,那么总是以max-age的值为准。 但是并不总是这样,因为有些缓存机制可能还不支持http1.1规范,那么它们就会忽略cache-control头,而仅仅采用expire头,所以在响应中总是同时指定expire头总是一个比较好的主意.
no-cache:

If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy a subsequent request without successful revalidation with the origin server.
就是说no-cache还必须指定可以被缓存的某个/某几个field-name,否则这个响应 不能被缓存下来。
no-store: 表示整个响应不能被保存下来,尤其是一些敏感信息。 请求和响应中都可以指定这个值。

[If-Modified-Since/Last-Modified]
last-modified: 表示请求的网页最后的修改时间(也许说是请求的资源最后修改时间更合适)。只会在响应中携带这个头。一般来说如果请求的是一个文件,这个头可能就是操作系统里面该文件的last-modified时间。
if-modified-since: 如果cache机制缓存了某个响应,这个响应中指定了last-modified头,那么下次有用户请求这个缓存的网页时, cache会向original server(产生这个资源的服务器)发送一个请求,请求中包含if-modified-since头,original server检查请求的资源,并且将服务器上该资源的last-modified和if-modified-since的值进行比较,如果服务器上的资源没有修改,那么返回304(这个响应只包括头),如果服务器上的资源已经有了修改,那么返回200,同时返回新修改过的资源。

如果想要弄清楚http的cache机制,动手实践是必不可少的。 我就是用firefox的一个tamper data的插件访问www.youtube.com来实验的, tamper data可以查看request/response中的头信息。 要说起来,如果做web开发,firefox真是个好东西,它的很多插件都很有意思,还可以推荐一个poster。
[ETag]
ETag 是实现与最近修改数据检查同样的功能的另一种方法:没有变化时不重新下载数据。其工作方式是:服务器发送你所请求的数据的同时,发送某种数据的 hash (在 ETag 头信息中给出)。hash 的确定完全取决于服务器。当第二次请求相同的数据时,你需要在 If-None-Match: 头信息中包含 ETag hash,如果数据没有改变,服务器将返回 304 状态代码。与最近修改数据检查相同,服务器仅仅 发送 304 状态代码;第二次将不为你发送相同的数据。在第二次请求时,通过包含 ETag hash,你告诉服务器:如果 hash 仍旧匹配就没有必要重新发送相同的数据,因为你还有上一次访问过的数据。

[压缩 (Compression)]

一个重要的 HTTP 特性是 gzip 压缩。 关于 HTTP web 服务的主题几乎总是会涉及在网络线路上传输的 XML。XML 是文本,而且还是相当冗长的文本,而文本通常可以被很好地压缩。当你通过 HTTP 请求一个资源时,可以告诉服务器,如果它有任何新数据要发送给我时,请以压缩的格式发送。在你的请求中包含 Accept-encoding: gzip 头信息,如果服务器支持压缩,它将返回由 gzip 压缩的数据并且使用 Content-encoding: gzip 头信息标记。




最后还说点和cache机制无关,但是也和网站性能有关的一个小话题。 用户访问一个网站,如何能做方便的到达他/她最关心的页面? 2次点击,4次点击? 似乎真是个小问题,但在我看来,至少涉及两个方面的问题,一个是易用性的问题,决定了用户是否会真正喜欢并且依赖你的站点; 另外一个是网站性能问题, 如果你把4次点击减少到2次点击,是不是意味着你的服务器承受的负荷也减少了一半? 如果你是在架构一个海量用户访问的站点,比如北京奥运的购票网站,那么这些问题绝对不是小问题。就算你在做小网站,这种努力也是很有价值的。