objective-c protected instance variable

最近在做斯坦福公开课cs193p的课后练习Machismo,其中用到了类的继承。

// GameViewController.h
@interface GameViewController : UIViewController
@property (nonatomic) Game *game;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@end

// CardGameViewController.h
@interface CardGameViewController : GameViewController
@property (nonatomic) CardMatchingGame *game;
@end

CardGameViewControllerGameViewController的一个子类。因为在子类中需要初始化@property (nonatomic) CardMatchingGame *game,所以就要在子类中访问基类中的instance variable,也就是_game。如果这样尝试的话,编译器会给出一个错误。因为_game是基类的private instance variable,子类是无法访问的。那如何才能将这个instance variable变为protected?只要将_game在interface中声明即可。

// GameViewController.h
@interface GameViewController : UIViewController {
    Game *_game;
}
@property (nonatomic) Game *game;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@end

在interface中声明的变量,默认都是@protected。如果想定义为其他类型,可以使用@public@private等关键字。这点和C++还是类似的。更多详细内容请参阅下面的资料。

参考

  1. How are declared private ivars different from synthesized ivars?
  2. Are synthesized instance variables generated as private instead of protected?
  3. Objective-C Access To Instance Variables
  4. Private properties, methods and ivars in Objective-C
  5. Objective-C Tuesdays: instance variables

redis sds数据结构的指针技巧

redis中没有直接使用char *,而是使用自定义的sds结构来表示字符串。sds的定义如下:

typedef char *sds;
struct sdshdr {
    int len;
    int free;
    char buf[];
};

sdshdr中的len代表buf中字符串的长度(不包括结尾的’\0’),free代表buf剩余可用的长度。这种结构与char *相比有什么优点,请参考Redis设计与实现

上面看到sds这种类型就是char *,那sds和sdshdr有什么关系?先来看生成sds的函数:

sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;

    if (init) {
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    sh->len = initlen;
    sh->free = 0;
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    return (char*)sh->buf;
}

每当生成sds的时候,会先生成sdshdr,然后返回sdshdr->buf。原来sds就是sdshdr->buf,sdshdr的意思应该是“sds header”。sds相关的函数大多以sds作为参数的类型,而不是sdshdr,当需要用到sdshdr的时候,redis使用了一个技巧,以便快速的通过sds得到sdshdr。以sdslen这个函数为例:

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

通过将sds的地址减去sizeof(struct sdshdr),便得到了sdshdr的地址。redis的文档里面已经解释了这个技巧。假如我们通过sdsnewlen("redis", 5)生成了一个sds,其sdshdr的地址为sh,那么像下图所示:

-----------
|5|0|redis|
-----------
^   ^
sh  sh->buf

sh = sh->buf - sizeof(struct sdshdr)。看到这里也许会有疑问,sizeof(strcut sdshdr)不应该是sizeof(int) + sizeof(int) + sizeof(char *)吗?C99标准中有如下规定:

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply.

However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

sdshdr最后一个成员是未完整定义的数组类型,这种结构体的定义方式使buf成为可变数组成员。内核中也有flexible array member的应用:

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);

这样就减少了kmalloc的调用次数。这就是通过将buf的地址减去sizeof(struct sdshdr)可以得到sdshdr地址的原因。

参考

  1. Flexible array members in C – bad?
  2. Size of a struct with two void pointers is 4?
  3. Hacking Strings

django+tornado

Django的部署方式有很多种:WSGI,FastCGI等等,当然最主流的还是WSGI。比如官方文档中推荐的django+apache+mod_wsgi, django+gunicorn,django+uWSGI等等。那么什么是WSGI?

其实这个问题也困扰了我很久。每当我想研究一下WSGI到底是何方神圣的时候,google给出的答案就是pep 3333,不过这篇像论文一样的pep 3333比较晦涩难懂,一直都没搞明白。直到后来发现了这篇文章。和其他的文章不一样,文章没有开头就讲WSGI是什么,反而开始就说WSGI不是什么:

What WSGI is not: a server, a python module, a framework, an API or any kind of software. What it is: an interface specification by which server and application communicate.

