Xavier's Blog

我认为的健康生活

| Comments

工作已有大半年,身为一个coder,工作还是挺辛苦的,生活也有时难免不规律,比如需要临时排查一个问题,或者要在晚上上线等等。但是,另一方面,正因为工作辛苦,健康对于我们来说就更为重要,毕竟身体是革命的本钱啊。经常看到关于IT从业者猝死的新闻,真是让人唏嘘不已。如果他们能够进行“可持续发展”,肯定能对社会有更大的贡献。

那么,什么样的生活才是健康的呢?才是可持续的?我想了想,至少应该有这么几点:

早睡早起

首先是要睡得早,你睡得晚必定起得就不会早。尽量能在23点上床睡觉,最好不要超过23:30。睡觉前泡泡脚,喝点牛奶蜂蜜啥的,有助睡眠。早上7点左右起床,在家吃好早饭,然后不急不忙的去上班。因为去公司比较早,人比较少,你还可以在比较安静的环境下高效的工作1.5个小时左右。在我们公司,上班的大部分时间都是十分吵闹的,各种交流、各种讨论,但是,coding有是项需要集中精力才能完成的事情,周围环境很吵闹的话,工作效率就不会很高(即使你戴上耳机听音乐)。因此,比别人早来公司的那段时间,就显得无比宝贵了。高效的完成一天的工作,你也就可以按时下班了。 可能人们心中会有这种等式:“生活规律” == “碌碌无为”,或者是“很晚下班” == “工作努力”。我觉得,很少部分情况下,这个等式才是成立的,绝大部分情况,只是说明你工作效率低下而已。

饮食清淡

现在,大家的午餐和晚餐基本上都是在外面吃了,其实是还是一个人住的时候。不过大家都知道,我国的食品,尤其是饭店的食品,可以说没什么安全可言。所以,能不在外面吃,就尽量不在外面吃,能自己做菜最好。不过可能也会中这样的烦恼:下班太晚,都没时间买菜、烧菜了。其实,只要能做到早点来公司并且工作效率较高的话,还是可以早点下班买菜的。 另外,就是吃的东西应该尽量清淡些,多吃蔬菜少吃肉。现在生活条件多好了,在外面吃的东西,基本都是很多肉、很多油的,我们早就摄入的过多了。所以当自己可以选择吃什么的时候,还是选择清淡些为好。

定期锻炼

工作了之后,锻炼的机会越来越少了,而且坐的时候越来越长了,这样子迟早会出问题的。坐了1、2个小时就站起来走走,去倒杯水,或者做些简单的操。(现在公司每天下午3点,大家会做广播体操,这个很赞)也可以去办张健身卡,用“既成事实法”,先把钱花出去,然后“逼迫”自己去锻炼,况且健身房的氛围也不错,也不用怕外面天气太冷或太热。

养成看书的习惯

身体需要锻炼,思想同样需要锻炼。不过,现在生活节奏快,发现我以及周围许多人,己经连读一篇几百字的文章的耐心都没有了,有个同事甚至开玩笑说,“我现在的头脑里面的内存很小,只有140字节,读太长的文章就溢出了”。 尽量争取每天读一篇比较有意义、有深度的文章,可以是和业界相关的,也可以说一些杂书里面的文章,不用太长时间,30分钟就足够了。少逛一次人人,少刷一次微博,时间就省下来了。

—EOF—

Memcached调研

| Comments

最近因开发需要,调研了下memcached,share一下!

什么是memcached

Memcached是一个开源的高性能,分布式的内存对象缓存系统,通过键值对的形式来对数据进行存取。

特性

简单的Key Value存储

|—-Memcached只把key value当作一组字符串来组织,不关心数据的格式 |—-没有持久化,所有数据都存储在内存中,当内存用满时,丢弃最旧的数据 -—数据只会保存一份,不支持replication,也不支持容错(一些客户端有自己的实现)

Server和client共同决定操作的行为

|—-Server只关心如何获取数据、存储数据、丢弃数据 -—Client只关心如何通过key定位server,以及如何和server通信

