【python进阶 笔记】装饰器(decorator) (重点)

【python进阶 笔记】装饰器 (重点)

目录

1. 装饰器介绍

1.1. 引入代码

1.2. 需求来了(通过需求逐步了解)

1.3. 装饰器的实现过程(重要)

2. 装饰器(decorator)作用

3. 装饰器示例

3.1. 对无返回值、无参数函数的装饰

3.2. 对有参数、无返回值的函数进行装饰

3.3. 用同一个装饰器对多个函数进行装饰

3.4. 装饰器几时开始装饰?!

3.5. 对不定长参数的函数进行装饰!

3.6. 对有返回值的不定长参数函数进行装饰(重要)

4. 多个装饰器对同一个函数装饰

多个装饰器对同一个函数装饰 应用Demo

5. 装饰器带参数,在原有装饰器的基础上,设置外部变量

6. 类装饰器(了解)


1. 装饰器介绍

装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,这也是Python面试必备的问题。

1.1. 引入代码

#### 第一段 ####
def foo():
    print('foo')

foo  # 表示是函数,变量名指向了函数
foo()  # 表示执行foo函数

#### 第二段 ####
def foo():
    print('foo')

foo = lambda x: x + 1  # lambda 匿名函数,默认返回值是表达式的结果

foo()  # 执行lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数

在python中,所有的方法名、函数名、变量名和类的名字通常是不允许相同的,python中都把他们当作一个变量名来对待。

故上面的代码中,函数名仅仅是个变量,只不过指向了定义的函数而已,所以才能通过 函数名()调用。如果 函数名=xxx被修改了,那么当在执行 函数名()时,调用的就不是之前的那个函数了

 

1.2. 需求来了(通过需求逐步了解)

初创公司有N个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

############### 基础平台提供的功能如下 ###############

def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

def f4():
    print('f4')

############### 业务部门A 调用基础平台提供的功能 ###############

f1()
f2()
f3()
f4()

############### 业务部门B 调用基础平台提供的功能 ###############

f1()
f2()
f3()
f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

(方案1)老大把工作交给 Low B,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。太棒了,有充足的时间泡妹子...

当天Low B 被开除了…

(方案2:对相关功能的函数进行一一修改老大把工作交给 Low BB,他是这么做的:

############### 基础平台提供的功能如下 ############### 

def f1():
    # 验证1
    # 验证2
    # 验证3
    print('f1')

def f2():
    # 验证1
    # 验证2
    # 验证3
    print('f2')

def f3():
    # 验证1
    # 验证2
    # 验证3
    print('f3')

def f4():
    # 验证1
    # 验证2
    # 验证3
    print('f4')

############### 业务部门不变 ############### 
### 业务部门A 调用基础平台提供的功能### 

f1()
f2()
f3()
f4()

### 业务部门B 调用基础平台提供的功能 ### 

f1()
f2()
f3()
f4()

代码重用性太大不行)过了一周 Low BB 被开除了…

方案3:定义一个函数用来调用)老大把工作交给 Low BBB,他是这么做的:

只对基础平台的代码进行重构,其他业务部门无需做任何修改

############### 基础平台提供的功能如下 ############### 

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass


def f1():

    check_login()

    print('f1')

def f2():

    check_login()

    print('f2')

def f3():

    check_login()

    print('f3')

def f4():

    check_login()

    print('f4')

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:

老大说:

写代码要遵循 开放封闭 原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块,不能再进行修改
  • 开放:对扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:

方案4:用装饰器)

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner

@w1
def f1():
    print('f1')
@w1
def f2():
    print('f2')
@w1
def f3():
    print('f3')
@w1
def f4():
    print('f4')

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

这段代码的内部执行原理

以下方代码为例:

def set_func(func):
	def call_func():
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		func()
	return call_func

@set_func  
def test1():
	print("-----test1----")

test1()


#输出:
#---这是权限验证1----
#---这是权限验证2----
#-----test1----
  • 若需要修改 函数A 的功能(如:在函数调用前要加一部分代码),可以在函数A的外面定义一个闭包,在函数A的前面写上 @闭包外部函数名 ,这样就会先执行 @的函数 。就可以实现在原来函数整体不修改的前提下增加功能。
  • 如上,在test1函数前添加 @set_func 就可以实现添加“验证” 。

1.3. 装饰器的实现过程(重要)

def set_func(func):
	def call_func():
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		func()
	return call_func


# 代码段1----------
@set_func  # 等价于test1 = set_func(test1) 
def test1():
	print("-----test1----")
test1()  # 可通俗的看做调用了call_func


# 代码段2------------
# def test1():
# 	print("-----test1----")
# ret = set_func(test1)
# ret()


# 代码段3-----------
# def test1():
# 	print("-----test1----")
# test1 = set_func(test1) 
# test1()

分别运行代码段1、2、3的输出结果一样:

---这是权限验证1----
---这是权限验证2----
-----test1----