看到这里心中无比的敞亮,原来WSGI就是一种标准:application和server之间交互的标准。application就是通常所说的framework,server则接受客户端的请求并将请求发给application处理,然后将application的应答返回给客户端。现在应该明白,之前说的apache+mod_wsgi, gunicorn, uWSGI都是扮演WSGI Server的角色,只要兼容WSGI的server都可以用来和任意的python web framework搭配。

今天的主角是Tornado。它包含两部分:一个framework和一个server。由于tornado使用了异步非阻塞的IO模型,所以特别适用于长轮询的场景,性能比较强大。今天主要用tornado的http server来部署django应用。

tornado的wsgi模块包含了两个类WSGIApplication和WSGIContainer。WSGIApplication是把tornado作为framework,用其他的WSGI server来驱动。WSGIContainer则是我们需要的,把其他web framework的应用封装一下,以便在tornado server上运行。
Continue reading

使用el-get管理emacs包

el-get是emacs的一个包管理器。你可以把它当作是emacs的synaptic、yum或者pacman,功能强大,用起来也十分方便。通过它你可以安装和管理各种elisp的包。对于我来说,el-get最大的功能在于,当你有多台机器时,仅仅需要一个.emacs文件就可以保证多个机器上的elisp包一致,而不是每个机器都需要手动管理。想想看,没有el-get的话,当你重装系统之后需要花多少时间来安装各种elisp包吧。有了el-get之后,只要一个.emacs文件,当你第一次打开emacs的时候,各种包已经开始默默的下载和安装了,完全不用你操心。这是一件多么美好的事啊。

el-get对软件包的描述放在recipe文件中,包括安装方式、url、安装完之后的操作,以及软件包的基本配置。也就是说当你用el-get安装完某个包之后,自己的.emacs文件基本上不用修改,新的包就可以用了。el-get支持多种安装方式,可以直接从github clone,也可以从指定的url下载回来然后解压,另外还支持ELPA,cvs,bzr,apt-get,pacman等等。最强大的地方在于你可以自己定义recipe,如果默认的recipe文件中没有你需要的包,那么你完全可以自己写一个。如果你在github上面fork了el-get,可以给作者提交pull request,请求将自己定义的recipe加入默认的recipe中。这样如果下次有别人需要用到这个包,你的recipe就派上用场了。

安装el-get非常简单,只要打开你的emacs,切换到scratch buffer,复制下面这段代码,然后在最后一个括号后面C-j就可以了。

(url-retrieve
 "https://raw.github.com/dimitri/el-get/master/el-get-install.el"
 (lambda (s)
   (let (el-get-master-branch)
     (goto-char (point-max))
     (eval-print-last-sexp))))

安装好el-get后,配置文件里面加入下面的内容

