python装饰器原理-python装饰器的原理和使用

一、最简单的装饰器

装饰器是python中很基础也很实用的一个特性。通过装饰器我们可以很方便地为一些函数添加相同的功能。我们以测量函数运行时间为例来讲一讲python装饰器的运行原理。

1、使用装饰器打印函数运行时间

通常我们使用 time 库来获取函数的运行时间。

import time

# 函数运行 2秒

def func():

time.sleep(2)

start_time = time.time()

func()

end_time = time.time()

print('函数运行时间为:%.2fs' % (end_time - start_time))

so easy,现在我们来思考一下:如果我们有 100个函数,怎么打印这100个函数的运行时间呢?

总不能按照上面的写法来100次吧,这绝不是一个程序猿应该做的事,这时候装饰器就可以排上用场了。

我们先看代码,然后再慢慢讲其中的原理。

import time

def timeit(func):

def result():

start_time = time.time()

func()

end_time = time.time()

print('函数运行时间为:%.2fs' % (end_time - start_time))

return result

@timeit

def func_0():

time.sleep(2)

# 省略98个

@timeit

def func_99():

time.sleep(2)

# 接下来直接调用这100个函数即可

func_0()

# ...

使用了 timeit 装饰器的函数和没使用装饰器的原始方法效果一样,不过这只是装饰器最简单的一个应用,我们下面来看看其中的原理。

2、timeit的运行原理

首先,大家需要知道在python中的函数也是对象,是对象就可以作为参数传递,这是装饰器实现的基础。

接下来我们来看看装饰器 timeit,不难看出 timeit 是一个函数。准确地说 timeit 是一个接受一个函数为参数并且返回一个函数的函数。

听着挺拗口的,别急,看我细细道来。

timeit 是一个函数,它接受一个参数,这个参数必须是函数(或者说可调用对象),并且 timeit 的返回结果一定是一个函数。

看到这里大家应该对装饰器有一点了解了,原来装饰器就是接受一个函数为参数返回另一个函数的函数。

现在我们就可以解释 timeit 的运行原理了,看下面的代码

# 代码块1

@timeit

def func_0():

time.sleep(2)

# 代码块2

def func_0():

time.sleep(2)

func_0 = timeit(func_0)

这里的代码块1和代码块2完全等价,注意完全两个字,这说明这两段代码的效果一模一样。

事实上代码块1就是代码块2的简写形式,因为代码块2的写法挺麻烦的,所以python的设计者加了一些语法糖,就有了代码块1的写法。

了解了这些我们再来看看 timeit 内部

def timeit(func):

def result():

start_time = time.time()

func()

end_time = time.time()

print('函数运行时间为:%.2fs' % (end_time - start_time))

return result

在 timeit里面我们定义了一个函数 result ,函数 result 里面的内容我们很熟悉,就是打印函数 func 的运行时间。这里的 func 就是我们要装饰的函数。

最后我们将函数 result 返回,我们再看到代码块2,返回的 result 函数替换了我们要装饰的函数 func_0,这时当我们再调用函数 func_0 时其实就是在调用函数 result。

3、什么是闭包

我们看一段代码:

def outer():

a = 0

def inner():

print(a)

return inner

func = outer()

func()

上面的函数和装饰器很像,以前学C语言的可能会疑惑为什么变量a在函数 outer 调用完成后仍然能够被访问。

这和python中变量的回收机制有关,python根据变量的引用计数来判断是变量是否需要回收。

当一个对象的引用被创建或复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,如果对象的引用计数减少为0,将对象的所占用的内存释放。

上面的例子中因为对象a的引用一直没有被销毁引用计数不为0,所以在函数 inner 里一直可以访问对象a的值。

而且我们发现在调用过函数 outer 后只有函数 inner 可以访问对象a。

这就相当于为函数 inner添加了一个私有的命名空间,而且只有函数 inner 可以访问这个命名空间,这就是python中的闭包。

装饰器就是利用了闭包的原理,所以我们说装饰器就是是闭包也是完全没有问题的。

二、带参数的装饰器

我们可能看到过这样的装饰器

@decorator('arg','arg2')

def func():

# do something

pass

在一些代码中我们发现有些装饰器是有参数的,这样可以带参数的装饰器是怎么实现的呢?下面我就和大家讲一讲带参数的装饰器是怎么实现的。

在了解了装饰器的原理之后我们知道装饰器其实就是一个返回一个函数的函数。

于是我们就想:既然有返回函数的函数,那有没有返回装饰器的函数呢?当然是有的!

事实上,上面提到的带参数的装饰器就是一个返回装饰器的函数。

其中的原理也很简单,圆括号表示函数调用,而我们的程序都是从右到左执行的,所以在程序运行的时候 @ 后面的应该是 decorator 返回的装饰器。

说完原理,我们再来看看带参数的装饰器应该怎么写,先上代码。

import time

def timeit(out='函数运行时间为:%.2fs'):

def decorator(func):

def result():

start_time = time.time()

func()

end_time = time.time()

print(out % (end_time - start_time))

return result

return decorator

@timeit('函数运行时间为:%.2fs --来自带参数的装饰器')

def func():

time.sleep(2)

func() # => 函数运行时间为:2.00s --来自带参数的装饰器

我们在原来打印函数运行时间的装饰器上添加了自定义打印语句的功能,在使用的时候和下面的代码等价。

func = timeit('函数运行时间为:%.2fs --来自带参数的装饰器')(func)

带参数的装饰器也是利用了闭包将参数绑定到返回的函数上。

三、更加通用的装饰器

前面两部分讲了装饰器的原理,这一部分就讲讲要写出一个通用的装饰器需要注意的问题。

首先就是参数的问题,装饰器返回的函数不是原来的函数,函数的签名也就和原来的函数签名不一样。

当我们还是按照来的方式去调用函数就有可能会出问题,我们通过一个例子来看一下。

def decorator(func):

def result(a):

print(a)

func()

return result

@decorator

def func(a,b,c):

print(a,b,c)

func(1, 2, 3)

# => TypeError: result() takes 1 positional argument but 3 were given

这里报错是因为装饰器返回的函数只接受一个参数,我们还按照调用 func 的方式来调用 result当然会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值