python进阶——装饰器

万物皆对象

介绍装饰器之前,我们需要理解一个概念:在介绍装饰器前,我们需要理解一个概念:在 Python 开发中,一切皆对象

什么意思呢?

就是我们在开发中,无论是定义的变量(数字、字符串、元组、列表、字典)、还是方法、类、实例、模块,这些都可以称作对象

怎么理解呢?在 Python 中,所有的对象都会有属性和方法,也就是说可以通过「.」去获取它的属性或调用它的方法,例如像下面这样:

i = 10  # int对象
print(id(i))  # 获取i的内存地址
print(type(i))  # 获取i的类型
s = 'hello'  # str 对象
print(id(s))  # 获取s的内存地址
print(type(s))  # 获取s的类型
​
def hello(): # function对象
    print ('Hello World')
print (id(hello)),
print(type(hello)),
print(hello.__name__)
hello2 = hello
print (id(hello2)),
print(type(hello2)),
print(hello2.__name__)
​
out:
140704515711920
<class 'int'>
2637870361712
<class 'str'>
2637873499536
<class 'function'>
hello
2637873499536
<class 'function'>
hello

 

我们可以看到,常见的这些类型:intstrdictfunction,甚至 classinstance 都可以调用 idtype 获得对象的唯一标识和类型。

例如方法的类型是 function,类的类型是 type,并且这些对象都是可传递的。

方法对象可传递会带来什么好处呢?

这么做的好处就是,我们可以实现一个「闭包」,而「闭包」就是实现一个装饰器的基础。

闭包

闭包的概念:概念:函数里面定义函数

闭包的三个条件:

1.函数中嵌套一个函数

2.外层函数返回内层函数的变量名

3.内层函数对外部作用域有一个非全局变量进行引用

闭包的作用:提高稳定性,实现数据的锁定

闭包举例:

假设我们现在想统计一个方法的执行时间,通常实现的逻辑如下:

*# coding: utf8*

import time

def hello(): 
    start = time.time() *# 开始时间* 
    time.sleep(1) *# 模拟执行耗时*
    print 'hello' end = time.time() *# 结束时间*
    print 'duration time: %ds' % int(end - start) *# 计算耗时*

hello()

*# Output:* *# hello* *# duration time: 1s*

 

统计一个方法执行时间的逻辑很简单,只需要在调用这个方法的前后,增加时间的记录就可以了。

但是,统计这一个方法的执行时间这么写一次还好,如果我们想统计任意一个方法的执行时间,每个方法都这么写,就会有大量的重复代码,而且不宜维护。

如何解决?这时我们通常会想到,可以把这个逻辑抽离出来:

`import time`

`def timeit(func):  *# 计算方法耗时的通用方法*`
  `start = time.time()`
  `func()     *# 执行方法*`
  `end = time.time()`
  `print 'duration time: %ds' % int(end - start)`

`def hello():`
  `time.sleep(1)`
  `print 'hello'`

`timeit(hello)  *# 调用执行*`

 

 

这里我们定义了一个 timeit 方法,而参数传入一个方法对象,在执行完真正的方法逻辑后,计算其运行时间。

这样,如果我们想计算哪个方法的执行时间,都按照此方式调用即可。

timeit(func1) *# 计算func1执行时间* timeit(func2) *# 计算func2执行时间*

虽然此方式可以满足我们的需求,但有没有觉得,本来我们想要执行的是 hello 方法,现在执行都需要使用 timeit 然后传入 hello 才能达到要求,有没有一种方式,既可以给原来的方法加上计算时间的逻辑,还能像调用原方法一样使用呢?

答案当然是可以的,我们对 timeit 进行改造:

*`# coding: utf8*`

`import time`

`def timeit(func):`
  `def inner():`
    `start = time.time()`
    `func()`
    `end = time.time()`
    `print 'duration time: %ds' % int(end - start)`
  `return inner`

`def hello():`
  `time.sleep(1)`
  `print 'hello'`

`hello = timeit(hello)  *# 重新定义hello*`
`hello()    *# 像调用原始方法一样使用*`

 

 

注意观察timeit的变动,它在内部定义了一个inner方法,此方法内部的实现与之前类似,但是,timeit最终返回的不是一个值,而是inner对象。所以当我们调用hello = timeit(hello)时,会得到一个方法对象,那么此时变量hello其实是inner,在执行hello()时,真正执行的是inner方法。我们对hello方法进行了重新定义,这么一来,hello不仅保留了其原有的逻辑,而且还增加了计算方法执行耗时的新功能。回过头来,我们分析一下timeit这个方法是如何运行的?在 Python 中允许在一个方法中嵌套另一个方法,这种特殊的机制就叫做「闭包」,这个内部方法可以保留外部方法的作用域,尽管外部方法不是全局的,内部方法也可以访问到外部方法的参数和变量。

装饰器

明白了闭包的工作机制后,那么实现一个装饰器就变得非常简单了。Python 支持一种装饰器语法糖「@」,使用这个语法糖,我们也可以实现与上面完全相同的功能:

# coding: utf8

@timeit   # 相当于 hello = timeit(hello)
def hello():
    time.sleep(1)
    print 'hello'

hello()  # 直接调用原方法即可

 

 

看到这里,是不是觉得很简单?这里的@timeit其实就等价于hello = timeit(hello)。装饰器本质上就是实现一个闭包,把一个方法对象当做参数,传入到另一个方法中,然后这个方法返回了一个增强功能的方法对象。这就是装饰器的核心,平时我们开发中常见的装饰器,无非就是这种形式的变形而已。

装饰器原理:

1、装饰器可以实现在不更改原有代码的前提下扩展新的功能

2、开放封闭原则:已实现的功能可扩展,但不能修改

 

实现一个简单装饰器:

# 实现一个简单装饰器
def func_a(fun):
    print("func_a调用了")
    def func_b():
        print("先洗手")
        fun()
    return func_b

@func_a
def func_demo():
    print("欢迎进入首页")

func_demo()


#func_a调用了
#先洗手
#欢迎进入首页

 

 

带参数的装饰器:

def add(func):
    def fun(a,b):
        print('相乘',a*b)
        print('相除',a/b)
        func(a,b)
    return fun

@add
def add_num(a,b):
    print("相加",a+b)

(add_num(2,4))
# 相乘 8
# 相除 0.5
# 相加 6

 

 

通用装饰器

def add(func):
    def fun(*args,**kwargs):
        print("装饰器的功能代码:登录")
        func(*args,**kwargs)
    return fun
@add
def index():
    print("这是网站首页")
@add
def goods_list(num):
    print("这是商品列表第{}页".format(num))
a = index
a()
b = goods_list
b(9)
​
# 装饰器的功能代码:登录
# 这是网站首页
# 装饰器的功能代码:登录
# 这是商品列表第9页

 

 

类装饰器

def add(func):
    def fun(*args, **kwargs):
        print("装饰器的功能代码:登录")
        return func(*args, **kwargs)  # 类级别一定要有return,否则会返回None
    return fun
@add
class Myclass:
    def __init__(self, name, pw):
        self.name = name
        self.pw = pw
m = Myclass('xiucai', 123)
print(m.pw)
print(m.name)

# 装饰器的功能代码:登录
# 123
# xiucai

 

 

以上文章部分参考:https://zhuanlan.zhihu.com/p/425626332

 
posted @ 2022-09-22 15:58  秀才哥哥  阅读(56)  评论(0编辑  收藏  举报