Server相互独立

|—-Server之间不会有通信,也不知道其他server的存在,但是Client知道所有server的存在。

所有操作都是常数时间,所有操作也都是原子操作

数据组织

内部结构

Memcached将内存分为不同的page(默认为1 Mb)、每个page被(永久)赋给某一个slab。Memcached中有多个级别的slab,每一种slab决定了内存的粒度。举例说明: 假设memcached可以操作1 Mb的内存,每个page为1Kb,有3类slab:class1、class2、class3。Class1的内存分配粒度为96字节,class2的内存分配粒度为288字节,class3的内存分配粒度为1024字节。那么,memcached的内存结构为下图:

实际运行效果:

./bin/memcached -vv -m 1 -I 1k -f 3 -M -p 11225

slab class   1: chunk size        96 perslab      10

slab class   2: chunk size       288 perslab       3

slab class   3: chunk size      1024 perslab       1

class 1中,chunk的大小为96字节,共有10个chunk; class 2中,chunk的大小为288字节,共有3个chunk; class 3中,chunk的大小为1024字节,共有1个chunk;

数据处理

存储

数据(item)以key、value的形式进行存储,key最大为250 byte,value最大为1 Mb。 当存储item时,memcached会找到最“适合”大小的chunk用于保存这个item。例如item大小为200字节,对于上面的例子,这个item会被保存至class 2。

删除

Memcached不会主动删除数据,删除数据的情况有两种:

  1. 当新数据无法找到合适的位置进行存储时,会删除数据,删除数据的原则是LRU算法。

  2. 当client查找一个数据,server发现其已经过期时,就会删除该数据。

对于情况1,具体来说,当对一个item进行存储时,如果在适合的slab class中,既找不到空闲的chunk,又找不着这个slab class中空闲的page,memcached就会选择数据进行删除。Memcached首先会在处于LRU队列尾部的几个数据中,查找已经过期的数据;如果找不到过期的数据,就在队列尾部选择一个未过期的数据进行删除。

线程工作方式

Memcached采用libevent库进行多线程请求处理。 (libevent是一个事件出发的网络库,使用与 windows, linux, mac os 等的高性能跨平台网络库 ,支持的多种I/O 网络模型 epoll poll dev/poll select kqueue 等) Memcached中,每个线程拥有一个自己的libevent实例。 如果memcached采用TCP/IP连接,将采用单线程监听端口,监听线程获取到请求之后,将其分配给其中一个处理线程,选取处理线程的算法为round-robin。 如果memcached采用UDP连接,所有(空闲的)处理线程都有监听UDP socket,其中一个线程监听到请求之后,进行相应处理。

操作

Memcached中,所有操作都是原子操作,操作时间都是常数级别。Memcached的操作有以下几类:

存储

Set 如有没有key对应的value,添加value;如果已存在该key对应的value,更新value。

Add 如有没有key对应的value,添加value;如果已存在该key对应的value,则操作失败。

Replace 如果已存在该key对应的value,更新value;如果没有key对应的value,则操作失败。

Append / Prepend 在key对应的value前/后添加数据

Cas(check and set) Client会事先获取一个关于key的64位cas值,更新时,只有当server的cas值与client的cas一致时,才会执行value更新操作,否则操作失败。

查询

Get(Mget) 根据一个key或者一系列key,获取对应的value(s)。

Gets(get with cas) 获取key所对应的value的同时,也会返回cas值。

Exist 查询某个key是否存在(不是官方标准,部分client会实现该功能)

删除

Del 删除对应key的value

自增/自减

Incr / Decr 如果value的形式是为64位整数的字符串,Incr / Decr将对该value进行自增/自减操作。

Flush

将memcached中的所有item都设置为过期。

参考

—EOF—

几个实用有趣的Vim设置

| Comments

cmdheight (ch)

设置vim下方命令行的行数,觉得命令行在最下面一行有些压抑的同学,可以把行数设置得大一些

cursorline (cul)

高亮光标当前行

