Damon

宏愿纵未了 奋斗总不太晚

0%

Python装饰器

如果对闭包不了解的同学请移步到这里先, 因为装饰器要通过闭包来实现

前言

刚开始学Python的时候,装饰器(decorator)一直是个让人难以理解的东西,所以想通过这篇文章能够带你一步一步来理解Python装饰器的原理

什么是装饰器?

经典的设计模式有23种,设计模式其实也就是巨人们常年写代码经验的思想总结,虽然说这是一种思想,但是由于语法的限制没有办法轻易实现(比如说用C语言来实现组合模式).在面向对象的设计模式中, decorator被称为装饰模式,OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。

下面开始介绍装饰器的原理

业务逻辑:

假如你是简书的开发者,刚开发了一个发布文章的功能:

1
2
3
def send():
# 核心代码
print("发布成功")

然后你美滋滋得上传代码了,第二天产品经理拿着刀来找你:你妹啊,随便就能发布文章了?不用登录的?

这时候只需要写一个验证登录的逻辑就好了

1
2
def check_login():
print("做登录验证")

然后你就开始纠结了,这段代码怎么放呢?这样?

1
2
3
4
def send():
check_login()
# 核心代码
print("发布成功")

这样确实完成了功能,但是不太优雅,耦合度太高,我们是要写出高质量代码的攻城狮!!!咳咳

所以,我们希望在不改变 send() 代码的前提下,还能做验证登录的操作,装饰器就出现了

1
2
3
4
5
6
7
8
9
10
11
12
13
def check_login(func):
def inner():
print("做登录验证")
print("开始发布文章")
func()
return inner

def send():
# 核心代码
print("发布成功")

send = check_login(send) #0
send()

可以看到,注释 0 处的代码返回值已经是inner函数对象了.在执行send()的时候实际上就是在执行inner(),这样就能做到不改变原有函数代码的前提下,提升函数的功能!

Python中,有一个语法糖可以不用写注释0处的代码

1
2
3
4
@check_login #语法糖
def send():
# 核心代码
print("发布成功")

调用send()结果一样,所以说

1
2
3
4
@check_login #语法糖
def send():
# 核心代码
print("发布成功")

1
send = check_login(send)

两种写法是等价的!

在上面的代码可以看出来,装饰器个函数,它的参数是被装饰的函数,返回值也是一个函数.

业务逻辑的变动

有一天你发现,发布文章的代码好像得接收用户编写文章的内容和标题才能发布出现显示给用户看哦(不然就是一堆假数据),然后你赶紧改了一下发布文章的逻辑代码:

1
2
3
def send(title,content):
# 核心代码
print("文章标题: %s, 内容:%s" % (title,content))

运行就报错了,因为加上装饰器之后调用send("title","content")是相当于调用了inner(),但是装饰器里的inner并没有接收参数,所以,应该修改装饰器中的代码:

1
2
3
4
5
6
def check_login(func):
def inner(*args, **kw):
print("做登录验证")
print("开始发布文章")
func(*args,**kw) #0
return inner

因为注释0代码处才是真正调用了发布文章的原函数,所以得把参数传回去,这样就解决问题了,可以传任意数量和任意类型的参数!!

新的问题

当你想要拿到发布文章的结果,发布成功或者失败:所以你得改发布文章的逻辑代码:

1
2
3
4
5
6
7
8
9
@check_login
def send(title,content):
# 核心代码
print("文章标题: %s, 内容:%s" % (title,content))
return True

# 然后接收
result = send("Python","Python的装饰器")
print(result)

输出结果:
做登录验证
开始发布文章
文章标题: Python, 内容:Python的装饰器
None

emmmmm? result怎么回事None呢?如果你能看懂前面内容的话这个小bug对你来说就根本不在话下,修改装饰器的代码:

1
2
3
4
5
6
def check_login(func):
def inner(*args, **kw):
print("做登录验证")
print("开始发布文章")
func(*args,**kw)
return inner

好了,一步一步到这.一个标准的装饰器终于完成了!!

装饰器的进阶

上面我们验证登录的装饰器代码中,验证完之后都会print("开始发布文章")来做调试.但是,我们不止发布文章的时候要做验证登录的操作,发图片,评论啊,私信啊,回复等等等…都是需要登录验证的装饰器(装饰器的优势体现出来了).然而,你在做其他操作的时候控制台来了一句开始发布文章 (黑人问号脸????),这时候你就想,要是调试的语句可以控制就好了

装饰器工厂

装饰器是可以接收自定义参数的,然后返回另一个装饰器.这样看来的话外面的装饰器其实就是个装饰器工厂,根据传来不同的参数生成不同的装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_decorator(console):
def check_log(func):
def inner(*args,**kw):
print(console)
return func(*args,**kw)
return inner
return check_log


@get_decorator("开始发布文章")
def send(title,content):
# 核心代码
print("文章标题: %s, 内容:%s" % (title,content))
return True

result = send("Python","Python的装饰器")
print(result)

get_decorator就是个装饰器工厂,根据参数返回一个装饰器

上面的等价拆分的话,等价于

1
2
3
check_login = get_decorator("开始发布文章")
inner = check_login(send)
send(title,content) = inner(title,content)