A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 不二晨 金牌黑马   /  2019-3-8 10:16  /  1067 人查看  /  1 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

装饰器

装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。

装饰器引入

下面就简单举个例子:

一天,A程序员接到一个登入的需求,写了一个方法。

def login():
    print('登入')


login()     

# 输出  
登入
1
2
3
4
5
6
7
8
9
突然,产品经理想加入一个登入事件。于是A程序员对方法进行了修改。

def login():
    print('事件记录')
    print('登入')


login()   

# 输出  
事件记录   
登入
1
2
3
4
5
6
7
8
9
10
11
然后,A的老大看到了说,你这样违反了开闭原则,好好思考一下如何改进。后面A就改进,通过闭包的方式,定义了一个event函数。

# 定义一个event函数,记录事件
def event(func):
    def inner():
        print('事件记录')
        func()
    return inner

def login():
    print('登入')


login = event(login)    # 这个代码意思是,把定义的login函数作为变量传递给event作为参数。event函数的返回值在赋值login变量
login()     # 调用login变量的方法

# 输出
事件记录
登入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里有个比较难理解的地方就是,def定义了一个login函数,实际上它是一个login变量指向def定义的login函数的地址值。

eg:

def login():
    print('登入')

print(login)    # <function login at 0x000001D8D0543E18>
a = login       # 将login函数的内存地址指向给a变量
a()             # 调用a函数,实际上调用的是a指向的地址值的方法

# 输出
登入
1
2
3
4
5
6
7
8
9
10
这个例子就说明login是一个变量,这个变量指向login函数地址值。

理解了这个例子,那么login = event(login)就比较好解释了。

login = event(login)


等号左边的login是event()的返回值,是经过inner变量赋值,inner变量指向的是inner函数的地址值。

等号右边的login变量是指向login函数的地址值。作为参数传递给event函数。实际上是在inner函数中调用。


login()     

调用login函数,因为login变量经过inner变量赋值,所以这时的login变量指向的是inner函数的地址值。

也就是说,login()调用,实际上调用的是inner函数,inner函数执行的顺序是:

    print('事件记录')
    func()      

func参数是由event函数传入,也就是login = event(login)式子等号右边的login,这个login变量指向的是def定义login()。

所以输出的结果是:

事件记录
登入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
装饰器语法糖

在Python中,可以使用@语法糖来精简装饰器的代码:

使用了@语法糖后,我们就不需要额外代码来给login重新赋值了。

eg:

def event(func):
    def inner():
        print('事件记录')
        func()
    return inner

@event          # 实际上做了 login = event(login)操作
def login():
    print('登入')

login()

# 输出
事件记录
登入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里login()的@event,其实本质就是login = event(login),@做了一步赋值操作。当认清了这一点后,后面看带参数的装饰器就简单了。

被装饰的函数带参数

我们只带login()作为登入函数,实际上是有用户名和密码的,这个时候login()需要2个参数,那么怎么处理呢。

eg:

def event(func):
    def inner(username, password):
        print('事件记录')
        func(username, password)
    return inner

@event
def login(username, password):
    print('username is %s password is %s,登入成功' %(username,password))


login('amy','123456')

# 输出
事件记录
username is amy password is 123456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果这时候需要加上一个注册的方法,注册需要3个参数,怎么使用呢?这个时候需要使用可变参数*args, **kwargs。

eg:

def event(func):
    def inner(*args, **kwargs):
        print('事件记录')
        func(*args, **kwargs)

    return inner


@event
def login(username, password):
    print('username is %s , password is %s,登入成功' % (username, password))


@event
def register(username, password, email):
    print('username is %s , password is %s , email is %s,注册成功' % (username, password, email))


login('amy','123456')

# 输出
事件记录
username is amy , password is 123456 , 登入成功


register('anne', '654321' , 'abc@126.com')

# 输出
事件记录
username is amy , password is 123456 , email is abc@126.com , 注册成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
被修饰的函数带返回值

现在登入和注册成功后,需要有返回值,那么怎么处理,请看下面例子:

def event(func):

    def inner(*args, **kwargs):
        print('事件记录')
        ret = func(*args, **kwargs)
        return ret

    return inner


@event
def login(username, password):
    print('username is %s , password is %s' % (username, password))
    return '登入成功'


@event
def register(username, password, email):
    print('username is %s , password is %s , email is %s' % (username, password, email))
    return '注册成功'


ret = login('amy','123456')
print(ret)      # 输出 登入成功


ret = register('anne', '654321' , 'abc@126.com')
print(ret)      # 输出 注册成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
如果函数没有返回值,怎么处理,函数没有返回值,其实返回的是None。上面的装饰器是比较通用的装饰器。

带参数的装饰器

eg:

def event(debug=True):
    if (debug):
        def deco(func):
            def inner(*args, **kwargs):
                print('事件记录')
                return func(*args, **kwargs)

            return inner
    else:
        def deco(func):
            return func
    return deco


@event()    # 使用默认参数
def login(username, password):
    print('username is %s , password is %s' % (username, password))
    return '登入成功'


@event(debug=False)     # debug参数设置成False,也可以直接写False
def register(username, password, email):
    print('username is %s , password is %s , email is %s' % (username, password, email))
    return '注册成功'


login('amy', '123456')
# 输出
事件记录
username is amy , password is 123456


register('anne', '654321', 'abc@126.com')
# 输出
username is anne , password is 654321 , email is abc@126.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@event加上参数,目的是可以灵活的控制函数的输出,如是否打开调试信息等。

如果@event加上参数,在login()函数定义之前就开始执行。我们可以看一下装饰器的执行顺序。

eg:

def event(debug=True):
    print('event函数调用')
    if (debug):
        def deco(func):
            print('deco函数调用')
            def inner(*args, **kwargs):
                print('inner函数调用')
                # print('事件记录')
                return func(*args, **kwargs)

            return inner
    else:
        def deco(func):
            print('deco函数调用')
            return func
    return deco


@event()
def login(username, password):
    print('login函数调用')
    # print('username is %s , password is %s' % (username, password))
    return '登入成功'



# 在未调用函数时,运行,打印输出:
event函数调用
deco函数调用



login()

# 调用函数后,运行,输出:

event函数调用
deco函数调用
inner函数调用
login函数调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
也就是说,定义的event方法和deco方法在未调用时Python解释器就已经开始执行了。

执行步骤:

1.定义event函数
2.调用event函数,执行打印event函数调用语句,返回deco函数的引用
3.使用@event,执行打印deco函数调用语句,返回inner函数的引用
4.使用func进行装饰
5.调用login函数
6.执行inner函数调用语句
7.执行login函数调用语句
---------------------
【转载】
作者:张行之
来源:CSDN
原文:https://blog.csdn.net/qq_33689414/article/details/78295628
版权声明:本文为博主原创文章,转载请附上博文链接!

1 个回复

倒序浏览
奈斯,感谢分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马