Python学习笔记·装饰器

给一个函数添加新功能


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)
 in ()
----> 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)
 in ()
----> 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学习笔记·装饰器)