Python Decorator

Python有很多漂亮的特性,decorator就是其中之一。什么是decorator?其他语言里面也有类似于decorator的东西,那就是宏。但是c里面的宏的用法是有点诡异的,Bruce Eckel都说,c里面的宏完全是另外一种语言[1]不能同意更多了,从好多#define都会被当做面试题就可以看出来了:D)。

废话少说,先上一个decorator过把瘾。

def my_decorator(f):

    def new_f(*args, **kwargs):
        print 'enter new_f'
        return f(*args, **kwargs)

    return new_f

@my_decorator
def f(a, b):
    return a + b

上面的my_decorator只是在调用f之前打印“Enter new_f”出来,然后再返回f的结果。想要达到同样的目的,更普通一点的做法是:

def f(a, b):
    return a + b

f = my_decorator(f)

你会选择普通点的做法,还是文艺点的@my_decorator呢?

用类做decorator

上面的例子用函数作decorator,类也是可以的,只要类实现了__call__方法。其实用类做decorator是更普遍的做法。

class Decorator(object):

    def __init__(self, f):
        print '__init__()'
        self.f = f

    def __call__(self):
        print '__call__'
        self.f()

用Decorator类修饰函数试下:

>>> @Decorator
... def f():
...     print "in f()"
...
__init__()
>>> f()
__call__
in f()

与前面的函数做decorator不同的是,用Decorator类修饰f()时产生了一句输出。其实这也很容易理解,因为python的函数本身也是对象,只是我们无法定义函数初始化时候的操作罢了。类就不同了,我们可以随意定义__init__函数来实现想要的功能。

带参数的decorator

decorator也可以有自己的参数,比如:

@decorate(message)
def f(....)

上面的等效于

temp = decorate(message)
f = temp(f)

带参数的decorator和不带参数的其实是有很大不同的。不相信?那自己写一个函数实现的decorator,再同下面这个做下对比:

def decorate(message):
    def wrap(f):
        def wrapped_f(*args):
            print message
            return f(*args)
        return wrapped_f
    return wrap

@decorate('message')
def f(a, b):
    print 'add a and b'
    return a + b

猜你的反应肯定是:“WTF!怎么会有这么多层函数!”。先别急,把最外面的一层函数去掉,不就是最简单的没有参数的decorator了吗?其实wrap只是在decoration阶段接收message参数,wrapped_f才是真正会调用的函数。

用类实现的就是下面这个样子的了:

class Decorator(object):

    def __init__(self, message):
        self.message = message

    def __call__(self, f):

        def wrapped_f(*args):
            print self.message
            return f(*args)

        return wrapped_f

Real word decorators

理解decorator的基础之后,再看几个例子。

我们都知道c++的类可以有static方法,这类方法并不属于某一个具体的实例,而是存在于整个类的命名范围之内的。所有的实例都共用这个方法。在python中,我们也可以实现。方法就是是借助@staticmethod这个decorator:

class Foo(object):
    @staticmethod
    def add(x,y):
        return x + y

x = Foo.add(3,4)

另外一个常见的decorator就是@classmethod。用classmethod修饰的方法和普通的方法不同,普通的方法第一个参数都是self,而classmethod的第一个参数则是类。例如:

class Times(object):
    factor = 1
    @classmethod
    def mul(cls,x):
        return cls.factor*x

class TwoTimes(Times):
    factor = 2

x = TwoTimes.mul(4)

还有更多的用法,参见这里

参考

  1. Decorators I: Introduction to Python Decorators
  2. Python Decorators II: Decorator Arguments
  3. PythonDecoratorLibrary

理解cpu load average

要问我平常使用频率最高的命令,那必须是top,没有之一。虽然天天用,但有的参数还真不是很清楚。比如一直没弄明白cpu百分比和load average这两个参数哪个更“权威”一点。其实是因为对load average这个参数没什么概念,只知道大的时候就说明cpu负载比较高。为了以后不再糊涂,所以仔细研究了一下cpu load average。

cpu百分比

首先需要搞清楚的是,cpu的使用状态是一个离散的变量。就是说某一时刻cpu要么就是正在执行指令,要么就是处于空闲状态,cpu的利用百分比要不就是100%,要不就是0%。而绝不会说执行某条指令使用了50%的cpu。那为什么top里面有时候会显示除了0%和100%呢?其实top里面的的cpu百分比是时间上的概念:

k: %CPU — CPU usage
The task’s share of the elapsed CPU time since the last screen update, expressed as a percentage of total CPU time. In a true SMP environment, if ‘Irix mode’ is Off, top will operate in ‘Solaris mode’ where a task’s cpu usage will be divided by the total number of CPUs. You toggle ‘Irix/Solaris’ modes with the ‘I’ interactive command.

以上内容出自top的manual。假设top里面更新一次的间隔是1s,进程a的cpu百分比为50%,那么说明在过去的一秒里面,cpu花了0.5秒来执行进程a的指令。如果总cpu百分比是70%,那么在过去的一秒中,总共有0.7秒cpu处于计算的状态,剩下的0.3秒处于空闲状态。这就是cpu百分比的真正含义。

load average

load average与cpu percentage主要有两点不同:第一,load average反映的不仅仅是某一瞬间的cpu利用情况,而是反映了一种趋势。第二,load average反映了系统对cpu的需求情况,而不只是cpu有多少时间处于活跃的状态。

为了更好的理解load average的概念,我们可以同交通流量做个类比。有一条单行道(cpu),最多只能容纳一定数量的车子在上面行驶(cpu执行任务)。车子在路上行驶可能有三种情况:

  1. 路上很空旷,很可以容纳更多的车辆行驶。
  2. 路上很挤,有很多车辆在排队等待通过。
  3. 路上的车辆数量跟道路的容量刚好相等。

以上3种情况,分别对应cpu load average小于1、大于1和刚好等于1。通过这样一个类比,load average的概念就清晰多了:

  1. load average小于1:任务数小于cpu的处理能力,cpu处于相对空闲的状态
  2. load average大于1:任务数大于cpu的处理能力,cpu处理不过来,某些任务排队等候处理
  3. load average等于1:任务数刚好等于cpu的处理能力,cpu既不繁忙也不空闲

如果cpu是多核的呢?那么我们不应该用单行道来做类比,而是双行道、四行道等等。因此多核的cpu 要根据核的数量判断load average高低。如果是双核,那load average等于2的时候刚刚好,以此类推。核的数量可以通过读取proc文件系统中的信息判断:

grep 'model name' /proc/cpuinfo | wc -l

参考:

  1. http://www.linuxjournal.com/article/9001
  2. http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages

*args and **kwargs in Python

Python里面*args和**kwargs的用途和c里面**argv的用法类似,都是用来表示可变参数。int main (int argc, char **argv)//Variable-length arguments in c

*args和**kwargs的区别在于前者用于表示非关键字可变长度参数,后者表示关键字可变长度参数。

*args

def func_args(arg1, *args):
    print 'Normal argument:', arg1
    for arg in args:
        print 'variable arg:', arg
 
>>> func_args(1, 2, '3')
Normal argument: 1
variable arg: 2
variable arg: 3

**kwargs

def func_kwargs(arg1, **kwargs):
    print 'Normal argument:', arg1
    for key in kwargs:
        print 'Keyworded variable arg:', key, kwargs[key]

>>> func_kwargs(arg1=1, karg1='2', karg2=3)
Normal argument: 1
Keyworded variable arg: karg2 3
Keyworded variable arg: karg1 2

除了定义函数,*args和**kwargs还可以用来调用函数。

def func(arg1, arg2, arg3):
    print 'arg1:', arg1
    print 'arg2:', arg2
    print 'arg3:', arg3

>>> args = (2, '3')
>>> kwargs = {'arg2':2, 'arg3':'3'}
>>> func(1, *args)
arg1: 1
arg2: 2
arg3: 3
>>> func(1, **kwargs)
arg1: 1
arg2: 2
arg3: 3

Python调用c函数

如果你已经写好了一个c程序,但是又需要用脚本来实现同样的功能,应该怎么做?用python重新实现一次当然可以。但这样又得耗费不少精力,而且重复的劳动也没有意义。这种情况下就应该用ctypes模块了。

比如我想在脚本里面调用jenkins hash方法来计算某个序列的哈希值。python没有这样的模块可用。不过手头倒是有jenkins hash的c程序。看这段代码,如果用python再实现一次,是有点复杂的。所以最好的方式是把这段程序拿过来直接用。