(setq
 el-get-sources
 '((:name asciidoc
          :type elpa
          :after (progn
                   (autoload 'doc-mode "doc-mode" nil t)
                   (add-to-list 'auto-mode-alist '("\\.adoc$" . doc-mode))
                   (add-hook 'doc-mode-hook '(progn
                                               (turn-on-auto-fill)
                                               (require 'asciidoc)))))

   (:name buffer-move   ; have to add your own keys
           :after (progn
                    (global-set-key (kbd "<C-S-up>") 'buf-move-up)
                    (global-set-key (kbd "<C-S-down>") 'buf-move-down)
                    (global-set-key (kbd "<C-S-left>") 'buf-move-left)
                    (global-set-key (kbd "<C-S-right>") 'buf-move-right)))

   (:name smex  ; a better (ido like) M-x
          :after (progn
                   (setq smex-save-file "~/.emacs.d/.smex-items")
                   (global-set-key (kbd "M-x") 'smex)
                   (global-set-key (kbd "M-X") 'smex-major-mode-commands)))

   (:name lisppaste        :type elpa)))

(setq my-packages
      (append
       '(cssh el-get switch-window escreen vkill xcscope color-theme color-theme-railscasts color-theme-tomorrow yasnippet python-mode python-pep8 pymacs rope ropemacs ropemode pylookup haskell-mode anything helm emacs-w3m auto-complete browse-kill-ring sr-speedbar session popup markdown-mode xml-rpc-el)
       (mapcar 'el-get-source-name el-get-sources)))

(el-get 'sync my-packages)

上面的el-get-sources变量是自定义的recipe,my-packages则是需要安装的包。只要把包的名字写在my-packages里面,下次启动emacs的时候el-get就会自动安装。

使用el-get就是这么简单。但是在使用的过程中发现el-get还有些不完美的地方。比如用el-get安装color-theme这个插件的时候,总会提示下载的color-theme.tar.gz无法解压。经过一番调查发现原来是el-get的http-method有问题。el-get下载的color-theme.tar.gz总会比用wget下载的少几个字节。http-method的实现是先用url-retrieve把文件拉到当前的buffer中,再用write-file将当前buffer的内容写入文件。问题就出在write-file这里。write-file默认会对buffer进行一些编码的转换,结果导致丢掉了几个字节。修改的方法也很简单,只要将write-file替换为write-region即可。发现这个bug之后在github上给作者提交了pull request,已经被合并了。之后再有人用el-get就不会碰到这个问题了。

学习elisp

对我来说,Emacs已经成为日常用的最多的编辑器了,vi则只有在ssh的时候才会用到。不过emacs可是很需要调教的,如果没有好好配置,基本上处于不可用的状态。关于目前使用的emacs配置,改天会再详细写出来。今天只是分享一个自己写的函数。

我很庆幸,现在的我能够沉下心来去研究elisp这么神奇的语言。虽然elisp的用途很狭隘,但它绝对会对提升你的生产力产生巨大的帮助。emacs我用了很久,大概跟我用linux的时间一样长。但是从2012年开始我才算真正入了emacs的门。以前只是在网上搜罗各种配置文件,安装各种插件。但是配置却看不懂,做不到自己定制,只是简单的依葫芦画瓢。配置不适用也不知道该如何下手解决。

随着使用的深入,现在慢慢学会了如何解决遇到的emacs问题。搞定了以前一直很想解决的bug。有些bug,其实是emacs的某些feature没有配置好。比如之前python-mode中有时出现C-x C-f会卡死的情况,最后发现原来是网上广泛流传的python-mode配置中(setq ido-use-filename-at-point 'guess)这一句,导致打开文件的时候会调用python-mode-path函数。只要把这句注释掉即可。再比如python-mode中M-b的跳转总是不正确,在其他的major mode中M-b总是会把“_”当作单词的分隔符,但python-mode中却不会。这点实际使用中会造成很大的困扰,因为不仅和其他的major mode行为不一致,即便在python-mode中M-b和M-f的行为也不一致。后来到python-mode的项目主页上准备提交这个bug,结果发现已经有人提过了,原因是“_”在python-mode的syntax table中是属于word类而不是symbol类。现在python-mode中已经有了一个自定义的变量py-underscore-word-syntax-p,只要把此变量的值设为nil即可。

解决了前面的几个bug之后,开始想自定义几个函数已满足自己的需求。以前用惯了vim的yy复制一行的功能,现在emacs里面没有,非常的不适应,这个操作如果自己做,需要按太多次键盘。先要C-a跳到行首,再C-spc设置标记,再C-e跳到行尾。于是开始翻阅elisp的文档。发现elisp的文档写的真是太人性化了,目录编排尤其合理,即便是我这种从来没用过的人也可以对着目录找到自己所需要的函数或者变量。下面是复制一行的函数

;;copy line
(defun copy-line ()
  (interactive)
  (kill-ring-save (save-excursion
                    (back-to-indentation)
                    (point))
                  (line-end-position))
  (message "line copied"))
(global-set-key "\C-c\C-y" 'copy-line)

实现起来很简单,kill-ring-save的描述是“Save the region as if killed, but don’t kill it”,即我们通常理解的“复制”。这个函数会接受两个参数,待复制区域的起始和结束位置。save-excursion的作用是再执行完函数参数内的操作后,把光标移动到执行之前的位置。save-excursion这段的作用是获得行的起始位置,略去前面的缩进。line-end-position很明显就是获得行的结束位置。这样就实现了复制一行的功能,再把这个函数绑定一个快捷键C-c C-y即可。

现在终于体会到了emacs的强大。emacs的各种插件还不足以让他成为神器,最强大的地方在与用户可以很容易的自己定制所需要的功能,而不用依赖于别人开发的插件。难怪有人说emacs是伪装成编辑器的操作系统了。

编译器拷贝优化(copy elision)

Copy Elision是大多数编译器都会执行的一项优化。gcc的手册里面是这样描述的:

-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

当需要创建一个只是用来初始化其他对象的临时对象时,编译器可以忽略此操作,不执行拷贝构造函数(copy-initialization),而是直接把临时对象创建在目标对象的空间上(direct-initialization),从而提高程序的性能。例如:

class foo
{
public:
    foo() {cout<<"ctor"<<endl;};
    foo(const foo &rhs) {cout<<"copy ctor"<<endl;}
    foo(foo &&rhs) {cout<<"move ctor"<<endl;}

    void swap(foo &with) {cout<<"swap"<<endl;}
    void incr() {i++;}

private:
    int i;
};

foo f()
{
    foo tmp;
    return tmp;
}

int main()
{
    foo a = f();
    return 0;
}

函数f创建了一个foo对象并将其返回。main函数中调用foo的copy constructor来初始化对象a。理论上应该默认构造函数被调用一次(函数f内),拷贝构造函数被调用两次(函数f返回tmp和foo a = f())。但实际上只有一次默认构造函数被调用。因为编译器将临时变量直接构造在了对象a的空间上,省去了拷贝的操作。gcc默认开启了优化,如果想看到没有优化的结果,编译时要加-fno-elide-constructors选项。

上面的优化叫做RVO(Return Value Optimization,返回值优化)。除了返回值,编译器还会对函数的参数进行优化。

int g(foo a)
{
    return 0;
}

int main()
{
    g(foo());
    return 0;
}

当函数的参数是右值的时候,也就说明此参数是一个临时对象。编译器将不会对此参数进行拷贝的动作。

如何利用拷贝优化

假设我们需要写这样一个函数:

int f1(const foo &a)
{
    foo tmp(a);
    do_something(tmp);

    return 0;
}

为了避免改变参数,函数f1生成一份参数的拷贝,更改复制得到的对象。其实这样做并没有什么好处,既然复制操作无法避免,那就让它来的自然点好了。

int f2(foo a)
{
    do_something(a);

    return 0;
}

与f1相比,f2的好处在于,当参数是右值的时候,拷贝操作被省略了。如果参数是左值,那么两个函数都会进行一次拷贝。

但有一点需要注意,如果函数将参数返回,那么RVO将不会起作用。

foo f3(foo a)
{
    do_something(a);

    return a;
}

其实这也很容易理解,编译器在编译使用此函数的代码时,必定会使用两个地址,一个存放按值传递的参数,另一个存放返回值。在返回的时候,一定执行一次拷贝构造函数,将参数复制到返回值的地址上。更详细的解释参见Why is RVO disallowed when returning a parameter?

即使不能利用RVO,我们也可以将函数优化。一般而言,默认构造函数和swap操作会比拷贝构造函数更快,所以可以这样写:

foo f4(foo a)
{
    do_something(a);
    foo tmp;
    tmp.swap(a);

    return tmp;
}

参考

  1. The Name Return Value Optimization
  2. C++: Exploiting Copy Elisions
  3. Why is RVO disallowed when returning a parameter?
  4. Is RVO (Return Value Optimization) guaranteed for all objects in gcc compilers?

Python Argument list Unpacking

今天遇到一个问题,假设有字符串s = "INSERT INTO tbl VALUES({0}, {1}, {2}, {3}, {4})",如何用format函数将一个元组的内容映射到字符串上去呢?当然,最简单的就是:

t = (1, 2, 3, 4)
s.format(t[0], t[1], t[2], t[3])

不过这种做法实在是too simple,too naive了。一点也不pythonic。正确的做法是:

s.format(*t)

怎么样,这样是不是看起来顺眼多了?

如果m是一个列表或元组,那么f(*m)将会把列表中的元素分别作为函数f的位置参数。同样,如果m是个字典类型,那么f(*m)将把m中的每个key:value对作为函数f的关键字参数。

参考

  1. Unpacking Argument Lists
  2. The Python IAQ

[译]详解C++右值引用

C++0x标准出来很长时间了,引入了很多牛逼的特性[1]。其中一个便是右值引用,Thomas Becker的文章[2]很全面的介绍了这个特性,读后有如醍醐灌顶,翻译在此以便深入理解。

目录

  1. 概述
  2. move语义
  3. 右值引用
  4. 强制move语义
  5. 右值引用是右值吗?
  6. move语义与编译器优化
  7. 完美转发:问题
  8. 完美转发:解决方案
  9. Rvalue References And Exceptions
  10. The Case of the Implicit Move
  11. Acknowledgments and Further Reading

概述

右值引用是由C++0x标准引入c++的一个令人难以捉摸的特性。我曾偶尔听到过有c++领域的大牛这么说:

每次我想抓住右值引用的时候,它总能从我手里跑掉。

想把右值引用装进脑袋实在太难了。

我不得不教别人右值引用,这太可怕了。

右值引用恶心的地方在于,当你看到它的时候根本不知道它的存在有什么意义,它是用来解决什么问题的。所以我不会马上介绍什么是右值引用。更好的方式是从它将解决的问题入手,然后讲述右值引用是如何解决这些问题的。这样,右值引用的定义才会看起来合理和自然。

右值引用至少解决了这两个问题:

  1. 实现move语义
  2. 完美转发(Perfect forwarding)

如果你不懂这两个问题,别担心,后面会详细地介绍。我们会从move语义开始,但在开始之前要首先让你回忆起c++的左值和右值是什么。关于左值和右值我很难给出一个严密的定义,不过下面的解释已经足以让你明白什么是左值和右值。

在c语言发展的较早时期,左值和右值的定义是这样的:左值是一个可以出现在赋值运算符的左边或者右边的表达式e,而右值则是只能出现在右边的表达式。例如:

int a = 42;                                                
int b = 43;                                                
                                                           
// a与b都是左值                              
a = b; // ok                                                
b = a; // ok                                                
a = a * b; // ok                                            
                                                           
// a * b是右值:                                      
int c = a * b; // ok, 右值在等号右边
a * b = 42; // 错误,右值在等号左边

在c++中,我们仍然可以用这个直观的办法来区分左值和右值。不过,c++中的用户自定义类型引入了关于可变性和可赋值性的微妙变化,这会让这个方法变的不那么地正确。我们没有必要继续深究下去,这里还有另外一种定义可以让你很好的处理关于右值的问题:左值是一个指向某内存空间的表达式,并且我们可以用&操作符获得该内存空间的地址。右值就是非左值的表达式。例如:

// 左值:                                                        
//                                                                
int i = 42;                                                        
i = 43; // ok, i是左值
int* p = &i; // ok, i是左值
int& foo();                                                        
foo() = 42; // ok, foo()是左值
int* p1 = &foo(); // ok, foo()是左值
                                                                   
// 右值:                                                        
//                                                                
int foobar();                                                      
int j = 0;                                                        
j = foobar(); // ok, foobar()是右值
int* p2 = &foobar(); // 错误,不能取右值的地址
j = 42; // ok, 42是右值

如果你对左值和右值的严密的定义有兴趣的话,可以看下Mikael Kilpeläinen的文章[3]
Continue reading

new的二三事

stackoverflow真是个绝佳的平台。基本上所有的问题都可以找到答案。今天遇到两个比较新鲜的关于new的问题,一个是operator new,另一个是placement new。

operator new

问个问题先:“new operator”和“operator new”有什么区别?new operator大家都很熟悉,就是平常用来创建对象的那个操作符咯。

my_class *x = new my_class(0);

operator new是什么?好像从来没听过。operator new是用来分配内存的,仅此而已。在概念上来说,operator new和malloc类似。虽然operator new并不常用,但是如果要自己写容器之类的,那么operator new就有用武之地了。operator new可以这样用:

char *x = static_cast<char *>(operator new(100));

operator new和new operator之间的区别在于,new operator会先用operator new去分配内存,然后调用类的构造函数去初始化对象。

placement new

上面说了,new的时候会先分配内存,然后调用构造函数。但是placement new允许在已经分配的内存上面直接构造对象。

这个东西是很有市场的。不用再次分配内存会节省很多时间,提高程序的效率。另外有时候我们不希望发生分配内存出错的情况,placement new就派上用场了,因为内存已经分配好了。placement new要这样用:

char *buf  = new char[sizeof(string)];   //pre-allocated buffer
string *p = new (buf) string("hi");  //placement new
string *q = new string("hi");  //ordinary heap allocation

参考

Difference between ‘new operator’ and ‘operator new’?
What uses are there for “placement new”?

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