装饰器可谓是Python中的一大难点,但看了这篇文章,包你学会(前提是基础先要打牢,而且你要认真看)
你要相信自己一定能学会装饰器
废话少说,闲话少讲,我们开始吧!
装饰器很难,所以我们要从娃娃抓起。
先从一个函数开始:
def hello():
print("Hello world")
hello()
这谁都会,定义一个hello函数
现在,我们给他加上开始和结束特效
你肯定想到的是:
def hello():
print("===start===")
print("Hello world")
print("===finish===")
hello()
或者这样:
def hello():
print("Hello world")
print("===start===")
hello()
print("===finish===")
问题来了:
- 第一种方法会改变函数
- 第二种方法如果要多次调用,每次都要加开始结束特效,将会非常耗机器,也非常麻烦
那能不能在不改变这个函数,且不在调用时执行的情况下,给这个函数增加功能?
这就是装饰起的作用,他能修饰这个函数,在不改变原函数的前提下,增加功能
首先,我们要再理一遍函数是什么:
我们定义一个函数:
def add(a, b):
return a + b
我们都知道要像这样调用:
def add(a, b):
return a + b
print(add(2, 3))
因为add函数是带返回值的。
但如果不加括号呢?
def add(a, b):
return a + b
print(add(2, 3))
print(add)
"""
运行结果:
5
<function add at [这里他会输出一个十六进制数]>
"""
他没有报错,而输出了一个function add at 十六进制数
重点来了:
一个函数加上括号就会被执行
我之前就犯过这个错误,在tkinter的Button组件的command属性里的函数加了括号,结果按钮还没有按他就执行了(command属性决定了这个按钮按下后会做什么,但不能加括号,否则你还没按他就会执行)
那他输出的这个function add at 十六进制数到底是什么呢?
我们知道,type()内置函数可以输出参数的类型,那么我们不妨试试让它输出type(add),也就是add的类型:
def add(a, b):
return a + b
print(type(add))
他说是function类型
我们知道,type(2)
就是<class 'int'>
(整数类型)
type("Hello world")
就是<class 'str'>
(字符串类型)
那么<class 'function'>
呢?
函数类型
也就是说函数也和整数、字符串、浮点数这些一样,都是一个对象,这也是为什么说python中一切均可视为对象的原因。
那既然字符串、整数这些东西能够进行赋值、当返回值、当参数等操作,那么函数也能做到,于是就有下面这些骚操作:
a = print
a("Hello world")
# 输出: Hello world
"""
解析:这个好理解,把print赋值给a
a("Hello world")就和print("Hello world")等价
而且更牛逼的是,即使print已经赋给a了
但print仍然可以使用
"""
def output():
return print
output()("Hello world")
# 输出: Hello world
"""
解析:
这个就写得比较有意思了
小技巧:任何返回值可以在调用时直接替换这个函数
output()的返回值是print,调用时系统就会把
output()("Hello world")中的output()替换成print,
后面在接一个("Hello world")输出Hello world
"""
def output():
print("Hello world")
def act(func)
func()
act(output)
# 输出: Hello world
"""
解析:我们定义了一个output函数
可以输出Hello world
但并没有直接调用,而是写了一个act函数
它的参数是一个函数,作用就是调用函数
那么act(output)就会间接执行output()
从而输出Hello world
"""
请务必看完这三段代码的解析,理解透彻之后再来看下面的内容
别偷懒啊,一定要看
请务必看完这三段代码的解析,理解透彻之后再来看下面的内容
不然很难理解装饰器
为了方便,接下来我们把
- 函数能被赋值称为定律1
- 函数能被返回称为定律2
- 函数能当参数称为定律3
好,我相信你已经理解了,那么我们在说说作用域
相信大家都知道,变量是有作用域的,就像下面这样:
def output(num):
word = "Hello world"
for i in range(num):
print(word)
output(2)
print("我输出了{}遍{}".format(num, word))
这就是错误的
因为num和word这两个变量的作用域在output里面(本地变量)
在外部不能使用
所以他说name 'num' is not defined
(num还没有被定义)
正确的做法应该声明"num"和"word"是全局变量,也就是关键字global
,像这样:
def output(num):
global num
global word
word = "Hello world"
for i in range(num):
print(word)
output(2)
print("我输出了{}遍{}".format(num, word))
这就对了
但函数也有作用域
def add(a, b):
def inner():
return a + b
# inner只能在add内部调用
# inner返回了a + b,那么add就会返回a + b
return inner()
print(add(2 + 3))
看懂了吗,
还记得我在解析定律2时分享的先技巧吗?
一个函数的返回值在调用时可以直接替换这个函数
return inner()也就把inner的返回值a + b套到了return后面,从而返回a + b
好,你已经理解了这个例子(不要嫌我啰嗦,装饰器我也是靠一个又一个的例子来理解的)
你离装饰器越来越近了,
# 你先别管deco函数什么意思
def deco(func):
def inner():
print("===start===")
# 根据变量作用域,这里可以使用func参数
# 也就是执行func
func()
print("===finish===")
# 待会儿会讲为什么inner不加括号
return inner
现在把你想象成python,你来分析分析主人写得代码什么意思
先看deco函数有一个叫func的参数
再看deco中定义了一个inner函数,inner函数的作用是:
- 先输出开始特效
- 执行deco的参数(就是那个func),根据定律3,这是成立的
- 输出结束特效
然后,deco函数返回了inner,根据定律2这也是行得通的
先不要管他,现在我们尝试去再写一个普普通通的函数:
def hello():
print("Hello world")
好,把这个hello当做deco的参数:
def deco(func):
def inner():
print("===start===")
func()
print("===finish===")
# 待会儿会讲为什么inner不加括号
return inner
# 上面是刚才写的deco函数,下面是hello函数
def hello():
print("Hello world")
# 我们想一想deco(hello)会发生什么
deco(hello)
- 现在那个
def deco(func):
里的func
替换成了hello
- 然后进入inner,他现在还没有执行,我们先看看什么意思,他会输出开始特效,然后执行hello,然后输出结束特效。
- return inner,deco的返回值是inner (不能加括号,前面我们说过,不管出现在哪里,一旦加了括号,他都会被执行)
- 我们又回到了deco(hello),他的返回值是inner,也就是一个可以让hello执行时增加开始和结束特效的函数
- 也就是说在这里
deco(hello)
和inner
是相等的
你发现了什么?
这个inner,也就是deco(hello),就是我们在文章开头说的能够实现在不改变hello的前提下,在hello执行前后增加开始结束特效的函数
所以我们根据定律1,这样做:
hello = deco(hello)
"""
解析:
把inner赋值给hello,使hello增加开始结束特效
"""
hello()
"""
这时执行的hello已经不是之前
那个只能输出Hello world的那个函数了。
因为前面我们把deco(hello),也就是那个inner赋给了hello,
所以这里执行的其实就是inner,
那个能在hello执行前后增加开始结束特效的函数。
"""
这,就是装饰器
修饰这个函数,在不改变原函数的前提下,增加功能,符合装饰器的定义
恭喜你已经做出来你的第一个装饰器了:
def deco(func):
def inner():
print("===start===")
func()
print("===finish===")
return inner
def hello():
print("Hello world")
hello = deco(hello)
hello()
"""
输出:
===start===
Hello world
===finish===
"""
是不是很有成就感?
如果要多次调用,就不用每一次都加print了,直接hello(),就有令小白瑟瑟发抖的高级特效。而且也不用改动原函数。
你可能觉得这还没直接改动原来函数方便呢,但如果有100个函数都要增加特效呢?
你写的随便一个函数都可以用这个装饰器来修饰
对不对?
高级吧?
而且,python还给我们准备了一个叫"语法糖"的东东
他能够将hello = deco(hello)给简化
这样做:
def deco(func):
def inner():
print("===start===")
func()
print("===finish===")
return inner
@deco
def hello():
print("Hello world")
hello()
"""
输出:
===start===
Hello world
===finish===
"""
用@deco
来代替hello = deco(hello)
,写到def hello():
前面
好了,到这里,你已经掌握装饰器了。
其实,在python中像生成器,装饰器这些高级的玩意很有用,用得高级,用得合理巧妙不仅能使代码更美观简便,还能让老板给你升职加薪。[滑稽]
当然,这是装饰器的基础用法,你还可以多去探索像多个装饰器修饰一个函数这种高级算法。
加油吧!!
最后来个小投票,你觉得下面三个操作哪个最难?