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
嗯,我就喜欢这样的装饰品,所以毫不犹豫地装了 Python 3.2——
http://docs.python.org/py3k/whatsnew/3.2.html#functools
[回复]
依云 回复:
四月 1st, 2011 at 4:18 下午
s/品/器
[回复]
Dante 回复:
四月 1st, 2011 at 6:16 下午
python居然原生支持了了,太强了!
啊,开始纠结是不是要往python3上迁移了。。。
[回复]
依云 回复:
四月 1st, 2011 at 8:37 下午
迁移吧,Vim 的 Python3 支持的 bug 我都搞定了,一直都没人测试呢。。。
[回复]
Dante 回复:
四月 2nd, 2011 at 2:42 上午
唉,django一直不肯支持python3,也是我一直不敢迁移的原因。。
[回复]
请问,win 上的 gvim 可以设置 程序半透明 或者 背景图片 吗?谢谢。网上的都是 linux 下在终端中用 vim 并设置终端透明
[回复]
对python不是很熟,不过读读代码看上去就是装饰器模式嘛~ 这个所有带有一点函数式味道的语言都可以实现的,c++也是可以的,函数对象+装饰器模式就可以~ 不丑陋的 : )
[回复]
大概是这样
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
}
}
[回复]
呃 deco类的构造函数参数类型应该把const去掉~ : ) 失误失误
[回复]
Dante 回复:
四月 26th, 2011 at 2:49 下午
不过总感觉c++用某些设计模式会比较别扭,包括语法,效率,之类的,之前写bayonet项目时,c++用到状态模式,到处是指针,挺郁闷。。
还有,博客支持pre标签哦,代码显示会友好一些,留言页面有说明~
[回复]
saalihmao 回复:
四月 26th, 2011 at 5:21 下午
c++的面向对象方面确实是这样~~ 一旦用到继承之类的就满天指针,不方便,而且没有GC内存分配的效率也很成问题~~
不过也不一定非要用对象嘛~ 类型也是可以的,就像这个问题这样可能更好些
[回复]
saalihmao 回复:
四月 26th, 2011 at 5:22 下午
呵呵pre标签不支持template …?
[回复]
Dante 回复:
四月 26th, 2011 at 11:47 下午
呃,你是说尖括号转义了吗?
[回复]
saalihmao 回复:
四月 27th, 2011 at 10:41 上午
嗯= = 尖括号被吃掉了~ 应该怎么写?~
[回复]
Dante 回复:
四月 27th, 2011 at 6:55 下午
我试一下
[回复]
Dante 回复:
四月 27th, 2011 at 6:55 下午
好像把lang写上就可以了,我写的是cpp类型。
[回复]
saalihmao 回复:
四月 27th, 2011 at 8:44 下午
测试下…
[回复]
saalihmao 回复:
四月 27th, 2011 at 8:46 下午
还是不行… 换个浏览器
[回复]
[...] 这篇文章也是说这个问题(http://www.vimer.cn/?p=2124),这个例子更为精炼(http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize) [...]