邯城往事 邯城往事

来自邯郸社畜的呐喊

目录
Python之@函数装饰器
/  

Python之@函数装饰器

@ 函数装饰器

装饰器的作用 —— 不想修改函数的调用方式 但是还想在原来的函数前后添加功能
原则: 开放封闭原则
开放 : 对扩展是开放的
封闭 : 对修改是封闭的

示例

当程序使用“@ 函数”(比如函数 A )装饰另一个函数(比如函数 B )时, 实际上完成如下两
步。

  1. 将被修饰的函数(函数 B )作为参数传给@符号引用的函数(函数 A )。
  2. 将函数 B 替换(装饰)成第 ① 步的返回值。
    从上面介绍不难看出,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西。

看一下实例:

def funA(fn):
    print('A')
    fn()
    return 'asdf'
'''
下面的装饰效果相当于funA(funB)
funB 将会被替换(装饰)成该语句的返回值
由于funA 函数返回fkit ,因此funB 就是asdf
'''
@funA
def funB():
    print('B')
print(funB)

上面程序使用@funA 修饰 funB ,这意味着程序要完成两步操作。
① 将 funB 作为 funA()的参数,也就是上面的粗体字代码相当于执行 funA(funB)。
② 将 funB 替换成 ① 第步执行的结果, funA()执行完成后返回 asdf,因此 funB 就不再是函数,而是被替换成一个字符串。
查看运行结果:

A
B
asdf

实例 2:

def wapper(func):
    def inner(*args,**kargs):
        ret = func(*args,**kargs)
        return ret
    return inner
@wapper   #qqxing = wrapper(qqxing)
def qqxing():
    print(123,123)
qqxing()  #实际是行inner

这个函数装饰器导致被修饰的函数变成了字符串,那么函数装饰器有什么用?
别忘记了,被修饰的函数总是被替换成@符号所引用的函数的返回值,因此被修饰的函数会变成什么,完全由于@符号所引用的函数的返回值决定一一如果@符号所引用的函数的返回值是函数,那么被修饰的函数在替换之后还是函数。

下面师范更为复杂的函数装饰器:

def foo(fn):
    def bar(*args):
        print("1",args)
        n = args[0]
        print(2,n*(n-1))
        print(fn.__name__)
        fn(n*(n-1))
        print("*"*15)
        return fn(n*(n-1))
    return bar
'''
下面的装饰效果相当于foo(my_test)
my_test 将会被替换(装饰〉成该语句的返回值
由于foo()函数返回bar 函数,因此funB 就是bar
'''
@foo
def my_test(a):
    print("==3==",a)
print(my_test)
# my_test(10)
my_test(5,6)

上面程序定义了一个装饰器函数 foo ,该函数执行完成后并不是返回普通值,而是返回 bar 函数(这是关键〉,这意味着被该 @foo 修饰的函数最终都会被替换成 bar 函数。

上面程序使用@foo 修饰 my_test()函数,因此程序同样会执行 foo(my_test),并将 my_test 替换成 foo()函数的返回值: bar 函数。所以,上面程序代码 print(my_test) 在打印 my_test 函数时,实际上输出的是 bar 函数,这说明 my_test 已经被替换成 bar 函数。接下来程序两次调用 my_test()函数,实际上就是调用 bar()函数。

运行结果:

<function foo.<locals>.bar at 0x000001E2ECA52708>
1 (5, 6)
2 20
my_test
==3== 20
***************
==3== 20

通过@符号来修饰函数是 Python 的一个非常实用的功能,它既可以在被修饰函数的前面添加一些额外的处理逻辑(比如权限检查),也可以在被修饰函数的后面添加-些额外的处理逻辑( 比如记录日志),还可以在目标方法抛出异常时进行一些修复操作……这种改变不需要修改被修饰函数的代码,只要增加一个修饰即可。

总结装饰器语法:

@deco
def target():
    print('running target()')

上述代码可写成:

def target():
    print('running target()')
target = deco(target)

两种写法的最终结果一样:上述两个代码片段执行完毕后得到的 target 不一定是原来那个 target 函数,而是 decorate(target) 返回的函数。

装饰器通常把函数替换成另一个函数

def deco(func):
    def inner():
        print('running inner()')
    return inner 
@deco
def target():
    print('running target()')
print(target())

❶ deco 返回 inner 函数对象。
❷ 使用 deco 装饰 target。
❸ 调用被装饰的 target 其实会运行 inner。
❹ 审查对象,发现 target 现在是 inner 的引用。

模拟场景:

def auth(fn):
    def auth_fn(*args):
    #用一条语句模拟执行权限检查
        print("---权限检查---")
    #回调修饰的目标函数
        fn(*args)
    return auth_fn

@auth
def test(a,b):
    print("执行test函数,参数a: %s,参数b: %s"%(a,b))
test(100,13)

上面程序使用@auth 修饰了 test()函数,这会使得 test()函数被替换成 auth()函数所返回的 auth_fn
函数,而 auth_fn 函数的执行流程是:
① 先执行权限检查;
② 回调被修饰的目标函数——简单来说, auth_fn 函数就为被修饰函数添加了一个权限检查的功能。

多个装饰器装饰一个函数

def wrapper1(func):
    def inner1():
        print('wrapper1 ,before func')
        ret = func()
        print('wrapper1 ,after func')
        return ret
    return inner1

def wrapper2(func):
    def inner2():
        print('wrapper2 ,before func')
        ret = func()
        print('wrapper2 ,after func')
        return ret
    return inner2

def wrapper3(func):
    def inner3():
        print('wrapper3 ,before func')
        ret = func()
        print('wrapper3 ,after func')
        return ret
    return inner3

@wrapper3
@wrapper2
@wrapper1
def f():
    print('in f')
    return '哈哈哈'
print(f())

返回值 L:

wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func
哈哈哈

@ 装饰函数顺序决定执行函数顺序

可大概看下是行顺序:
image.png

  • 装饰器会先找离自己最近的函数,因为 @wrapper2 没找到,则在 @wrapper1 那里进行执行。

大致代码执行过程:
image.png

image.png


标题:Python之@函数装饰器
作者:cuijianzhe
地址:https://solo.cjzshilong.cn/articles/2019/11/21/1574322166949.html