incsearch (is)

在搜索字符串时,会高亮显示当前匹配的位置

list

显示Tab和分行符,处理log的时候很有用

updatecount (uc)

设置输入多少个字符之后,会刷新swap文件

updatetime (ut)

设置多长时间后,会刷新swap文件

visaulbell (vb)

一般情况下,如果键入错误的命令或者光标到达一行结尾,都会发出声音作为提示。如果set vb,则这些情况就不会有声音,而是用视觉上的提醒,就是屏幕闪一下

参考资料:

http://vimdoc.sourceforge.net/htmldoc/quickref.html#option-list

—EOF—

C++虚函数的实现

| Comments

前段时间公司进行校招,大家那段时间也顺带看看一些面试题目。其中一道经典的题目就是C++中的虚函数是如何实现的?要是直接问我,我还真不记得了。(现在让我再去面试,肯定完蛋)

所谓虚函数,就是函数声明前使用virtual关键字,作用是当基类指针指向子类对象时,通过基类指针调用虚函数,运行的就是实际子类的对应函数,而不是基类的。这种特性在像Java这种“纯”面向对象的语言中,是默认实现的,在C++中就要进行显示声明。如果不显示声明的话,运行的就会是基类的函数。

C++中的虚函数是通过虚表(virtual table)实现的。声明类时,编译器会放一个隐藏的指针,这个指针放在了类的最开始位置,并且指向的就是虚表,虚表里面存放着这个类声明的各个虚函数的地址。假设如果有基类Base:

class Base {
    virtual void f1();
    virtual void f2();
}

那么Base类的虚表的结构就是:

Base
    __vptr    -->    Base::f1(), Base::f2()
    .....(其他成员)

假设还有类D:

class D: public Base {
    virtual void f1();
}

那么D的虚表就是下面这个样子的:

D
    __vptr    -->    D::f1(), Base::f2()
    .....

这样的话,当Base类的指针p_base= D()并且p_base->f1()的时候,调用的就是D的f1()了。注意,因为Base和D的虚表的结构一致,所以才能保证调用了正确的函数。

那么,如果是多重继承,即有多个基类时,结果如何呢?C++中,一个类的每一个基类,都有一个单独的虚表与之对应。假设类结构如下:

class B1 {
    virtual void f1();
    virtual void f3();
}

class B2 {
    virtual void f2();
    virtual void f3();
}

class D: public B1, public B2 {
    virtual void f1();
    virtual void f2();
}

那么D类的虚表结构就是:

D
    __vptr  --> D::f1(), B1::f3()
            --> D::f2(), B2::f3()
    .....

还有种情况,那就是在多重继承的情况下,子类声明了不属于任何父类的虚函数。例如:

class B1 {
    virtual void f1();
    virtual void f3();
}

class B2 {
    virtual void f2();
    virtual void f3();
}

class D: public B1, public B2 {
    virtual void f1();
    virtual void f2();
    virtual void f4();
}

子类声明的不属于任何父类的虚函数地址就会继续向第一个虚表的结尾放。

D
    __vptr  --> D::f1(), B1::f3(), D::f4()
            --> D::f2(), B2::f3()
    .....

这样的话,不管你用什么类的声明,声明类的虚表结构和实际类的就会是一致的(准确的说应该是父类虚表的结构一定是子类对应的虚表的前缀),这样就实现了基类指针调用子类函数的功能。

参考文章:

C++ 虚函数表解析

The virtual table

Busting C++ myths: virtual function calls are slow

—EOF—

马太效应 and 长尾理论

| Comments

平常准备没事记录下与专业相关的概念,再加上一些例子。让看的人更容易理解,也让自己更容易记忆和消化。

马太效应

这个名字来源于《圣经 马太福音》中的一句话:

“For to all those who have, more will be given, and they will have an abundance; but from those who have nothing, even what they have will be taken away.”

“凡有的,还要加给他叫他多余;没有的,连他所有的也要夺过来。”

马达效应就是指好的越好,差的越差,是一种两级分化的现象。

