python cookbook第三版学习笔记二十:可自定义属性的装饰器

在开始本节之前,首先介绍下偏函数partial。首先借助help来看下partial的定义

首先来说下第一行解释的意思:

partial 一共有三个部分:

(1)第一部分也就是第一个参数,是一个函数,这个函数可以是你定义的,也可以是Python内置函数

(2)第二部分是一个可变参数,*args,比如内置函数max的参数就是一个可变参数,max(1,2,3,4,5)=5

(3)第三部分是一个关键字参数,比如内置函数int的第二个参数就是命名关键字参数,默认base=10,表示int转换时默认是10进制的:

partial函数的作用就是:将所作用的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字,没有的话,按原有参数顺序进行补充。

 

偏函数的使用

 

A、偏函数的第二个部分(可变参数),按原有函数的参数顺序进行补充,参数将作用在原函数上,最后偏函数返回一个新函数(类似于,装饰器decorator,对于函数进行二次包装,产生特殊效果;但又不同于装饰器,偏函数产生了一个新函数,而装饰器,可改变被装饰函数的函数入口地址也可以不影响原函数)

 

 

 

案例:我们定义一个sum函数,参数为*args可变,计算这些可变参数的和。

 

扩展:我们想要对sum函数求和后的结果,再加上10加上20甚至加更多,得到一个新的结果

 

实现:我们分别用decorator和partial来实现,对比一下二者的区别

(一)装饰器 decorator 实现

from functools import wraps

 

def sum_add(*args1): #我们要给我们的装饰器decorator,带上参数

    def decorator(func):

        @wraps(func) #加上这句,原函数func被decorator作用后,函数性质不变

        def my_sum(*args2): #注意,参数要和原函数保持一致,真正实行扩展功能的是外层的装饰器

            my_s = 0

            for n in args1:

                my_s = my_s +n #这个是我们新加的求和结果

            return func(*args2) + my_s #这个,我们在原求和函数的结果上再加上s,并返回这个值

        return my_sum #返回my_sum函数,该函数扩展原函数的功能

    return decorator  #返回我们的装饰器

 

@sum_add(10,20) #启用装饰器 对sum函数进行功能扩展

def sum(*args):

    s = 0

    for n in args:

        s = s+n

    return s

print(sum(1,2,3,4,5))

print(sum.__name__)

 

sum最后返回的值应该是10+20+15 = 45,这样一来,我们的decorator就实现了我们想要的扩展功能,最后,发现,原函数sum的name属性,仍然是sum,说明,这种装饰扩展功能,不影响我们的原函数:

(二)偏函数 partial function 实现

A:普通函数可变参数顺序执行

  1. def sum(*args):
  2.     s = 0
  3.     for n in args:
  4.         s = s + n
  5.     return s
  6. print(sum(10,20)+sum(1,2,3,4,5))

我们如果想实现+10+20的效果,必须写两遍sum,这样写,显然是最易懂的,但是,却显得很邋遢

B:普通函数可变参数加关键字参数组合

def sum(*args,**others):

    s = 0

    for n in args:

        s = s + n

    s1 = 0

    for k in others:

        s1 = s1 + others[k] #我们还要算一下,关键字参数里蕴藏的求和结果,k是dict中的关键字key

    return s+s1 #最终,我们实现扩展功能,顺序参数和关键字参数结果相加

   

D= {'value1':10,'value2':20}

print(sum(1,2,3,4,5,**D))

代码看起来,是显得专业了,但是感觉冗余,没必要

 

C:偏函数可变参数顺序填充一步到位

from  functools import partial

 

def sum(*args):

    s = 0

    for n in args:

        s = s + n

    return s

 

sum_add_10    = partial(sum,10)    #10 作用在sum第一个参数的位置

sum_add_10_20 = partial(sum,10,20)  #10 20 分别作用在sum第一个和第二个参数的位置

print('A____________我们看下原函数sum的函数地址入口:')

print(sum)

print('B______我们看下partial函数返回函数的地址入口:')

print(partial(sum,10))

print(sum_add_10(1,2,3,4,5))    # --> 10 + 1 + 2 + 3 + 4 + 5 = 25

print(sum_add_10_20(1,2,3,4,5)) # --> 10 + 20 + 1 + 2 + 3 + 4 + 5 = 45

可以看出,我们针对sum函数的求和结果,再加上10,或者加10加20,甚至加更多,都是可以通过偏函数来实现的,注意偏函数的第二部分,参数是可变的,是按顺序走的,因此,偏函数产生的新函数,sum_add_10 实际上等同于sum(10,*args):

 

 

下面来看可自定义属性的装饰器

from functools import wraps,partial

import logging

def attach_wrapper(obj,func=None):

    if func is None:

        return partial(attach_wrapper,obj)

    setattr(obj,func.__name__,func)

    return func

 

def logged(level,name=None,message=None):

    def decorate(func):

        logname=name if name else func.__module__

        log=logging.getLogger(logname)

        logmsg=message if message else func.__name__

        @wraps(func)

        def wrapper(*args,**kwargs):

            log.log(level,logmsg)

            return func(*args,**kwargs)

        @attach_wrapper(wrapper)

        def set_level(newlevel):

            nonlocal level

            level=newlevel

        @attach_wrapper(wrapper)

        def set_message(newmsg):

            nonlocal logmsg

            logmsg=newmsg

        return wrapper

    return decorate

 

@logged(logging.DEBUG)

def add(x,y):

    return x+y

 

@logged(logging.CRITICAL,'example')

def spam():

print("Spam!")

 

首先用attach_wrapper来装饰set_level以及set_message。在调用的时候首先是attach_wrapper(wrapper)(set_level)。初次调用的时候由于func为空,因此调用partial(attach_wrapper,obj),将obj也就是wrapper函数作为第一个参数,并返回一个新的attach_wrapper函数,第一个参数是wrapper函数实例,再次调用的时候就变成attach_wrapper(wrapper,set_level). 在这次调用中set_level被设置为wrapper的属性。setattr(obj,func.__name__,func)也就等于wrapper.set_level=set_level

通过这种方式将set_message和set_level设置成了wrapper的方法。

在set_message和set_level中引用了nonlocal来修改内部变量。这样就可以控制logging的等级以及输出信息。

if __name__=="__main__":

    logging.basicConfig(level=logging.DEBUG)

    add(2,3)

    add.set_message('Add called')

    add(2,3)

    add.set_level(logging.WARNING)

    add(2,3)

运行结果如下:

DEBUG:__main__:add

DEBUG:__main__:Add called

WARNING:__main__:Add called

 

posted @ 2018-12-25 14:07  red_leaf_412  阅读(502)  评论(0编辑  收藏  举报