首页 > 代码库 > 插件和过滤器装饰器开发中的感悟-python-django

插件和过滤器装饰器开发中的感悟-python-django

    写这篇随笔是因为今天自己在写插件和过滤方法的过程中碰壁了,折腾了好久终于稍微发现些问题,在此记下,以作备忘。

在看了xadmin的插件机制后,笔者也想使用该思想来扩展kadmin中视图的方法。

例如,在一个登陆视图中,一般的逻辑如下:

class LoginView(BaseAdminView):    ‘‘‘登陆视图‘‘‘    auth_form=None#用于认证的表单类    login_template=None    title=""    def update_login_params(self,defaults):        ‘‘‘ 用于在执行login视图函数前修改参数的钩子‘‘‘        return defaults    @never_cache    def get(self,request,*arg,**kwargs):        from django.contrib.auth.views import login        context=self.get_context(request,*arg,**kwargs)#获取父类的context        context.update({            title:self.title,            app_path:request.get_full_path,            REDIRECT_FIELD_NAME:request.get_full_path(),        })        defaults={            extra_context:context,            authentication_form:self.auth_form or AdminAuthenticationForm,            template_name:self.login_template or kadmin/views/login.html,        }
       defaults=self.update_login_params(defaults) 

return login(request,**defaults) @never_cache def post(self,request,*arg,**kwargs): return self.get(request,*arg,**kwargs)

我们希望能通过update_login_params方法,在执行login函数前更新context,就应该使用插件来接管该方法的执行以按需要进行一些修改。

这里有两种思路:

1、使用过滤器,过滤update_login_params的结果并返回

2、使用插件函数,控制update_login_params的执行(一般是活得update_login_params的执行结果 传入插件函数 由插件函数进行一些修改后再返回)

    也可以用插件函数接管update_login_params,并自行决定是否执行update_login_params,或是在update_login_params执行前或后进行一些行为

从上述两点看,插件更为灵活和自由,控制范围更大,二过滤器只能就update_login_params的返回值进行操作并返回。

1、过滤器

定义过滤器装饰器@filter_hook

def filter_hook(func):    #过滤器装饰器    #用于对被hook的方法返回值进行再处理    func_name=func.__name__    @wraps(func)    def inner_func(self,*arg,**kwargs):        if getattr(self,filters,None):            filter_infos=[(getattr(fi,priority,10),fi) for fi in self.filters if fi.__name__==func_name]            filters=[fi for p,fi in sorted(filter_infos,key=lambda x:x[0],reverse=False)]            result=func(self,*arg,**kwargs)            for fi in filters:                result=fi(self,result,*arg,**kwargs)            return result        else:            return func(self,*arg,**kwargs)    return inner_func

源码说明:在视图对象self上遍历filters 得到过滤函数,过滤结果并返回结果

例如我们可以定义一个过滤器给say_hello的返回值加上括号

@set_attr(attr_name=__name__,value=http://www.mamicode.com/say_hello)def add_tag(self,result,o,*arg,**kwargs):    return "(%s)"%result
class Person:    name=akun    sex="akun-male"    plugins=[MalePlugin(sex_num) for sex_num in range(4)]    filters=[add_tag]    @filter_hook    @plugin_hook    def say_hello(self,go,*arg,**kwargs):        print("-----------done--------")        return "Hello i am %s"%self.nameif __name__=="__main__":    p=Person()    print(p.say_hello(go))

说明:@set_attr是一个装饰器,负责设置函数的属性值,因为filter_hook在查找过滤函数时是找被hook的同名函数,所以需要把add_tag的__name__设为say_hello

2.插件

定义插件装饰器:

def plugin_chain(funcs,token,func,*arg,**kwargs):    if token==-1:        return func()    @wraps(func)    def _inner_func():        wrap_func=funcs[token]        arg_specs=getargspec(wrap_func)[0]        if len(arg_specs)==1:            func()            return wrap_func(*arg,**kwargs)        elif len(arg_specs)>=2 and arg_specs[1]=="__":            back=func        else:            back=func()        return wrap_func(back,*arg,**kwargs)    return plugin_chain(funcs,token-1,_inner_func,*arg,**kwargs)            def plugin_hook(func):    func_name=func.__name__    @wraps(func)    def method(self,*arg,**kwargs):        @wraps(func)        def _inner_func():            return func(self,*arg,**kwargs)        if getattr(self,plugins,None):            plugin_funcs=[(getattr(p,func_name),getattr(getattr(p,func_name),priority,10))                      for p in self.plugins if getattr(p,func_name,None) ]            print(plugin_funcs)            #对插件方法按照priority升序排列a            plugin_funcs=[p for p,priority in sorted(plugin_funcs,key=lambda x:x[1],reverse=False)]            return plugin_chain(plugin_funcs,len(plugin_funcs)-1,_inner_func,*arg,**kwargs)        else:            return func(self,*arg,**kwargs)    return method

源码说明:类似于@filter_hook只是,注意

        wrap_func=funcs[token]        arg_specs=getargspec(wrap_func)[0]        if len(arg_specs)==1:            func()            return wrap_func(*arg,**kwargs)        elif len(arg_specs)>=2 and arg_specs[1]=="__":            back=func        else:            back=func()        return wrap_func(back,*arg,**kwargs)

如果若用于接收被hook方法的返回值的参数名为"__"则,__会被设置为被hook的方法,也就是可以通过插件来接管被hook的方法。

我们定义一个插件来在执行say_hello前打印出被hook的方法

class BeforeHook:    @set_attr(attr_name=priority,value=http://www.mamicode.com/6)    def say_hello(self,__,o,*arg,**kwargs):        print("the back is %s"%__)        return __(*arg,**kwargs)

这里的__或被设置为被hook的方法

 

插件和过滤器装饰器开发中的感悟-python-django