#include <stdio.h>
#include <string.h>
#define mix(a,b,c)                              \
    {                                           \
        a -= b; a -= c; a ^= (c >> 13);         \
        b -= c; b -= a; b ^= (a << 8);          \
        c -= a; c -= b; c ^= (b >> 13);         \
        a -= b; a -= c; a ^= (c >> 12);         \
        b -= c; b -= a; b ^= (a << 16);         \
        c -= a; c -= b; c ^= (b >> 5);          \
        a -= b; a -= c; a ^= (c >> 3);          \
        b -= c; b -= a; b ^= (a << 10);         \
        c -= a; c -= b; c ^= (b >> 15);         \
    }


unsigned int bob_hash(void *val, unsigned int length)
{
    char *k = (char *)val;
    unsigned long a,b,c,len;

    /* Set up the internal state */
    len = length;
    a = b = c = 0x9e3779b9;  /* the golden ratio; an arbitrary value */

    /* Handle most of the key */
    while (len >= 12)
    {
        a += (k[0] +((unsigned long)k[1] << 8) +((unsigned long)k[2] << 16) +((unsigned long)k[3] << 24));
        b += (k[4] +((unsigned long)k[5] << 8) +((unsigned long)k[6] << 16) +((unsigned long)k[7] << 24));
        c += (k[8] +((unsigned long)k[9] << 8) +((unsigned long)k[10]<< 16)+((unsigned long)k[11] << 24));
        mix(a,b,c);
        k += 12; len -= 12;
    }

    /* Handle the last 11 bytes */
    c += length;
    switch(len)              /* all the case statements fall through */
    {
    case 11: c+=((unsigned long)k[10] << 24);
    case 10: c+=((unsigned long)k[9]  << 16);
    case 9 : c+=((unsigned long)k[8]  << 8);
        /* the first byte of c is reserved for the length */
    case 8 : b+=((unsigned long)k[7] << 24);
    case 7 : b+=((unsigned long)k[6] << 16);
    case 6 : b+=((unsigned long)k[5] << 8);
    case 5 : b+=k[4];
    case 4 : a+=((unsigned long)k[3] << 24);
    case 3 : a+=((unsigned long)k[2] << 16);
    case 2 : a+=((unsigned long)k[1] << 8);
    case 1 : a+=k[0];
    }
    mix(a,b,c);

    return c;
}

int hash_string(void *tmpstr)
{
    int hash = (int)bob_hash(tmpstr, strlen(tmpstr));
    if(hash < 0)
    {
        hash = -hash;
    }
    return (int)(hash % 200);
}

int main()
{
    printf("%d", hash_string("15882917276"));
}

首先把这段程序编译为so文件。

gcc -fPIC -shared bob_hash.c -o bob_hash.so

然后在python里面用ctypes加载刚才的动态库。argtypes和restype分别对应函数的参数和返回值的类型。这样就可以直接调用了。

>>> from ctypes import CDLL, c_int, c_void_p
>>> bob_hash = CDLL('/home/jxq/code/bob_hash.so')
>>> hash_string = bob_hash.hash_string
>>> hash_string.argtypes = [c_void_p]
>>> hash_string.restype = c_int
>>> hash_string('123')
90

给从前的自己

想起美剧《英雄》里的日本小胖子,为了拯救世界,从未来回到过去。找到当时的自己,讲了几句至关重要但是又难以理解的话之后,消失了。后来到底有没有成功的拯救地球,我不太关心。不过,我倒是很羡慕小胖子穿越的本事。

有的时候我也禁不住去幻想,如果我也可以回到过去,会留下什么重要的信息给当时的自己呢?认真考虑考虑,还真想出了几点。当然穿越这事儿是不太可能发生的,写在这里,就当是个总结。
Continue reading

Python修改函数参数?

有很多次尝试修改函数参数的值,因为不知道究竟能不能改,所以每次都写个函数测试一下。经过若干次的“测试”,发现还是没有明确的结论。好像对于不同类型的参数,结果不同。最终,在stackoverflow上面找到了问题的答案

先猜下这段代码的输出:

def f(n, x):
    n = 2
    x.append(4)
    print 'In f():', n, x

def main():
    n = 1
    x = [0,1,2,3]
    print 'Before:', n, x
    f(n, x)
    print 'After: ', n, x

main()

c语言的函数修改参数,参数类型必须为指针才可以。c++则是通过传递“引用”的方式。那么Python有类似的处理方式吗?
Continue reading

理解python生成器

先看一个函数:

def group(seq, size):
    """
    Returns an iterator over a series of lists of length size from iterable.

        >>> list(group([1,2,3,4], 2))
        [[1, 2], [3, 4]]
        >>> list(group([1,2,3,4,5], 2))
        [[1, 2], [3, 4], [5]]
    """

    def take(seq, n):
        for i in xrange(n):
            yield seq.next()

    if not hasattr(seq, 'next'):  
        seq = iter(seq)
    while True:
        x = list(take(seq, size))
        if x:
            yield x
        else:
            break

函数中没有常见的return,而是使用了yield。yield和return相同的地方是都用来向函数的调用者返回值。下面这个简单的例子展示了yield与return的不同。
Continue reading

用python和redis打造短网址服务

这篇文章的启发,用python+redis实现了文中的短网址服务。其中使用了web.py作为web框架,redis作为后台存储。部署在dotcloud上面。访问http://81aca6aa.dotcloud.com查看效果。由于使用了几个css3的效果,建议使用firefox、chrome或者ie 8以上的浏览器打开。代码放在GitHub

Web.py比django精简很多,使用起来也相对容易些。基本上对着文档,几分钟就可以写个服务出来。Django给人的感觉则是非常庞大,但是其功能是相当完善的。在框架的选择上,没有最好的,只有最适合的。这个短网址的服务不需要太复杂的功能,而且仅仅是作为一个demo写来玩玩而已,就选择了web.py。
Continue reading

X server、Xorg、X window

X window、Xorg、X server这几个名字,听着都很熟悉,只知道这些东西是与linux上面的gui有关的,却对其内部的结构一无所知。今天花了点时间研究下,算是了解了个大概。

X window system是一个总称,它不仅包括了一些程序,还包括一些网络协议。X系统最初是由MIT于1984年开发,当时的X是用在Unix System V上面。在X的设计之初,设计者们希望能通过网络来使用图形界面,因此设计了很多的网络协议。随着不断的开发,在1987年X进化到11这个版本,这个版本的X进步明显,因此后来的X系统基本上都是在这一版的基础上进行改进的。在1994年发布了X11R6,后来的架构都是沿用这一版本。后来因为授权的问题,X系统由Xorg基金会接手进行维护。这就是为什么在Arch里面安装X的时候需要安装xorg-server xorg-xinit xorg-utils xorg-server-utils等等。

X使用了client-server模式,一个X server与多个X client进行通信。X server接受进行图形输出的请求,并且向X client返回用户的输入(鼠标、键盘等)。X系统的client-server模式,与我们平常所见到的刚好是相反的。比如有多个用户机要连接到一台服务器上面,那么用户机上需要安装的是X server,而服务器上安装的则是X client。这点可能开始很难理解,但是理解X server和X client分别是起什么作用之后,就会明白这是非常自然的设计。

X server的主要功能就是管理主机上的硬件,包括键盘、鼠标、显示器、显卡。因为用户机不可能与服务器的硬件配置相同,所以要将X server安装在用户机上面。服务器上的X client向用户机上面的X server发出绘制窗口的请求,然后由X server来完成请求。此外,X server还要将用户的输入通知给X client。

X client的主要功能就是接受由X server发来的用户输入行为,然后进行处理,再将绘图请求返回给X server。

浅拷贝与深拷贝

对象赋值实际上是只是对象的引用,如果想要复制某个对象,需要通过完全切片或者工厂函数以及使用copy模块的方法来完成。但是这三种方法都是所谓的“浅拷贝”。首先明确,“浅拷贝”和“深拷贝”的概念仅仅是针对容器类型的对象,因为非容器类型没有拷贝这一说法。他们的区别在于:

  • 浅拷贝将创建一个新的容器对象,然后把对原容器中元素的引用插入到新容器中。
  • 深拷贝将创建一个新的容器对象,然后递归地把原容器中元素的“拷贝”插入到新容器中。

Continue reading