• 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

  • 相关阅读:
    寻找字符串中只出现一次的第一个字符
    【二叉树】已知二叉树前序序列和中序序列,重建唯一二叉树
    单向链表插入与删除
    【二叉树->链表】二叉树结构转双向线性链表结构(先序遍历)
    先序构建二叉树及先序遍历二叉树
    【Leetcode】寻找数串中连续最大整数和且最大长度的子串
    稀疏矩阵存储、转置、乘法运算
    面试编程题拾遗(06) --- 打印n对括号的全部有效组合
    面试编程题拾遗(05) --- 括号匹配检查
    做到这一点,你也可以成为优秀的程序员
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/10173603.html
Copyright © 2020-2023  润新知