财富的分配就会遇到这种问题,有钱的人,更容易获取各种资源,因此可以凭借这些资源获得更多的钱;没钱的人,很难获取资源,也就很难有赚钱的机会,也就很难变得有钱。

好吧,社会问题就不说了。互联网中,也有很多这样的例子。

比如排序问题,可以想象成一个网站对各种商品进行排序的问题。需要根据商品的权重大小来对商品排序,商品的权重除了与自身的属性有关外,还跟在页面上面的表现有关,比如展现次数、点击次数、下单次数。这样,问题就出现了,一开始由于自身属性好而排在前面的商品,它的展现数据、点击数和下单数从概率上就会比排在后面的商品要好。也就是一开始排在前面的商品,它的权重就会越来越大,而一开始在后面的商品,与前面商品的权重差距就会越来越大,这就出现了马太效应。后续可能要加入时间热度、或者轮流展现的技术来消除这种马太效应。

长尾理论

这是一种与“二八定律”相悖的理论。“二八定律”是指80%的价值存在于总体的20%里面,例如80%的财富掌握在20%的人手中;80%程序运行时间被20%的代码所占用。但是,长尾理论是指,有些情况下,少部分个体的单位价值比较大,绝大部分个体的单位价值都比较少。例如,大公司就那么几家,而中小公司就有很多很多。这个似乎是符合“二八定律”的,但是,如果符合“二八定律”,那么少部分个体的价值之和应该占据总体价值的大多数。而实际情况是,那些单位价值较少的许多个体的价值之和,占了总体价值的大多数

Google就是利用长尾理论的公司之一,它的AdSense产品的用户就是不计其数的中小网站和个人用户上面,而没有面向那些看起来价值更大的大公司,结果AdSense占据了其收入的半壁江山。

亚马逊副经理史蒂夫·凯塞尔也说过:

“如果我有10万种书,哪怕一次仅卖掉一本,10年后加起来它们的销售就会超过最新出版的《哈利·波特》。”

但这长尾理论有个前提,那就是成本足够低。就好像出版书籍的成本足够低了,我才可以接受一本书只卖出去几本,因为就算只卖出去几本,我也是赚钱的。怪不得电子书现在这么火,一旦谁占领了市场,大概就是稳赚不赔了,我比较看好Amazon。

—EOF—

博客搬家了

| Comments

博客搬家了,从原来胡戈戈的虚拟主机搬到了Hello,Host!的VPS上面,域名也从qxavier.info换成了qxavier.me了。给博客搬家不是因为主机的质量或者服务问题,对于胡戈戈的服务态度以及虚拟主机的质量,我感到很满意。每次我有问题,他都能及时的响应,给我满意的答复。博客搬家的主要原因是自己想“折腾”、或者说是虚拟主机的限制有些多,除了写博客,其他事情基本做不了,就算在上面搭一个很简单的PHP程序,也很费劲。于是自己就将眼光投向了自由度更大、费用也还算合理的VPS。

自由度大了,但你也要因此而付出代价,那就是在博客搬家途中会踩到各种各样的“坑”。在这也整理一下,给大家提供些参考。

域名解析

.me等“非主流”域名,需要第三方解析服务,例如dnspod.cn,可以参考这里。这也是防止域名或者DNS服务器被墙的一个措施。

FTP服务安装

wordpress在安装插件或者上传图片时,需要FTP服务。因为操作系统用的是ubuntu,所以安装软件apt-get install一下就可以了,安装的是vsftpd。安装过程很简单,但关键是修改配置。除了以下配置:

# Uncomment this to allow local users to log in.
local_enable=YES
#
# Uncomment this to enable any form of FTP write command.
write_enable=YES

还需要特别注意掩码的设置,之前就是因为忘了设置,结果在上面耗了几乎两天的时间!

# Default umask for local users is 077. You may wish to change this to 022,
# if your users expect that (022 is used by most other ftpd's)
local_umask=022

用户权限设置

一般VPS直接就给我们root帐号用了,但是如果没有其他帐号,vsftpd是无法使用的,因为它不支持root登录,所以之前还得创建一个用户。另外,如果是将wordpress文件夹放在该用户下,还需要将里面所有文件的所有者和所有组从root改为这个用户。

Apache开启rewrite_mod模块

wordpress的文章url是虚拟的,依赖于apache的rewrite_mod模块,所以必须开启该模块。

数据库导入

之前以为很困难,其实比较简单。因为换了域名,所以数据库中的出现域名的地方需要替换成新的域名,只要dump处理,替换一下,然后导入到新的数据库就行了。

—EOF—

RD应该像QA一样思考

| Comments

在我的潜意识里,以前总有种不知从何而来的误解,认为QA(测试人员)只是在开发完的软件或者网页上点点鼠标,初步看看程序有没有错。有错的话,就会把RD(开发人员)喊过来,用鼠标复现一次错误,然后让RD去改代码;没有错的话,就算是大功告成了。

工作了才发现,事实并不是这样的。在一个同事身上发生过这样一件事情:他本来在QA部门实习,等到转正面试的时候,面试官对他说,你的能力大概还不能胜任QA,你可以考虑去做RD!这件事让我着实有些震惊。不过在公司工作了一段时间,发现身边的QA的确不比RD弱,无论是技术方面,还是工作态度方面。刚来公司实习的一个QA mm,什么Shell、Python都很熟,写test case,找bug也不在话下。有时候会一起和RD看代码、调bug,有次甚至一直到晚上十点半才下班,真是令人钦佩。还听说公司的好多中高层,都是QA出身,看来其中必定是有原因的。

RD常常会有一种莫名的优越感,认为是我把这个程序从无到有写出来的,大部分的功劳应该归我。代码写出来了,至于测试这种“小事”,就让QA来做吧。如果有了这种“优越感”,那么思维就会有局限,在这种思维里,下意识把开始写代码看作是起点,把程序可以运行起来并且貌似没什么问题看作是终点,其他部分,都不怎么关心。

QA同学则不然,QA更多时间是用来考虑代码写完之后的事情,他们不但会把整个软件当作是一个黑盒,去设计各种test case,之后还会进行压力测试,以检验代码在不同场景下的性能。有时发现了bug,还会自己去看RD的代码,进行静态的走查……所以说,QA应该是最了解需求的,无论是功能上的,还是性能上的,因为这些需求就是他们进行测试的依据。而RD通常都是听到一个需求之后,感觉自己懂了,然后就开始码代码了,这样往往容易考虑的过于细节,而把宏观的Big Picture抛在脑后。

可能RD的确要比QA思考的更细节一些,比如这个数据结构是用数组还是用链表,应该调用哪个API等等,但是RD也应该像QA一样思考,时时不忘功能的需求,以及性能上的指标。一个合格的RD,写出代码应该只是开发的第一步,后续还应该写一个测试用例、性能测试程序等等,对于自己写出的代码要负责到底。既然QA会走查RD写出的代码,那么RD为什么不能和QA一起测试呢?: ) 另外,在编码阶段或者在编码完成立刻发现的bug,比过了一段时间再测试时发现的bug,所处理的成本要低得多。

不妨向QA同学多取取经,问问他们是怎么设计测试用例的,用了哪些自动化测试的框架,有了哪些代码静态检查、压力测试的工具。最关键的,是要学习QA是如何确认软件是符合当初分析时的种种需求和规范的。这些如果都能学好,对RD应该大有裨益。

32位?64位?

| Comments

前两天室友买了台Dell Inspiron 14R,装机的时候向我要Win7 64位的安装盘,我略感惊讶,原来笔记本也普及64位了,我还停留在Windows XP的3.2G的“美好”回忆里。借此机会,正好看看平常所说的“32位”“64位”到底有什么不同。

到底是哪个地方的“位”?

本质上,讲的就是一个地方的“位”——CPU,也就是CPU进行一次运算支持的数据位数,或者是CPU一条基本指令的位数。

