运维架构笔记

运维架构笔记 新版即将到来!-- 2022/02/01


  • Home

  • Archives

python的装饰器

Posted on 2022-01-31

python的装饰器

看文章你必须了解:

  • python函数的知识
  • python基础操作

刚学python的时候,对装饰器这个概念很是难理解。在百度和知乎上看过很多大佬的文章,但总没有醍醐灌顶的那种感觉。总是觉得好像懂了些什么,其实又不懂。

所以我决定写篇文章,用我的思路来来帮助看到这篇文章的读者理解python中的装饰器,希望能让大家少踩些坑。

名词解释

我觉得要了解装饰器,首先要明白什么是装饰器,装饰器拿来干嘛用,装饰器为什么这么写。这就是看完这篇文章你能知道的全部

  • 装饰器:

从字面上的意思来说,装饰器。起到的是装饰的作用,其实说白了就是给函数加一个新的功能。

他一般长成下面这个样子:

1
2
3
4
5
6
7
8
9
def decorator(f):
def wrapper():
f()
return wrapper

@decorator
function():
some code
return ....

其中decorator就是装饰器了。可能看到这边会有点懵,到后边会慢慢讲

  • 闭包:

在我们讲装饰器的时候,要先讲一下闭包。闭包是一种奇特的函数

变量的作用域

1
2
3
4
5
wr = 1

def foo():
wr = wr+1
return wr

我创建了一个变量wr,和一个函数foo()。大家看一下在foo函数中是不是能取到函数外的wr变量呢?当然是可以的

然后看下一个例子

1
2
3
4
5
def foo():
wr =1
def wrapper():
wr = wr+1
return wr

那么问题来了,在这个foo函数中我嵌套了一个wrapper函数,在wrapper函数中能不能取到在foo函数中的wr变量呢,其实也是可以的

wrapper函数使用到wr这个变量的时候,他先在自己函数内部寻找wr变量,发现没找到之后就会在他的上级,也就是这里的foo函数中寻找,然后他找到了wr = 1。如果foo中也没有找到wr变量的话,还会继续向更上一级寻找。

python中的一切都是对象

然后我们这边要讲一句python中很经典的一句话,就是python中的一切都是对象,其实我们实现的每个函数,都是顶级父类object的子类。这句话看不懂没关系,我们来举例子:

1
2
3
def foo():
print("im foo function!")
fun = foo

大家觉得这段代码对吗,看起来我把foo赋值给了fun,那么fun里面到底是什么呢,我们来print一下吧:

print(fun)

在我加上一个print来把fun输来看到下面的提示。没有报错说明我们的程序没有错,那么输出的是什么呢?

1
<function foo at 0x02BA7348>

function foo at 一串奇怪的东西,这串奇怪的东西其实是一串16进制的值,是函数foo在内存中的地址。原来我们在代码中打

fun = foo

的时候其实是把foo函数的内存地址给了fun啊

那我们想想我们之前是怎么调用foo函数的?

1
2
3
def foo():
print("im foo function!")
foo()

是不是这样,用 foo()来调用。那么我们是不是就可以得出:

1
2
3
4
def foo():
print("im foo function!")
fun = foo
fun()

这样我们就给foo函数改了个名字变成了fun,大家可以把代码放到pycharm里试试,把fun()和foo()的答案是不是一样的

知道这两样之后,我们可以继续来了解闭包了。

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

以下给出一个闭包的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
def outer():
a = 10
def inner():
b= 10
print(b)
print(a)
return inner

if __name__ == '__main__':
inner_func = outer()
inner_func()

>> 10

在这里a作为outer的局部变量,一般情况下会在函数结束的时候释放为a分配到的内存。但是在闭包中,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

我们先不谈闭包到底是干嘛的,到后面会有演示,我们刚才说过,闭包是一种奇特的函数,那么我们就先搞清楚什么样的函数才能称得上叫做闭包这个名字:

  • 闭包函数必须有内嵌函数
  • 内嵌函数需要引用该嵌套函数上一级namespace中的变量
  • 闭包函数必须返回内嵌函数

嗯,达成这三样我们就能创建一个闭包了,那大家和我一起来创建一个吧

闭包函数必须有内嵌函数

先达成第一件,我们要先有个内嵌函数:

直接抄上面的例子

1
2
3
def foo():
def wrapper():
print("i want have a closure!")

好了好了,现在我有个内嵌函数了,在foo中嵌了个wrapper函数。

内嵌函数需要引用该嵌套函数上一级namespace中的变量

然后第二步其实就是说嵌在里面的那个函数要引用外面那个函数里面的一个变量才行。那这个简单啊,我可以

1
2
3
4
5
def foo():
a = 1
def wrapper():
a = a+1
print("i want have a closure!")

嵌套在foo函数中的wrapper函数引用了foo函数中的a变量

嘿嘿,先别管这个函数有什么卵用,我们先来实现一个闭包先,然后我们这样也可以完成第二点:

1
2
3
def foo(a):
def wrapper():
print(a)

然后我们来达成第三步

闭包函数必须返回内嵌函数

这个就更简单了嘛,加个return嘛,但是这里要注意了,是闭包函数返回内嵌函数,也就是说,套在外边的那个函数用return返回套在里面的那个函数的内存地址

也就是foo函数return wrapper函数的内存地址

1
2
3
4
def foo(a):
def wrapper():
print(a)
return wrapper

这样我们就亲手创建了一个闭包,下面我们就要讲讲什么时候我们会用到闭包了:

用途解释

首先我们来假象一个需求,我们要创建两个个函数foo、bar foo函数只要做一件事情,print一个hello

bar函数只要做一件事情,print一个world,这个简单啊对不对,大家都会:

1
2
3
4
5
def foo():
print("hello")

def bar():
print("world")

好了,这个函数做得不错,大家都采用了这个方案并在很多代码中调用了这个函数。

有一天产品经理过来说要给所有函数加一个功能,打印这个函数的运行时间(mmp,就输出个hello world还要我打印运行时间)。产品经理的小小要求还是要满足的:

1
2
3
4
5
6
7
8
9
10
11
12
13
import time

def foo():
startTime = time.time()
print("hello")
stopTime = time.time()
print(stopTime - startTime)

def bar():
startTime = time.time()
print("world")
stopTime = time.time()
print(stopTime - startTime)

但是这样虽然成功了,万一有很多函数到时候都要打印运行时间,那岂不是要完蛋。并且这种代码被同行看见会被笑话的,不行不行不行。改一改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time

def timeFUN(fun):
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)

def foo():
print("hello")

def bar():
print("world")

timeFUN(foo)

写完之后瞬间觉得自己的智商碾压大众,让他们以后要用某个函数想知道运行时间的时候就用timeFUN()函数。

但是!那foo函数和bar函数可能已经上线了,被做成接口已经在很多地方应用了,改了调用方式相当于说要和所有用了foo和bar这两个函数的人都说一次:“接口改了,把那一万行里面的foo函数全部改成timeFUN(foo)”。这当然不行啊,怕是要被其他人打死。得想想其他办法。这个时候我们想到了闭包的移花接木大法,具体操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

def timeFUN(fun):
def wrapper()
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)
return wrapper

def foo():
print("hello")

def bar():
print("world")

foo = timeFUN(foo)
bar = timeFUN(bar)
foo()

很显然timeFUN是一个标准的闭包吧,timeFun返回了wrapper函数的返回地址,这个时候我们把timeFun返回的wrapper函数的返回地址再赋值回foo,是不是有点绕,多看几遍这句话自己敲下代码

经过这一顿骚操作,瞬间完成了需求。

最后,其实python给了我们一个更方便的方法可以偷懒不用写

foo = timeFUN(foo)

这种赋值了

最后完美版代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

def timeFUN(fun):
def wrapper()
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)
return wrapper

@timeFUN
def foo():
print("hello")

@timeFUN
def bar():
print("world")

foo()
bar()

是不是和开头的例子很像呢,这就是python的装饰器的基础运用。其实装饰器就是闭包的一种啦。下篇有时间我来讲讲装饰器的高级运用,如果foo这个函数有参数又该怎么办呢?

Hello World

Posted on 2019-04-02

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

运维架构笔记

溯流而

2 posts
© 2019 运维架构笔记
Powered by Hexo
|
Theme — NexT.Muse v5.1.4 粤ICP备19029923号