分析:

  • 先看 代码段2:通过闭包实现了添加功能 添加了两句输出。
  • 再看 代码段1在函数 test1定义前加上 @set_func , 等价于test1 = set_func(test1) 。此处使用了装饰器,整个代码段1与 代码段3完全等价。
  • 代码段3
    1. 在python中,所有的方法名、函数名、变量名和类的名字通常是不允许相同的。
    2. 但代码段3中,在 函数test1 定义后,语句 test1 = set_func(test1) 又定义了变量test1 用来指向set_func(test1) 的返回结果;
    3. set_func(test1)将函数test1 的引用作参数传给了函数set_func,set_func内定义 函数call_func 并作函数返回值 (此时call_func中的func 指向原来的函数test1,func() 即调用函数test1)  ;
    4. 此时 set_func(test1) 的返回值指向了 函数call_func,(包括添加的功能代码和原来的test1函数代码),再定义 变量test1 来接收其返回值,这是,可以通俗的说执行test1()就会调用call_func
    5. 这样,巧妙的实现了给 函数test1 添加功能,又用变量test1重新指向添加功能后的代码。相比代码段1调用时仍然使用 test1 来调用
    6. 代码段3 即代码段2中使用 装饰器的实现过程

代码段2装饰器和代码段3中的优点

  1. 不用修改原函数内部的代码可以加功能;
  2. 调用时依然用原来的函数名来调用,无需修改。
  3. 能做到调函数不变的情况下,做到功能的拓展

注意,装饰器只能在函数的前或者后添加代码或者说功能。

2. 装饰器(decorator)作用

  1. 引入日志
  2. 函数执行时间统计
  3. 执行函数前预备处理
  4. 执行函数后清理功能
  5. 权限校验等场景
  6. 缓存

demo:统计函数的运行时间

import time


def set_func(func):
	def call_func():
		start_time = time.time()
		func()
		stop_time = time.time()
		print("alltimeis %f" % (stop_time - start_time))
	return call_func


@set_func  # 等价于test1 = set_func(test1) 
def test1():
	print("-----test1----")
	for i in range(10000):
		pass
test1()

3. 装饰器示例

3.1. 对无返回值、无参数函数的装饰

参考1.3的demo即可。

3.2. 对有参数、无返回值的函数进行装饰

def set_func(func):
	def call_func(a):
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		func(a)
	return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num):
	print("-----test1----%d" % num)

test1(100)  # 可通俗的看做调用了call_func
# test1(200)

test1(100)可通俗的看作是调用了call_func,100传给a,a再传给func,func的实参即原来的函数test1。

3.3. 用同一个装饰器对多个函数进行装饰

def set_func(func):
	def call_func(a):
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		func(a)
	return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num):
	print("-----test1----%d" % num)


@set_func  # 相当于 test2 = set_func(test2)
def test2(num):
	print("-----test2----%d" % num)


test1(100)  # 可通俗的看做调用了call_func(100)
test2(200)

  • 装饰一个函数相当于创建一个闭包。

3.4. 装饰器几时开始装饰?

def set_func(func):
	print("---开始进行装饰")
	def call_func(a):
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		func(a)
	return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num):
	print("-----test1----%d" % num)


# 装饰器在调用函数之前,已经被python解释器执行了,所以要牢记 当调用函数之前 其实已经装饰好了,尽管调用就可以了
# test1(100)



以上demo并没有调用执行test1函数,但会输出:---开始进行装饰

  • 可见:只要函数定义前写上 @闭包外层函数名 就开始装饰了,而不是被装饰的函数执行时才开始装饰
  • 即只要遇到装饰器就会执行,装饰器在调用函数之前,,已经被python解释器执行了。
  • 所以要牢记 当调用函数之前 其实已经装饰好了,尽管调用就可以了

3.5. 对不定长参数的函数进行装饰!

def set_func(func):
	print("---开始进行装饰")
	def call_func(*args, **kwargs):
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		# func(args, kwargs)  # 不行,相当于传递了2个参数 :1个元组,1个字典
		func(*args, **kwargs)  # 做实参时写上*或**会拆包(写args相当与直接传一个元祖或字典参数)
	return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
	print("-----test1----%d" % num)  # num替换%d 
	print("-----test1----" , args)  # 先打印逗号前的部分,再打印逗号后的args元祖
	print("-----test1----" , kwargs)


test1(100)
test1(100, 200)
test1(100, 200, 300, mm=100)

其实,对于有0个及以上的函数装饰时,直接写不定长参数,就可以达到通用的效果。注意参数再传递时拆包再传。

注:*args, **kwargs参数作为实参再传给给别参数时,写上*或**才会拆包。而传args或kwargs相当与直接传一个元祖或字典参数。

3.6. 对有返回值的不定长参数函数进行装饰(重要)

该Demo的装饰器对不定长参数和有无返回值的函数都通用,通用装饰器

def set_func(func):
	print("---开始进行装饰")
	def call_func(*args, **kwargs):
		print("---这是权限验证1----")
		print("---这是权限验证2----")
		# func(args, kwargs)  # 不行,相当于传递了2个参数 :1个元组,1个字典
		return func(*args, **kwargs)  # 做实参时写上*或**会拆包(写args相当与直接传一个元祖或字典参数)
	return call_func


