修饰符(有的也翻译成装饰符、装饰器,英文叫decorator)是Python中一个很有意思的feature,它可以向已经写好的代码中添加功能。这其实也叫元编程,因为程序的一部分在编译的时候尝试修改程序的另一部分。
高阶函数
在学习Python的修饰符前,我们要知道几个a概念,首先是Python中的所有东西都是对象,所有我们定义的变量、类甚至与于函数,都是对象。函数是对象是个什么概念呢?就是函数之间可以相互赋值:
def first(msg):
pritn(msg)
first("Hello")
>>> Hello
second = first
second("Hello")
>>> Hello
上面的first
和second
都是指向同一个函数对象的。这种直接函数名之间的直接赋值,在C++中是不支持的。
当然,函数也可以当作参数传递给其他函数,最常见的就是map
、filter
和reduce
这三个函数了。
def func(x):
return x*2
list(map(func, [1,2,3]))
>>>[2,4,6]
list(map(lambda x: x * 2, range(1,4)))
>>>[2,4,6]
下面我们自定义一个函数,其参数也是函数:
def add(x):
return x + 1
def test(func, x):
return add(x)
执行
test(add, 2)
>>> 3
在Python中,把其他函数当做参数的函数,叫做高阶函数。
嵌套函数
就是nested function,在函数中定义函数,这个我们之前写过,直接放上之前的链接:Python嵌套函数 闭包 详解
Python修饰符
下面回归正题,来讲Python修饰符。在Python中,使用@
来表示修饰符,但这里我们先看下不使用@
来完成一个修饰函数。
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
pretty = make_pretty(ordinary)
pretty()
>>> I got decorated
I am ordinary
在这个例子中,make_pretty()
就是一个修饰器,在pretty = make_pretty(ordinary)
语句中,ordinary
函数被修饰,返回的函数赋值给了pretty
。所以修饰器就像对函数又进行了一层的包装,下面来看使用修饰符@来对函数进行修饰,只需在定义函数的上一行加上@func
即可。
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
@make_pretty
def ordinary():
print("I am ordinary")
ordinary()
>>> I got decorated
I am ordinary
修饰符必须出现在函数定义前一行,不允许和函数定义在同一行。只可以在模块或类定义层内对函数进行修饰,不允许修饰一个类。一个修饰符就是一个函数,它将被修饰的函数做为参数,并返回修饰后的同名函数或其它可调用的东西。
本质上讲,修饰符@类似于回调函数,把其它的函数作为自己的入口参数,在目的函数执行前,执行一些自己的操作,然后返回目的函数。当Python解释器读取到修饰符时,会调用修饰符的函数,来看下面的例子(这个例子只是为了解释Python读取到修饰符时会直接调用,这个修饰函数并没有返回目的函数)
# test.py
def test(func):
print('This is test function step1')
func()
print('This is test function step2')
@test
def func():
print('This is func...')
代码打印如下:
This is test function step1
This is func...
This is test function step2
这段代码中只是定义了两个函数,并没有调用它们,但仍然会有结果打印出来。
当被修饰的函数中含有参数时,应该怎么来写呢?
我们定义一个除法函数,参数a和b,返回a/b的结果。
def divide(a,b)
return a / b
我们知道除法的除数不能是0,因此当我们令b=0时,就会报错。
>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
所以我们需要增加判断机制,当除数为0时,需要告诉用户不能执行。但divide()
函数中我们就只想完成除法的功能,判断机制就可以通过修饰符来完成。
def smart_divide(func):
def inner(a, b):
print("I am going to divide", a, "and", b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a, b)
return inner
@smart_divide
def divide(a, b):
print(a/b)
可以看到,在修饰符函数内容的嵌套函数inner()
中,嵌套函数的参数依然的(a,b),返回值也同样是func(a,b)
,这就是包含参数的函数被修饰时的写法。
divide(2,5)
>>> I am going to divide 2 and 5
0.4
divide(2,0)
>>> I am going to divide 2 and 0
Whoops! cannot divide
一般的,我们定义函数的参数可以设为*args, **kwargs
,其中*args表示位置参数,位置很重要,是以列表形式呈现;**kwargs:关键字参数,位置不重要。字典形式呈现。
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
@works_for_all
def ff(*args, **kwargs):
print(args)
print(kwargs)
调用示例如下:
ff(1,2,a=3,b=4)
>>> I can decorate any function
(1, 2)
{'a': 3, 'b': 4}
一个函数的多个修饰符
Python中,一个函数可以有多个修饰符。
def star(func):
def inner(*args, **kwargs):
print("*" * 30)
func(*args, **kwargs)
print("*" * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 30)
func(*args, **kwargs)
print("%" * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer("Hello")
打印结果如下:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
其实上面的:
@star
@percent
def printer(msg):
print(msg)
就等效于下面的写法:
def printer(msg):
print(msg)
printer = star(percent(printer))
因此执行的步骤为:
- 将
printer
函数传入到percent
函数中,percent
函数直接返回inner
函数给star
函数 - 在
star
函数中,传入的参数func
是percent
的内嵌函数inner
,此时在star
的inner
中,先执行print("*" * 30)
,再执行func()
,也就是会进入到percent->inner
中执行 - 此时将执行
print("%" * 30)
,再执行func()
,此时的func
是printer
- 再执行
percent->inner
中的print("%" * 30)
,返回后又到了star->inner
中,执行print("*" * 30)
写的有点绕,看下面的图会更加清晰,从左到右执行。
总结
下面来总结一下修饰符的要点:
- 修饰符必须出现在函数定义前一行,不允许和函数定义在同一行;
- 修饰符的内嵌函数的参数跟被修饰的函数参数相同,内嵌函数的返回值跟被修饰函数的定义表达式一样
- 一个函数可以用多个修饰符,执行顺序是参考上图。
微信公众号: