python装饰器的一个妙用

好吧,我知道是大半夜……,但我还是觉得赶紧花上半个小时,把这最新的想法分享出来是值得的~直接进入正题~

我们来模拟一个场景,需要你去抓去一个页面,然后这个页面有好多url也要分别去抓取,而进入这些子url后,还有数据要抓取。简单点,我们就按照三层来看,那我们的代码就是如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def func_top(url):
    data_dict= {}
 
    #在页面上获取到子url
    sub_urls = xxxx
 
    data_list = []
    for it in sub_urls:
        data_list.append(func_sub(it))
 
    data_dict['data'] = data_list
 
    return data_dict
 
def func_sub(url):
    data_dict= {}
 
    #在页面上获取到子url
    bottom_urls = xxxx
 
    data_list = []
    for it in bottom_urls:
        data_list.append(func_bottom(it))
 
    data_dict['data'] = data_list
 
    return data_dict
 
def func_bottom(url):
    #获取数据
    data = xxxx
    return data

func_top是上层页面的处理函数,func_sub是子页面的处理函数,func_bottom是最深层页面的处理函数,func_top会在取到子页面url后遍历调用func_sub,func_sub也是同样。
如果正常情况下,这样确实已经满足需求了,但是偏偏这个你要抓取的网站可能极不稳定,经常链接不上,导致数据拿不到。

于是这个时候你有两个选择:

  • 1.遇到错误就停止,之后重新从断掉的位置开始重新跑
  • 2.遇到错误继续,但是要在之后重新跑一遍,这个时候已经有的数据不希望再去网站拉一次,而只去拉没有取到的数据

对第一种方案基本无法实现,因为如果别人网站的url调整顺序,那么你记录的位置就无效了。那么只有第二种方案,说白了,就是要把已经拿到的数据cache下来,等需要的时候,直接从cache里面取。

OK,目标已经有了,怎么实现呢?
如果是在C++中的,这是个很麻烦的事情,而且写出来的代码必定丑陋无比,然而庆幸的是,我们用的是python,而python对函数有装饰器。

所以实现方案也就有了:
定义一个装饰器,如果之前取到数据,就直接取cache的数据;如果之前没有取到,那么就从网站拉取,并且存入cache中.

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import os
import hashlib
 
def deco_args_recent_cache(category='dumps'):
    '''
    装饰器,返回最新cache的数据
    '''
    def deco_recent_cache(func):
        def func_wrapper(*args, **kargs):
            sig = _mk_cache_sig(*args, **kargs)
            data = _get_recent_cache(category, func.__name__, sig)
            if data is not None:
                return data
 
            data = func(*args, **kargs)
            if data is not None:
                _set_recent_cache(category, func.__name__, sig, data)
            return data
 
        return func_wrapper
 
    return deco_recent_cache
 
def _mk_cache_sig(*args, **kargs):
    '''
    通过传入参数,生成唯一标识
    '''
    src_data = repr(args) + repr(kargs)
    m = hashlib.md5(src_data)
    sig = m.hexdigest()
    return sig
 
def _get_recent_cache(category, func_name, sig):
    full_file_path = '%s/%s/%s' % (category, func_name, sig)
    if os.path.isfile(full_file_path):
        return eval(file(full_file_path,'r').read())
    else:
        return None
 
def _set_recent_cache(category, func_name, sig, data):
    full_dir_path = '%s/%s' % (category, func_name)
    if not os.path.isdir(full_dir_path):
        os.makedirs(full_dir_path)
 
    full_file_path = '%s/%s/%s' % (category, func_name, sig)
    f = file(full_file_path, 'w+')
    f.write(repr(data))
    f.close()

然后,我们只需要在每个func_top,func_sub,func_bottom都加上deco_args_recent_cache这个装饰器即可~~

搞定!这样做最大的好处在于,因为top,sub,bottom,每一层都会dump数据,所以比如某个sub层数据dump之后,是根本不会走到他所对应的bottom层的,减少了大量的开销!

OK,就这样~ 人生苦短,我用python!


感谢依云的提醒,python3居然原生支持了这种功能!链接如下:
http://docs.python.org/py3k/whatsnew/3.2.html#functools





原创文章,版权所有。转载请注明:转载自Vimer的程序世界 [ http://www.vimer.cn ]

本文链接地址: http://www.vimer.cn/?p=2124

19 个评论 在 “python装饰器的一个妙用”

  1. 依云 说:

    嗯,我就喜欢这样的装饰品,所以毫不犹豫地装了 Python 3.2——
    http://docs.python.org/py3k/whatsnew/3.2.html#functools

    [回复]

    依云 回复:

    s/品/器

    [回复]

    Dante 回复:

    python居然原生支持了了,太强了!
    啊,开始纠结是不是要往python3上迁移了。。。

    [回复]

    依云 回复:

    迁移吧,Vim 的 Python3 支持的 bug 我都搞定了,一直都没人测试呢。。。

    [回复]

    Dante 回复:

    唉,django一直不肯支持python3,也是我一直不敢迁移的原因。。

    [回复]

  2. == 说:

    请问,win 上的 gvim 可以设置 程序半透明 或者 背景图片 吗?谢谢。网上的都是 linux 下在终端中用 vim 并设置终端透明

    [回复]

  3. saalihmao 说:

    对python不是很熟,不过读读代码看上去就是装饰器模式嘛~ 这个所有带有一点函数式味道的语言都可以实现的,c++也是可以的,函数对象+装饰器模式就可以~ 不丑陋的 : )

    [回复]

  4. saalihmao 说:

    大概是这样
    class base
    {
    public:
    int operator()(char* some_para) = 0;
    };

    class deco:public base
    {
    base* pBase;
    public:
    explicit deco(const base* p){…;}

    int operator()(char* some_para)
    {
    //do something

    (*pBase)(some_para);
    ….
    //still do something
    }
    }

    [回复]

  5. saalihmao 说:

    呃 deco类的构造函数参数类型应该把const去掉~ : ) 失误失误

    [回复]

    Dante 回复:

    不过总感觉c++用某些设计模式会比较别扭,包括语法,效率,之类的,之前写bayonet项目时,c++用到状态模式,到处是指针,挺郁闷。。

    还有,博客支持pre标签哦,代码显示会友好一些,留言页面有说明~

    [回复]

    saalihmao 回复:

    c++的面向对象方面确实是这样~~ 一旦用到继承之类的就满天指针,不方便,而且没有GC内存分配的效率也很成问题~~

    不过也不一定非要用对象嘛~ 类型也是可以的,就像这个问题这样可能更好些

    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    //方便起见, 约定这些函数/函数类型参数都是char*, 返回类型都是int
    template
    class deco
    {
    public:
    int operator()(char* some_para)
    {
    //do sth
     
    func(some_para);
     
    //still do sth
    }
    };

    [回复]

    saalihmao 回复:

    呵呵pre标签不支持template …?

    [回复]

    Dante 回复:

    呃,你是说尖括号转义了吗?

    [回复]

    saalihmao 回复:

    嗯= = 尖括号被吃掉了~ 应该怎么写?~

    [回复]

    Dante 回复:

    1
    
    template <T>

    我试一下

    [回复]

    Dante 回复:

    好像把lang写上就可以了,我写的是cpp类型。

    [回复]

    saalihmao 回复:

    测试下…

    template
    class deco
    {
    public:
    int operator()(char* some_para)
    {
    //do sth
     
    func(some_para);
     
    //still do sth
    }
    };

    [回复]

    saalihmao 回复:

    还是不行… 换个浏览器

    template
    template 
    template

    [回复]

  6. [...] 这篇文章也是说这个问题(http://www.vimer.cn/?p=2124),这个例子更为精炼(http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize) [...]

我要评论

*

*