Python装饰器(包学包会)

装饰器可谓是Python中的一大难点,但看了这篇文章,包你学会(前提是基础先要打牢,而且你要认真看)

你要相信自己一定能学会装饰器

废话少说,闲话少讲,我们开始吧!
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中像生成器,装饰器这些高级的玩意很有用,用得高级,用得合理巧妙不仅能使代码更美观简便,还能让老板给你升职加薪。[滑稽]

当然,这是装饰器的基础用法,你还可以多去探索像多个装饰器修饰一个函数这种高级算法。

加油吧!!

最后来个小投票,你觉得下面三个操作哪个最难?

  • 17
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值