美文网首页程序员
Python学习笔记·装饰器

Python学习笔记·装饰器

作者: yhmyhmyhm | 来源:发表于2018-05-14 00:21 被阅读0次

    给一个函数添加新功能


    1. 装饰器介绍

    def hello(func):
        def newfunc():
            print("hello", end = " ")
            func()
            return
        return newfunc
        
    @hello
    def xiaoli():
        print("xiaoli")
        return
    xiaoli()
    #输出: hello xiaoli
    

    如上面代码所示,装饰器是一个函数 可调用对象,接收一个函数作为参数,并将其替换成一个函数。

    2. “装饰”过程

    分析1的代码,函数xiaoli在定义时只会输出"xiaoli",而调用时却额外输出了"hello ",显然我们的调用过程事实上调用的是hello内部定义的newfunc,并且func即为我们实际定义的xiaoli。
    先无视第8行的@hello尝试手动实现这个效果。

    def hello(func):
        def newfunc():
            print("hello", end = " ")
            func()
            return
        return newfunc
        
    def xiaoli():
        print("xiaoli")
        return
    xiaoli()
    #输出:xiaoli
    

    去掉了@hello,正常的调用了xiaoli,这里和hello没有什么关系。

    hello(xiaoli)
    #无输出
    

    外层函数hello被调用,查看返回值发现返回一个函数对象,根据hello的定义这里返回了内层函数newfunc。
    尝试调用它:

    hello(xiaoli)()
    #输出:hello xiaoli
    

    这样输出就和1的示例一致了,为了让调用过程也保持一致,加入一行代码:

    xiaoli = hello(xiaoli)
    xiaoli()
    #输出:hello xiaoli
    

    这样就实现了和1中示例代码同样的效果。对于那个@hello我们可以理解为它就是在函数定义结束后添加了一行xiaoli = hello(xiaoli)。至少目前看来这二者没有什么区别。

    3. 装饰带参数和返回值的函数

    了解了装饰器的用法后我们可以试图让所有自己定义的函数都变得懂礼貌,只需要在定义时使用hello装饰器即可。如:

    @hello
    def xiaoyu():
        print("xiaoyu")
        return    
    @hello
    def xiaoliu():
        print("xiaoliu")
        return    
    @hello
    def xiaozhou():
        print("xiaozhou")
        return
    

    充分发扬懒惰精神,我们可以写成这样

    @hello
    def xiaonashei(nashei):
        print(xiao + str(nashei))
        return
    

    看上去就有毛病,调用果然出错:

    In [133]: xiaonashei("nashei")
    ---------------------------------------------------------------------------
    TypeError   Traceback (most recent call last)
    <ipython-input-133-e12e640621a4> in <module>()
    ----> 1 xiaonashei("nashei")
    TypeError: newfunc() takes 0 positional arguments but 1 was given
    

    显然是写hello考虑不周,newfunc应当考虑不同参数情况。

    def hello(func):
        def newfunc(*args):
            print("hello ", end = "")
            func(*args)
            return 
        return newfunc
    

    顺便发现第5行内部函数居然没有返回值,一并解决。

    def hello(func):
        def newfunc(*args):
            print("hello ", end = "")
            result = func(*args)
            return result
        return newfunc
    

    然后就可以愉快地装饰xiaonashei了。

    @hello
    def xiaonashei(nashei):
        print(xiao + str(nashei))
        return
    
    xiaonashei("penyou")
    #输出:hello xiaopenyou
    

    带返回值的函数也能正确获取返回值。

    @hello
    def bbs(nasha):
        return abs(nasha)
    
    b = bbs(-1)
    #输出: hello
    print(b)
    #输出: 1
    

    4. 参数化装饰器

    既然装饰器也是函数,理应可以接受其他参数以实现不同功能,尝试根据性别打招呼。

    def hello(func,sex):
        def newfuncforboy(*args):
            print("hello boy ", end = "")
            result = func(*args)
            return result
        def newfuncforgirl(*args):
            print("hello girl ", end = "")
            result = func(*args)
            return result
        return newfuncforboy if sex == "m" else newfuncforgirl
    

    运行试试

    In [130]: @hello("m")
         ...: def xiaoxiao():
         ...:     print("xiaoxiao")
         ...:
    ---------------------------------------------------------------------------
    TypeError   Traceback (most recent call last)
    <ipython-input-130-fd7cd46b49f4> in <module>()
    ----> 1 @hello("m")
          2 def xiaoxiao():
          3     print("xiaoxiao")
          4
    TypeError: hello() missing 1 required positional argument: 'sex'
    

    为什么会这样?回忆2里对@hello的替换,

        @hello("m")
        def xiaoxiao():
        ...
    

    看成

        def xiaoxiao():
        ...
        xiaoxiao = hello("m")(xiaoxiao)
    

    也就是说,xiaoxiao并不会被当做参数传入hello,而是被传入hello("m"),事实上,在那之前代码就会因为hello("m")这个错误的函数调用而中止。
    回忆1讲过的内容,装饰器是一个可调用对象,接收一个函数作为参数,并将其替换成一个函数。@符号永远认为其紧跟着的是一个装饰器,在无参的情况下hello是一个装饰器,在带参数的情况下hello("m")也应当是一个装饰器。也就是说hello("m")的返回值才应当是作用于xiaoxiao的装饰器。
    再加入一层函数嵌套:

    def hello(sex):
        def inner(func):
            def newfuncforboy(*args):
                print("hello boy ", end = "")
                result = func(*args)
                return result
            def newfuncforgirl(*args):
                print("hello girl ", end = "")
                result = func(*args)
                return result
            return newfuncforboy if sex == "m" else newfuncforgirl
        return inner
        
    @hello("m")
    def xiaoming():
        print("xiaoming")
        return
        
    @hello("w")
    def xiaohong():
        print("xiaohong")
        return
    
    xiaoming()
    #输出: hello boy xiaoming
    xiaohong()
    #输出: hello girl xiaohong
    

    终于正常实现功能了。
    事实上如果使用一开始的写法,手动装饰。

    def hello(func,sex):
        def newfuncforboy(*args):
            print("hello boy ", end = "")
            result = func(*args)
            return result
        def newfuncforgirl(*args):
            print("hello girl ", end = "")
            result = func(*args)
            return result
        return newfuncforboy if sex == "m" else newfuncforgirl
       
    def xiaoming():
        print("xiaoming")
        return
    xiaoming = hello(xiaoming,"m")
    
    def xiaohong():
        print("xiaohong")
        return
    xiaohong = hello(xiaohong,"w")
    
    xiaoming()
    #输出: hello boy xiaoming
    xiaohong()
    #输出: hello girl xiaohong
    

    好像同样能够实现这一功能,但是这样会导致叠放装饰器的时候很麻烦,所以还是建议使用前面的写法。

    5. 叠放装饰器

    可以叠放多个装饰器,装饰器自下而上作用。

    def hell0(func):
        print("!hell0")
        def newfunc():
            print("hell0", end = " ")
            func()
            return
        return newfunc
        
    def hell1(func):
        print("!hell1")
        def newfunc():
            print("hell1", end = " ")
            func()
            return
        return newfunc
    
    def hell2(func):
        print("!hell2")
        def newfunc():
            print("hell2", end = " ")
            func()
            return
        return newfunc
    
    @hell2
    @hell1
    @hell0
    def xiaoli():
        print("xiaoli")
        return
        
    xiaoli()
    
    

    运行上面的代码,观察输出时机和顺序即可。

    相关文章

      网友评论

        本文标题:Python学习笔记·装饰器

        本文链接:https://www.haomeiwen.com/subject/hcgrdftx.html