CPU的位数决定了运算的范围或者说是精确程度,也决定了计算机内存的寻址范围。

说到数字的精度,下面是IEEE754的标准中,32位浮点数(float)和64位浮点数(double)在内存中分别是如下形式:

image

IEEE 754 Double Floating Point Format.svg

其中,

float的计算方式为image,数值范围为1.18 × 10−38 ~ 3.4 × 1038 。

double的计算方式为image,数值范围为2.2250738585072014 x 10−308 ~ 1.7976931348623157 x 10308

大家也可以看一下斯坦福大学的公开课《范式编程》,前2节课讲的就是各种基础类型是如何在内存中存储的。

至于寻址范围,理论上32位的寻址范围是232,64位的就是264。但实际用户能用到的并没有那么多,32位机器也就3.4G左右可供用户使用,而64位机器一般是支持100~200G左右的内存,64位Windows 7专业版支持最大192G。

CPU –> OS –> Application

Processor, OS and application hierarchy

CPU、OS和Application,是一个前者决定后者的关系。是否可以运行64位的应用程序,得看OS是不是64位的;能不能安装64位的OS,得看CPU(以前主板等硬件)是否是支持64位的。下表可以清晰的各种情况的CPU、OS和Application是否兼容的情况:

image

—EOF—

http://www.techsupportalert.com/content/32-bit-and-64-bit-explained.htm

http://en.wikipedia.org/wiki/IEEE_floating_point

http://v.163.com/special/opencourse/paradigms.html

利用Expect添加开机启动程序

| Comments

都说“懒”是程序员的美德,如果“懒”是指想尽一切办法让计算机来完成重复的事情,那么这句话完全正确!

最近遇到一个问题,让我忍不住想要“懒”一下,情况是这样的:

5、6年前买的笔记本,我现在依然在使用,不过它实在太老,已经沦为“上网本”,我只能在上面装上Ubuntu+Chrome,平时上上网。但是,每次开机,我都要重复的做3件事:

  1. 开启ssh,为了能看看墙外的世界

  2. 打开含有ssh账户密码的文本文件

  3. 打开Chrome

这实在是在麻烦了!我得想个办法,让计算机能自动完成这些工作。关键的问题在于如何在ssh提示你输入密码的时候,能够自动输入,而不用我把密码贴过去。终于,我找到了一个在linux下可以和其他程序“talk”的程序——Expect。

Expect有3个关键的命令:expectsendspawn

expect——期待程序给我某个信息

send——Expect给程序发送某个信息

spawn——启动某程序

那么,目前我需要Expect的“逻辑”是这样的:

  1. 利用spawn启动ssh

  2. 期待(expect)ssh程序提示输入密码

  3. 把密码send给ssh程序

如果逻辑不复杂的话,Expect的语法还是相对简单的,可以参考《Exploring Expect》和man expect

Expect的代码如下:

#!/usr/bin/expect --

#一直等待程序的输入
set timeout -1

#设置变量
set username blablabla
set hostname blablabla.com
set password blablabla

#不知为何利用-f -n将ssh转入后台就无法成功运行,待解决
#spawn ssh -qTfnN -D 7070 $username@$hostname
#启动ssh程序
spawn ssh -qTN -D 7070 $username@$hostname
#匹配ssh程序的输出,如果含有“password”,就将密码send给ssh
expect "password" {send "$password\r"}

#之后将ssh控制权转交给用户
interact

应该还是比较通俗易懂的吧。

好,现在Expect的工作完成了, 在再之前加上启动gnome-terminal和用bash启动Expect,然后最后加上自启动Chrome,这样就大功告成了!之前寻找各种添加开机启动脚本的方法,都没能成功(怀疑和启动时还未进行gnome桌面环境有关),最后只能通过Ubuntu的”System”–>“Preferences”–>“Startup Applications”来添加启动命令,分别添加一下两条命令:

#开启gnome模拟终端
gnome-terminal -e "bash -l -c '~/test_expect.sh'" &
#开启Chrome浏览器
/opt/google/chrome/google-chrome

