首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

python奇遇记:深入理解装饰器

什么是装饰器

装饰器是什么,简单来说,装饰器可以改变一个函数的行为,比如原本有一个函数用来计算菲波那切数列,我们给这个函数加个计算执行时间的装饰器,这样原来的函数不仅能够计算菲波那切数列,而且还可以输出计算花费了多少时间。

在Python中,有几个很常见的内置装饰器:比如, 它可以将一个类的方法声明为静态的。, 为类中的变量设置get和set方法,保证了封装性。

如果你使用过python的web框架(比如flask)开发过网站,你应该经常会见到装饰器,像下面这样:

@app.route("/")

defhello():

return"Hello World!"

这段代码把路由绑定到hello函数上,这样你输入网址之后就可以看到。

先来看个很简单的例子:

# 定义了一个装饰器

defdeco(func):

defhah(): print('hahha')

returnhah

上面我们定义了一个装饰器,打印hahah,接下来使用:

# 使用这个装饰器

@deco

deflal(): pritn('lalalala')lal()

执行lal()会输出。 可见deco装饰器改变了lal函数的功能。上面的代码中,我们实际上是把lal函数放入了deco函数,像这样:

lal = deco(lal)

只不过,直接使用@标志把装饰器放在某个函数上更方便一点而已。

装饰器其实就是一个函数嵌套另一个函数(这里涉及到一个概念叫做闭包,下面会讲到)。在装饰器的定义中,需要把内部的函数返回(像hah),内部函数用来真正的改变被装饰函数的功能。

不过,上面定义的装饰器好像没什么用,我们来真正的写一个装饰器,像文章开头说的那样,定义一个装饰器计算函数执行的时间。

实现一个简单的装饰器

importtime

# 这个装饰器接收一个函数作为参数

defclock(func):

# clocked用来改变被装饰函数功能# 接收任意可变参数defclocked(*args):

#先计算时间t0 = time.perf_counter()

# 然后运行被装饰的函数result = func(*args)

# 计算运行前后的时间差elapsed = time.perf_counter()-t0

# 函数的名字name = func.__name__

# 被装饰函数的所有变量arg_str =','.join(repr(arg)forarginargs)

# 输出print('[%0.8fs] %s(%s) -> %r'% (elapsed, name, arg_str, result))

# 返回被装饰函数执行结果# 可见装饰器是在原来的函数上增加了某些功能# 而不是完全改变被装饰函数returnresult

# 把clocked函数返回returnclocked

来使用一下上面定义的装饰器:

@clock

deffactorial(n):

return1ifn

执行结果:

可以看到,在输出计算结果的同时,输出了每一步的执行时间。

装饰器除了改变函数功能之外还有一个特性是,函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。这点需要注意。

当然了,装饰器之上还可以放一个装饰器,不过是多了一层嵌套而已。

python中还有一个内置的模块functools,这里面定义了一些常用的装饰器函数,帮助你更好地定义自己的装饰器。这里就不讲了。

闭包

说到闭包,在上面的代码中我们已经见识到了,函数中嵌套函数就是闭包。严格来说,闭包是指延伸了作用域的函数,怎么理解?不如来看个例子:

我们定义一个函数不断计算平均值,它会记住上一次计算的值进行累计。

# 先看一些效果

avg = make_averager()print(avg(10))print(avg(11))print(avg(12))

输出如下:

10.010.511.0

第一次输出10,第二次输出10加11的平均值,第三次输出10加11加12的平均值。

怎么实现的?

defmake_averager():

# 局部变量series# 用来保存每次输入的值series = []

defaverager(new_value): series.append(new_value) total = sum(series)

returntotal/len(series)

returnaverager

上面的函数中,series是局部变量。当我们调用avg(10)的时候,函数已经返回了,按理说它的本地作用域已经不存在了,但是我们还是可以继续使用。这是因为series其实是自由变量,它不受本地作用域的限制。需要注意的是,对于不可变类型,需要显示用关键字声明自由变量,如果不声明的话,会隐式的创建局部变量,这样自由变量就会失效。而可变类型则不需要。比如,我们来更改一下上面的代码:

# 改一下求平均值的函数# 用另一种方法

defmake_averager(): count =total =defaverager(new_value):

# count、total是不可变类型# 需要声明为自由变量nonlocalcount, total count +=1total += new_value

returntotal / count

returnaverager

除了上面说的装饰器的用法之外,我们还可以为装饰器添加参数,像这样,限于篇幅,下一篇文章再介绍。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180104A0UA3D00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券