Python开发知识:理解和使用装饰器@decorator
Python的装饰器是一个很棒的机制,也是熟练使用Python的必杀技之一。装饰器,顾名思义,就是用来装饰的。它装饰一个函数,保留被装饰函数原来的功能,装饰(然后在其中添加一些其他函数)并返回一个带有新函数的函数对象,所以装饰装饰器本质上是一个返回函数对象的函数(需要确切地说,装饰器必须是可检索的对象(除了函数之外,类也可以充当装饰器)。
在编程时,我们经常会遇到这样的场景:登录验证、权限验证、日志记录等,这些功能代码可能在不同的环节都需要,但它们非常相似,都是被装饰器抽象和剥离的。这部分代码可以很好的解决这类场景。
什么是装饰器?
要了解Python的装饰器,我们首先了解Python的函数对象。我们知道Python中的一切都是对象,函数也不例外。函数是第一类对象。它们可以分配给变量,也可以用作列表的元素,还可以作为参数传递给其他函数。 。
函数可以通过变量引用
定义一个简单的函数:
def say_hi():
print('Hi!')
say_hi()
# Output: Hi!
我们可以通过另一个变量 Say_hi2 来引用函数 Say_hi:
say_hi2 = say_hi
print(say_hi2)
# Output: <function say_hi at 0x7fed671c4378>
say_hi2()
# Output: Hi!
在上面的语句中,Say_hi2 和 Say_hi 引用的是同一个函数定义。两者的执行结果也是一样的。
函数可以作为参数传递给其他函数
def say_more(say_hi_func):
print('More')
say_hi_func()
say_more(say_hi)
# Output:
# More
# Hi
在上面的示例中,我们将函数 Say_hi 作为参数传递给 Say_more 函数,Say_hi 由变量 Say_hi_func 引用。
该函数可以在其他函数中定义
def say_hi():
print('Hi!')
def say_name():
print('Tom')
say_name()
say_hi()
# Output:
# Hi!
# Tom
say_name() # 报错
在上面的代码中,我们在Say_hi()函数中定义了另一个函数Say_name()。 Say_name()仅在Say_hi函数内可见(即其作用域在Say_hi函数内),外包调用Say_hi时会出错。
函数可以返回对其他函数的引用
def say_hi():
print('Hi!')
def say_name():
print('Tom')
return say_name
say_name_func = say_hi()
# 打印Hi!,并返回say_name函数对象
# 并赋值给say_name_func
say_name_func()
# 打印 Tom
在上面的示例中,say_hi 函数返回对内部定义的函数 say_name 的引用。这样,say_name函数也可以在say_hi函数之外使用。
我们之前已经了解了函数,这将有助于我们了解装饰器。
装饰器
装饰器是用于修改函数或类的可调用对象。
可调用对象是可以接受某些参数并返回某些对象的对象。 Python 中的函数和类是可调用对象。
函数装饰器接受一个函数作为参数,包装函数参数,然后返回添加了包装器的函数,即生成一个新函数。
我们看下面的例子:
def decorator_func(some_func):
# define another wrapper function which modifies some_func
def wrapper_func():
print("Wrapper function started")
some_func()
print("Wrapper function ended")
return wrapper_func # Wrapper function add something to the passed function and decorator returns the wrapper function
def say_hello():
print ("Hello")
say_hello = decorator_func(say_hello)
say_hello()
# Output:
# Wrapper function started
# Hello
# Wrapper function ended
在上面的例子中,decorator_func是定义的装饰器函数,它接受some_func作为参数。它定义了一个调用 some_func 的wrapper_func 函数,但也添加了自定义代码。
上面代码中使用装饰器的方法看起来有点复杂。其实,装饰器真正的Python语法是这样的:
装饰器的Python语法
@decorator_func
def say_hi():
print 'Hi!'
@ 一致性是装饰器的语法糖,在定义时使用say_hi函数来避免另一个赋值语句。
上面的语句等价于:
def say_hi():
print 'Hi!'
say_hi = decorator_func(say_hi)
装饰器的顺序
@a
@b
@c
def foo():
print('foo')
# 等同于:
foo = a(b(c(foo)))
带参数函数的装饰器
def decorator_func(some_func):
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
print ("Hi!" + name)
在上面的代码中,Say_hi 函数有一个参数。通常,不同功能的函数可以有不同的类别和不同数量的参数。在编写wrapper_func时,我们不确定参数的名称和数量。我们可以通过 *args 和 **kwargs 引用函数参数。
带参数的装饰器
不仅被装饰的函数可以带参数,装饰器本身也可以带参数。参考下面的例子:
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
简单来说,参数化装饰器就是在无参数装饰器外面嵌套一个参数的函数,并且该函数返回无参数装饰器。
作为装饰器的类
之前我们说过装饰器是可召唤的对象。在Python中,类不仅是函数,而且还是可调用的对象。使用课堂装饰器的优点是灵活性、高内聚性和封装性。通过在类中实现 __call__
方法,当使用 @ 语法糖将装饰器附加到函数时,会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def say_hi():
print('Hi!')
say_hi()
# Output:
# class decorator running
# Hi!
# class decorator ending
functools.wraps
使用装饰器很大程度上重用了代码,但一个缺点是它缺少原始函数的元信息,例如函数的文档字符串、__name__ 和参数列表。考虑以下示例:
def decorator_func(some_func):
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
'''Say hi to somebody'''
print ("Hi!" + name)
print(say_hi.__name__) # Output: wrapper_func
print(say_hi.__doc__) # Output: None
如您所见,Say_hi 函数已被wrapper_func 函数替换,其 __name__ 和 docstring 当然是wrapper_func 函数的那些。
不过不用担心,Python 有 functools.wraps。 Wraps自己也是一名装饰师。它的作用是将原函数的元信息复制到装饰器函数中,使得装饰器函数也具有与原函数相同的元信息。
from functools import wraps
def decorator_func(some_func):
@wraps(func)
def wrapper_func(*args, **kwargs):
print("Wrapper function started")
some_func(*args, **kwargs)
print("Wrapper function ended")
return wrapper_func
@decorator_func
def say_hi(name):
'''Say hi to somebody'''
print ("Hi!" + name)
print(say_hi.__name__) # Output: say_hi
print(say_hi.__doc__) # Output: Say hi to somebody
类的内置装饰器
类属性@property
静态方法@staticmethod
类方法@classmethod
通常情况下,我们首先需要实例化类的对象,然后调用其方法。
如果类方法使用@staticmethod或@classmethod,则可以直接通过classname.methodname()调用,无需实例化。
从使用角度来看,@staticmethod 不需要 self 指向自己的对象,也不需要 cls 参数指向自己的类,就像使用常规函数一样。 @classmethod不需要self参数,但第一个参数必须是指向自己类的cls参数。如果想在@staticmethod中调用该类的一些属性方法,只能使用直接方法classname.propertyname或者classname.methodname。
因为@classmethod包含cls参数,所以可以调用类属性、类方法、实例化对象等。
总结
通过了解Python的函数,我们逐渐了解了装饰器的来龙去脉。装饰器是代码复用的一个很好的工具,可以在编程过程中在适当的场景中使用。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。