好了,这样我就可以每次开机让计算机替我完成之前重复的工作了,not perfect, but practical !

参考资料:

-EOF-

Python Decorator初体验

| Comments

前段时间在看一些有关python web framwork的时候,发现在python语言里竟然有“@”符号,一查资料,原来是python装饰器(python装饰器也可以通过除了“@”的其他语法进行定义)。装饰器,是一种设计模式,用于动态地给对象添加行为,之前的一篇文章也提到过。python中也有装饰器,不过和普遍意义上的装饰器不同,python中的装饰器实际上是一种“语法糖”,是一种语句的简便写法。比如,a[idx]就是*(a+idx)的一种简便写法,也算是一种语法糖。 假设有如下写法:

@dec
def func():
  pass

它等同于:

func = dec(func)

dec也是一个函数,只不过这个函数比较特殊,它的参数是一个函数(就是原先被装饰的函数func),它的返回值是一个函数。这样,再运行func()时,就是运行经过“装饰”的函数了。 python decorator可以帮助我们轻松地为函数或者类添加行为,而不用像普通的装饰器模式那样基于某个接口,大概这也算是动态语言的优势之一吧。 好,来看一些例子。

面向切面的编程

当我们要对许多函数进行相同的测试或者进行其他处理的时候,如果在每个函数都写一遍的话,代码太过重复,不利于统一管理。我们可以写一些统一的函数,然后在需要进行处理的函数前面加上decorator就行了。

def before(f):
    def wrapper():
        print 'before function'
        f()
    return wrapper

def after(f):
    def wrapper():
        f()
        print 'after function'
    return wrapper

@before
@after
def func():
    print 'this is function'

if __name__ == '__main__':
    func()

这样,就可以在函数前后进行相应的处理了。程序输出如下:

before function

this is function

after function

Singleton模式

def singleton(cls):
    instances = {}
    def wrapper():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return wrapper

@singleton
class MyClass:
    def __init__(self):
        self.num = 0

if __name__ == '__main__':
    c1 = MyClass()
    print c1.num
    c2 = MyClass()
    c2.num = 1
    print c1.num
    print (c1 == c2)

这样,每次“新建”MyClass类型,得到的都会是同一个实例。程序输出如下:

0

1

True

检验函数参数和返回值的类型

def accepts(*types):
    def check_args(f):
        #若去掉该断言,则代码可正常运行
        assert len(types) == f.func_code.co_argcount, 'type len: %d args len: %d' %(len(types), f.func_code.co_argcount)
        def new_f(*args, **kwds):
            for (a, t) in zip(args, types):
                assert isinstance(a, t), 'arg %r does not match %s' % (a, t)
            return f(*args, **kwds)
        new_f.func_name = f.func_name   # why?
        return new_f
    return check_args

def returns(rtype):
    def check_ret(f):
        def new_f(*args, **kwds):
            ret = f(*args, **kwds)
            assert isinstance(ret, rtype), 'return value %r does not match %s' %(ret, rtype)
            return ret
        new_f.func_name = f.func_name
        return new_f
    return check_ret

@returns((int, float))
@accepts(int, (int, float))
def func(arg1, arg2):
    return arg1 + arg2

if __name__ == '__main__':
    print func(1, 2.0)
    print func('1', '2')

在这段代码中,accepts函数用来保证被装饰的函数有两个参数,第一个参数为int型,第二个参数为int或float型,returns函数保证返回值为int或float型。那么,运行func(1, 2.0),assert就能通过,而func(‘1’, ‘2’)就不能通过。

Todo:这里还有一个问题,就是当@returns和@accepts语句交换顺序之后,accepts中检测函数参数个数的assert就无法通过,输出参数个数为0,还不知道是什么原因,待解决。

若去掉check_args函数中的对于f的参数个数的assert判断,则returns和accepts两个decorator无论什么顺序,代码均可正常运行。是由于new_f(*args, **kwds)改变了实际传入的参数的个数?

参考资料:

—EOF—