@set_func  # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
	print("-----test1----%d" % num)
	print("-----test1----" , args)
	print("-----test1----" , kwargs)
	return "ok"


@set_func
def test2():
	pass

ret = test1(100)
print(ret)

ret = test2()
print(ret)

ret = test1(100)中的test1指向闭包的函数call_func。

test1(100)调用过程:

test1  ---调用---> 

call_func ---func(对应实参test1)会调用---> 

test1 ---test1返回ok至---> 

call_func ---返回 test1返回的ok ,并赋值给--->

变量ret

由函数test2可见,没有返回值的函数其装饰器写上 return 也没有影响,因为都会返回None。

4. 多个装饰器对同一个函数装饰

def add_privilege_1(func):
	print("---开始进行装饰权限1的功能---")
	def call_func(*args, **kwargs):
		print("---这是权限1验证----")
		return func(*args, **kwargs)
	return call_func


def add_privilege_2(func):
	print("---开始进行装饰权限2的功能---")
	def call_func(*args, **kwargs):
		print("---这是权限2验证----")
		return func(*args, **kwargs)
	return call_func


@add_privilege_1  # 等价于 test1 = add_privilege_1(test1)
@add_privilege_2  # 等价于 test1 = add_privilege_2(test1)
def test1():
	print("------test1------")


test1()

输出:

---开始进行装饰权限2的功能---
---开始进行装饰权限1的功能---
---这是权限1验证----
---这是权限2验证----
------test1------
 

由输出可见,分析:

  • 用多个装饰器装饰一个函数时,先装下面的,后装上面的,类似脱衣原则
    • 比如,demo中 @add_privilege_2离函数test1更近,先装饰。@add_privilege_2离函数函数test1第二进,第二个装饰。
  • @add_privilege_2 等价于 test1 = add_privilege_2(test1),@add_privilege_1 等价于 test1 = add_privilege_1(test1) (注意:此时作为参数的 test1 的指向是装饰器@add_privilege_2执行后有新指向的test1,其不再指向原来的 test1 函数...),装饰完之后,test1指向最后装饰的 func。
  • 故,执行 test1() 时,先执行写在上面的,后执行写在下面的,与装饰顺序相反

多个装饰器的意义当一个原来的函数需要添加多个功能时,可以使用多个装饰器来添加功能,并保证一个装饰器一个功能

多个装饰器对同一个函数装饰 应用Demo

用装饰器对原函数返回的值加上<h1></h1> 标签和<td></td>标签。

def set_func_1(func):
	def call_func():
		# "<h1>haha</h1>"
		return "<h1>" + func() + "</h1>"
	return call_func

def set_func_2(func):
	def call_func():
		return "<td>" + func() + "</td>"
	return call_func


@set_func_1
@set_func_2
def get_str():
	return "haha"

print(get_str())

输出: <h1><td>haha</td></h1>

输出结果再次验证了装饰器的执行顺序(注:装饰顺序相反)。

5. 装饰器带参数,在原有装饰器的基础上,设置外部变量

实例和注释如下:

from time import ctime, sleep

def timefun_arg(pre="hello"):
    def timefun(func):
        def wrapped_func():
            print("%s called at %s %s" % (func.__name__, ctime(), pre))
            return func()
        return wrapped_func
    return timefun

# 下面的装饰过程
# 1. 调用timefun_arg("test")
# 2. 将步骤1得到的返回值,即time_fun返回, 然后time_fun(foo)
# 3. 将time_fun(foo)的结果返回,即wrapped_func
# 4. 让foo = wrapped_fun,即foo现在指向wrapped_func

@timefun_arg("test")  # 等价于foo = timefun_arg("test")(foo)
def foo():
    print("I am foo")

foo()
sleep(2)
foo()

# 执行时可以理解为:foo() == timefun_arg("test")(foo)() == time_fun(foo)() == wrapped_func()

输出:

foo called at Fri Feb 28 14:10:00 2020 test
I am foo
foo called at Fri Feb 28 14:10:02 2020 test
I am foo

6. 类装饰器(了解)

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的。

# def set_func_1(func):
# 	def call_func():
# 		# "<h1>haha</h1>"
# 		return "<h1>" + func() + "</h1>"
# 	return call_func


class Test(object):
	def __init__(self, func):
		self.func = func

	def __call__(self):
		print("这里是装饰器添加的功能.....")
		return self.func()


@Test  # 相当于get_str = Test(get_str)  ,等号右边类名()意味着创建了一个实例对象(get_str再指向实例对象),get_str传给了类的__init__方法
def get_str():
	return "haha"

print(get_str())  # 实例对象() 会调用__call__方法

输出:

这里是装饰器添加的功能.....
haha

-----end-----

发布了50 篇原创文章 · 获赞 10 · 访问量 6604

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104535477
今日推荐