深入了解和应用Python 装饰器 @decorator
作者:呆木木人儿 发布时间:2024-01-02 09:20:05
Python的装饰器(decorator)是一个很棒的机制,也是熟练运用Python的必杀技之一。装饰器,顾名思义,就是用来装饰的,它装饰的是一个函数,保持被装饰函数的原有功能,再装饰上(添油加醋)一些其它功能,并返回带有新增功能的函数对象,所以装饰器本质上是一个返回函数对象的函数(确切的说,装饰器应该是可调用对象,除了函数,类也可以作为装饰器)。
在编程过程中,我们经常遇到这样的场景:登录校验,权限校验,日志记录等,这些功能代码在各个环节都可能需要,但又十分雷同,通过装饰器来抽象、剥离这部分代码可以很好解决这类场景。
装饰器是什么?
要理解Python的装饰器,首先我们先理解一下Python的函数对象。我们知道,在Python里一切都是对象,函数也不例外,函数是第一类对象(first-class objects),它可以赋值给变量,也可以作为list的元素,还可以作为参数传递给其它函数。
函数可以被变量引用
定义一个简单的函数:
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_hi函数外部也可以使用say_name函数了。
前面我们理解了函数,这有助于我们接下来弄明白装饰器。
装饰器(Decorator)
装饰器是可调用对象(callable objects),它用来修改函数或类。
可调用对象就是可以接受某些参数并返回某些对象的对象。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作为参数。它定义了一个wrapper_func函数,该函数调用了some_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
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__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,就可以不需要实例化,直接类名.方法名()来调用。
从使用上来看,@staticmethod不需要指代自身对象的self或指代自身类的cls参数,就跟使用普通函数一样。
@classmethod不需要self参数,但第一个参数必须是指代自身类的cls参数。如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名,或类名.方法名的方式。
而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等。
总结
通过认识Python的函数,我们逐步弄清了装饰器的来龙去脉。装饰器是代码复用的好工具,在编程过程中可以在适当的场景用多多使用。
来源:https://my.oschina.net/JUANererer/blog/3021215
猜你喜欢
- 1. Cookie 介绍HTTP 协议是无状态的。因此,若不借助其他手段,远程的服务器就无法知道以前和客户端做了哪些通信。Cookie 就是
- 前言学会向程序中添加必要的注释,也是很重要的。注释不仅可以用来解释程序某些部分的作用和功能(用自然语言描述代码的功能),在必要时,还可以将代
- # coding:utf-8import urllibimport urllib2import cookielibfrom bs4 impo
- 本文实例讲述了python实现简单ftp客户端的方法。分享给大家供大家参考。具体实现方法如下:#!/usr/bin/python# -*-
- 1.创建一个项目django-admin.py startproject HelloWorld2.进入HelloWorld项目,在manag
- AIML全名为Artificial Intelligence Markup Language(人工智能标记语言),是一种创建自然语言软件代理
- 在《多线程与同步》中介绍了多线程及存在的问题,而通过使用多进程而非线程可有效地绕过全局解释器锁。 因此,通过multiprocessing模
- XmlDocume
- MS SQL Server中文版的预设日期datetime格式是yyyy-mm-dd hh:mm:ss.mmm 长短日期格式 --短日期格式
- PHP使用缓存即时输出内容(output buffering)的方法。分享给大家供大家参考。具体如下:$buffer = ini_get(&
- 目录支持多种类型的过滤避免多个范围查询优化排序理解索引概念最简单的方式是通过一个案例来进行,以下就是这样的一个案例。假设我们需要设计一个在线
- 有时候,我们需要将TensorFlow的模型导出为单个文件(同时包含模型架构定义与权重),方便在其他地方使用(如在c++中部署网络)。利用t
- 挑钻石第二弹seaborn是matplotlib的补充包,提供了一系列高颜值的figure,并且集成了多种在线数据集,通过sns.load_
- 前言孙悟空在花果山称王的时候,特意去了一趟东海,在那里淘到了如意金箍棒。因为身为一个山大王,怎么能没有一件趁手的兵器呢?作为程序员的我们也一
- 一、python魔法方法Python的魔法方法会在特定的情况下自动调用,且他们的方法名通常被双下划线包裹,之前我们学习的构造函数和析构函数就
- 本文为大家分享了python爬取酷狗音乐排行榜的具体代码,供大家参考,具体内容如下#coding=utf-8from pymongo imp
- requests是常用的请求库,不管是写爬虫脚本,还是测试接口返回数据等。都是很简单常用的工具。这里就记录一下如何用requests发送js
- 这篇文章主要介绍了python paramiko远程服务器终端操作过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参
- 大家都知道 在Python 中可以用如下方式表示正负无穷:float("inf") # 正无穷float("-
- 1.关系模型:用二维表格结构表示实体集,外键表示实体间联系的数据模型称为关系模型。关系模型是由若干个关系模式组成的集合。2.关系模式:关系模