通过上一篇文章的学习,我们已经知道:
本片将继续深入探索 Python 中函数的特点,并详细介绍装饰器的更多精彩内容!
一. 深入探索函数
一等公民
我们知道,在 Python 中函数是一等公民,因此可以像基本数据类型一样,赋值给另一个变量:
def my_func(content="Strive for a hundred years and set sail on a new journey"):
return content.upper()
运行:
>> my_func()
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'
>> func_upper = my_func
>> func_upper()
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'
甚至在删除原来的引用 my_func
之后,仍然可以使用新的引用 func_upper
访问原来的函数对象:
>> my_func('helllo python')
...
NameError: name 'my_func' is not defined
>> func_upper('helllo python')
'HELLLO PYTHON'
嵌套函数
Python 中,一个函数中可以定义另一个函数:
def talk():
def whisper(word="yes"):
return word.upper()+"!"
print(whisper())
运行结果:
>> talk()
YES!
>> whisper()
...
NameError: name 'whisper' is not defined
函数引用还可以作为另一个函数的返回值:
def talk():
def inner(word="yes"):
return word.upper()+"!"
return inner
运行结果:
>> whisper = talk()
>> whisper()
'YES!'
>> whisper('no')
'NO!'
此外,函数引用也可以作为参数传递:
def do_something_before(func):
print('Do Something and then call the func.')
ret = func()
return ret
运行结果:
>> do_something_before(func_upper)
Do Something and then call the func.
'STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY'
综合上述知识,我们可以写出一个装饰器,再被封装函数之前和之后分别执行一段代码,而不去修改函数本身:
def my_decorator(func):
def wrapper():
print('Do something and then call the func.')
print(func())
print('Call the fun and then do otherthing.')
return wrapper
运行结果:
>> func_wrapper = my_decorator(func_upper)
>> func_wrapper()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.
上述代码实现了一个最简单的装饰器:将 func_upper
函数传递给装饰器,装饰器将动态地将其包装在任何你想执行的代码中,然后返回一个新的函数 wrapper
。调用新函数 func_wrapper
,可以看到装饰器的效果。
上述代码的问题是:每次调用 func_upper
函数都需要修改为调用 func_wrapper
函数。为此,我们重新赋值 func_upper
引用即可:
>> func_upper = my_decorator(func_upper)
>> func_upper()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.
针对以上过程,Python 为我们提供了一个语法糖简以化操作:
@my_decorator
def my_func(content="Strive for a hundred years and set sail on a new journey"):
return content.upper()
运行结果:
>> my_func()
Do something and then call the func.
STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY
Call the fun and then do otherthing.
最后,装饰器可以叠加使用,但是先后顺序非常重要:
def bold(func):
"""bold 装饰器"""
def wrapper():
return "<b>{}</b>".format(func())
return wrapper
def italic(func):
"""italic 装饰器"""
def wrapper():
return "<i>{}</i>".format(func())
return wrapper
先添加 @italic
装饰器,然后添加 @bold
装饰器:
@bold
@italic
def my_func():
return "Strive for a hundred years and set sail on a new journey".upper()
运行结果:
>> my_func()
'<b><i>STRIVE FOR A HUNDRED YEARS AND SET SAIL ON A NEW JOURNEY</i></b>'
相应地字符串两边先添加了 <i></i>
标签,随后添加了 <b></b>
标签。
下面是交换顺序之后的结果:
@italic
@bold
def my_func2():
return 'hello python'.upper()
运行结果:
>> my_func2()
'<i><b>HELLO PYTHON</b></i>'
二. 向装饰器函数传参
当我们调用装饰器返回的函数时,实际上是调用包装函数 wrapper
,因此给包装函数传递参数即可将参数传递给装饰器:
def decorator_pass_args(func):
def wrapper_with_args(arg1, arg2):
print("args in wrapper: {}, {}".format(arg1, arg2))
func(arg1, arg2)
return wrapper_with_args
@decorator_pass_args
def print_full_name(first, last):
print("My name is {}.{}".format(last, first))
运行结果:
>> print_full_name('Li', 'Mia')
args in wrapper: Li, Mia
My name is Mia.Li
在装饰方法时,必须考虑方法的首个参数,即指向当前对象的引用 self)
:
def decorator_method(func):
def wrapper(self, lie):
lie -= 3
return func(self, lie)
return wrapper
class Person:
def __init__(self, age):
self.age = age
@decorator_method
def print_age(self, lie):
print("age: {}".format(self.age+lie))
运行结果:
>> Alex = Person(18)
>> Alex.print_age(3)
age: 18
>> Alex.print_age(6)
age: 21
也可以使用 *args
和 **kwargs
,定义一个更加通用的装饰器,可以用在任何函数或对象方法上,而不必关心其参数:
def decorator(func):
def wrapper(*args, **kwargs):
print("args ", end=':')
print(args, kwargs)
func(*args, **kwargs)
return wrapper
@decorator
def func_no_args():
print('PYTHON')
运行结果 1:装饰无参数函数
>> func_no_args()
args :() {}
PYTHON
修饰函数,传参方式为位置传参:
@decorator
def func_args(a, b):
print('a={}, b={}'.format(a, b))
运行结果 2:装饰位置参数函数:
>> func_args(100, 1000)
args :(100, 1000) {}
a=100, b=1000
修饰函数,传参方式为位置传参和关键字传参:
@decorator
def func_kwargs(a, b, c=0, d=0):
print('a={}, b={}, c={}, d={}'.format(a, b, c, d))
运行结果 3:装饰位置参数和关键字参数函数:
>> func_kwargs(100, 1000, c=1000000)
args :(100, 1000) {'c': 1000000}
a=100, b=1000, c=1000000, d=0
修饰方法:
class Person:
def __init__(self, age):
self.age = age
@decorator
def print_age(self, lie):
print("age: {}".format(self.age+lie))
运行结果 4:装饰类中的方法:
>> Alex = Person(18)
>> Alex.print_age(3)
args :(<__main__.Person object at 0x000001B915B69160>, 3) {}
age: 21
三. 向装饰器本身传递参数
由于装饰器必须使用函数作为参数,因此不能直接传递参数给装饰器本身,向装饰器本身传递参数时,我们必须另辟蹊径:声明一个用于创建装饰器的函数。
定义装饰器创建函数::
def decorator_maker():
"""此函数用于创建装饰器"""
print("make decorator!")
def decorator(func):
print("decorator the func: {}".format(func.__name__))
def wrapper(*args, **kwargs):
print("do something before")
func(*args, **kwargs)
print("do something end")
return wrapper
print("return the decorator")
return decorator
使用装饰器创建函数 decorator_maker
返回一个装饰器 my_decorator
:
使用创建的装饰器,装饰函数 func_no_args
:
调用被装饰的函数 func_no_args
:
下面,我们可以跳过中间变量 my_decorate
,直接使用 @decorator_maker
来装饰 func_no_args
:
下面,定义一个包含参数的装饰器创建函数,并使用此装饰器传递参数:
def decorator_maker(decorator_arg1, decorator_arg2):
"""此函数用于创建装饰器"""
print("make decorator!")
def decorator(func):
print("decorator receive args: {}, {}".format(decorator_arg1, decorator_arg2))
print("decorator the func: {}".format(func.__name__))
def wrapper(*args, **kwargs):
print("wrapper also receive args: {}, {}".format(decorator_arg1, decorator_arg2))
func(*args, **kwargs)
print("do something end")
return wrapper
print("return the decorator")
return decorator
运行结果:
修改列表 l1
,再次调用 func_no_args
:
>> l1[0] = 100000
>> func_no_args()
wrapper also receive args: [100000, 2, 3], [4, 5, 6]
PYTHON
do something end
装饰器仅在 Python 代码被导入时执行一次,因此之后不能再动态地改变参数。
四. @wraps
装饰器
定义一个函数,打印其 __name__
和 __doc__
属性:
def func_no_args():
"""print python"""
print('PYTHON')
运行结果:
>> func_no_args.__name__
'func_no_args'
>> func_no_args.__doc__
'print python'
给函数 func_no_args
添加上文中定义的装饰器 decorator_maker
:
此时,我们发现无法获取原函数 func_no_args
的 __name__
和 __doc__
属性,取而代之的是 wrapper
函数的相关属性。
定义装饰器时使用 @wraps
装饰 wrapper
函数:
from functools import wraps
def decorator_maker(decorator_arg1, decorator_arg2):
"""此函数用于创建装饰器"""
print("make decorator!")
def decorator(func):
print("decorator receive args: {}, {}".format(decorator_arg1, decorator_arg2))
print("decorator the func: {}".format(func.__name__))
@wraps(func)
def wrapper(*args, **kwargs):
print("wrapper also receive args: {}, {}".format(decorator_arg1, decorator_arg2))
func(*args, **kwargs)
print("do something end")
return wrapper
print("return the decorator")
return decorator
运行结果:
五. 装饰为什么那么有用
因为使用装饰器可以在函数的外部扩展一个函数的行为,而不用修改内部的调用。
为了加强巩固,下面分别使用装饰器来打印一个函数的执行时间、记录函数日志、记录一个函数的运行次数:
import time
from functools import wraps
def benchmark(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time.time()
res = func(*args, **kwargs)
print("执行时间: %s %.2f" % (func.__name__, time.time() - t))
return res
return wrapper
def logging(func):
@wraps(func)
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print("运行日志:", func.__name__, args, kwargs)
return res
return wrapper
def counter(func):
count = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal count
res = func(*args, **kwargs)
count += 1
print("执行次数:", func.__name__, count)
return res
return wrapper
@counter
@logging
@benchmark
def reversed_str(string):
time.sleep(3)
return reversed(string)
运行结果: