函数:python中的一等对象
函数也是对象
要想理解装饰器的语法、原理以及运行过程,首先要理解一个概念:在python中,函数是一等对象。
所谓一等对象是指满足一下条件的实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
在Java或者C++中, 整数、字符、数组都是一等对象,这些在python中也不例外,但是在python里函数也是一等对象,同样满足以上条件,因此常被人简称“一等函数”。
举个例子
def foo():
print('hello world!')
>>> foo() # 1
hello world!
>>> type(foo) # 2
function
>>> foo.__name__ #3
'foo'
以上三部分代码分别表示:
foo
是一个函数,可以调用foo
是function类的一个实例foo
具有一个属性,名为__name__
,即函数名
用函数来处理函数
既然函数也是一等对象,那么既然函数可以用来处理基本类型,自然也就可以用来处理函数。
举个例子:
>>> def f(): # 1
... '''
... f is a function
... '''
... pass
>>> f.__doc__ # 2
'\n\tf is a function\n\t'
>>> def re_doc(func, doc): # 3
... func.__doc__ = doc
... return func
>>> f = re_doc(f, 'do nothing') # 4
>>> f.__doc__ # 5
'do nothing'
- 定义一个函数
f
,以及函数说明__doc__
- 函数说明
- 定义一个函数,功能是修改一个函数的
__doc__
属性,参数是一个函数和一个字符串,返回值是一个函数 - 调用
re_doc
f.__doc__
被修改了
用函数来处理函数,就是这么简单。
函数内定义函数
既然可以函数内部定义一个变量,那么同样作为python中的一等对象,函数也可以在函数的内部定义
>>> def outer():
... a = 0 # 1
...
... def inner(): # 2
... print('inner function')
... inner() # 3
... print('outer function')
>>> outer()
inner function
outer function
- 在函数内部定义一个变量
a
- 在函数内部定义一个函数
inner
- 调用
inner
装饰器:用函数修改函数
假如现在有一个需求,需要函数运行结束后打印其运行时间,那么传统的做法又两种,一种是修改函数内部的代码,加入时间统计,二是在调用函数的地方统计。
# 未修改的代码
func1()
func2()
func3()
且不讨论实现这两种方式会影响到代码本身的结构,如果要统计的函数很多,那绝对是一场灾难。
# 修改后的代码
start_time = time.time()
func1()
print(time.time() - start_time)
start_time = time.time()
func2()
print(time.time() - start_time)
start_time = time.time()
func3()
print(time.time() - start_time)
既然是修改函数的功能,那我们可以尝试像对待普通变量一样,用函数来处理,也就是今天要讨论的主题——装饰器。
装饰器是一种特殊的函数,可以动态的调整函数(function)、方法(method)和类(class)的功能,而不需要修改其源代码或定义子类。
写一个装饰器
现在我们使用装饰器来实现上述需求
>>> import time
>>> def timeit(func): # 1
... def wrapper(*args, **kwargs): # 2
... start_time = time.time()
... result = func(*args, **kwargs)
... print('<run %s, cost time:%.4fs>' %(func.__name__, time.time() - start_time))
... return result
... return wrapper # 3
>>> def f(a, b):
... return a + b
>>> f = timeit(f) # 4
>>> print(f(1, 2))
<run f, cost time:0.0000s>
3
- 定义一个函数(装饰器)
- 函数内部定义一个新的函数
- 返回这个函数
- 将装饰器应用于
f
这段代码定义了一个装饰器,将函数传递给该装饰器后,会返回一个新的函数,功能跟原来的函数一致,且增添了统计时间的功能。再之后的使用中,不需要重新编写统计时间的代码。
如果想给新的函数添加统计时间的功能,只需要将这个装饰器应用于这个函数
>>> def f2():
... print('hello world!')
...
>>> f2 = timeit(f2)
>>> f2()
hello world!
<run f2, cost time:0.0000s>
语法糖
语法糖(Syntactic sugar)是指对语言的功能没有影响,但是会方便程序员使用的语法。
根据上面的例子,如果想对一个函数应用decorater
,需要这样写:
>>> def f(): pass
>>> f = decorater(f)
python针对装饰器提供了一个语法糖
>>> @decorater
... def f(): pass
二者的作用是一样的,将装饰器应用到函数f
上
并且使用@
会有一些好处,使用原有的方式时,将应用装饰器的语句f = decorator(f)
放到函数体下面时,如果函数体过长,函数的声明语句与装饰器语句距离很远,有损代码的可读性。
如果对一个函数使用多个装饰器,,如果不用@
,有以下两种方式
# 方式一
f = decorator1(f)
f = decorator2(f)
# 方式二
f = decorator2(decorator1(f))
而使用@
,代码的可读性与可维护性都比上述两种方式更高
@decorator1
@decorator2
def f():
总结
由于python中的函数可以作为函数的参数和返回值,使得python中出现一种特殊的函数——装饰器。可以在不修改函数源码的情况下,增强函数的功能。语法糖@
使得对函数使用装饰器变得更简单清晰。
行香子·过七里濑
苏轼
一叶舟轻,双桨鸿惊。水天清、影湛波平。
鱼翻藻鉴,鹭点烟汀。过沙溪急,霜溪冷,月溪明。
重重似画,曲曲如屏。算当年、虚老严陵。
君臣一梦,今古空名。但远山长,云山乱,晓山青。