分享

Python装饰器详解

 网摘文苑 2023-02-01 发布于新疆

装饰器的作用与原理

相当于java中的注解

我们可以在不入侵一个函数的情况下,在这个函数前和函数后,执行一些预设的功能。通常我们会把函数中都会用到的某一类功能(如:鉴权、日志、参数校验等)封装成一个装饰器,这样代码更简洁,语义也更清晰。

在python中用@ 装饰器函数,简化了上述设计模式的使用过程

我们来看一个简单应用

def log(fn): def wrap_function(): print('准备运行函数fn') fn() print('函数fn运行结束') return wrap_function@logdef fnA(): print('正在运行fnA....')fnA()# 输出:'''准备运行函数fn正在运行fnA....函数fn运行结束'''

与下面的代码是等价的

def log(fn):    def wrap_function():		    print('准备运行函数fn')		    fn()		    print('函数fn运行结束')		return wrap_functiondef fnA():    print('正在运行fnA....')b = log(fnA)b()


1. log() 作为装饰器函数,封装了通用的业务逻辑,去装饰 fnA()
2. @log 相当于简化了装饰器函数的赋值步骤:log(fnA),代码阅读时的业务逻辑更简洁。

使用扩展

  • 在装饰器函数中 return wrap_function,返回是必须的吗? 从上述的代码中,我们看到:装饰器函数要被赋值给一个变量,并最终被执行的。所以装饰器函数需要返回一个可调用对象callable。所以不返回,返回整数字符串等,程序都是会报错的。 可以返回一个不相干的函数,程序不会报错。当然这样不推荐。
  • 在装饰函数中,如何调用原函数的入参?
def log(fn): def wrap_function(a): print('原函数入参...', a) print('准备运行函数fn') fn(a) print('函数fn运行结束') return wrap_function @log def fnA(a): print('正在运行fnA...', a) fnA('入参A') # 输出: ''' 原函数入参... 入参A 准备运行函数fn 正在运行fnA... 入参A 函数fn运行结束 '''
  • 接受任意数量入参
def log(fn): 	def wrap_function(*args,**kwargs):   	print('原函数入参...', args) 		print('准备运行函数fn')		fn(*args,**kwargs) 		print('函数fn运行结束') 	return wrap_function @logdef fnA(a, b): 	print('正在运行fnA...', a, b) fnA('入参A', '入参B') # 输出: ''' 原函数入参... ('入参A', '入参B') 准备运行函数fn 正在运行fnA... 入参A 入参B 函数fn运行结束 '''

函数装饰器嵌套执行顺序

先由上到下执行before,执行完原函数后,再由下往上执行After函数

def deA(fn): def wrap_function(*args, **kwargs): print('deA原函数入参...', args) print('deA准备运行函数fn') fn(*args, **kwargs) print('deA函数fn运行结束') return wrap_functiondef deB(fn): def wrap_function(*args, **kwargs): print('deB原函数入参...', args) print('deB准备运行函数fn') fn(*args, **kwargs) print('deB函数fn运行结束') return wrap_functiondef deC(fn): def wrap_function(*args, **kwargs): print('deC原函数入参...', args) print('deC准备运行函数fn') fn(*args, **kwargs) print('deC函数fn运行结束') return wrap_function@deA@deB@deCdef fnA(a, b): print('正在运行fnA...', a, b)fnA('入参A', '入参B')# 输出:'''deA原函数入参... ('入参A', '入参B')deA准备运行函数fndeB原函数入参... ('入参A', '入参B')deB准备运行函数fndeC原函数入参... ('入参A', '入参B')deC准备运行函数fn正在运行fnA... 入参A 入参BdeC函数fn运行结束deB函数fn运行结束deA函数fn运行结束'''

带参数的装饰器

在上述基本的装饰器的基础上,在外面套一层接收参数的函数,来实现装饰器带参数功能

def logger(msg=None):	  def out_wrapper(fn):	      def wrap_function(*args, **kwargs):	          print('装饰器的参数:', msg)	          print('准备运行函数fn')	          fn(*args, **kwargs)	          print('函数fn运行结束')	      return wrap_function	  return out_wrapper@logger(msg='hi')def fnA(a):		print('正在运行fnA....', a)fnA('入参A')# 输出:'''装饰器的参数: hi准备运行函数fn正在运行fnA.... 入参A函数fn运行结束'''

Python常用的内置装饰器

@staticmethod、 @classmethod、 @property、@abstractmethod

  • 静态方法 @staticmethod
    将函数转成静态方法
class B: @staticmethod def add(a, b): return a + b B.add(10, 5) # 15
  • 类方法 @classmethod
    将函数转成类方法
class B:   @classmethod 	def add(cls):   	print('This is class method')B.add() # This is class method
  • 属性 @property
    将函数转成只读属性,可以防止被外部修改
class B: def __init__(self): self._var1 = 3 self.var2 = 5 @property def var1(self): return self._var1b = B() b.var1 b.var1 = 10 # 尝试给其赋值,会报错
  • 抽象方法 @abstractmethod
    实现抽象类的特性,含有@abstractmethod修饰的父类不能实例化,子类必须实现所有被@abstractmethod装饰的方法
from abc import ABC, abstractmethod class F(ABC):   @abstractmethod 	def info(self):   	print('info') 	@abstractmethod 	def f(self):   	print('f') class S(F): 	def info(self):   	print('S - info') f = F() # 报错:父类不能被实例化 s = S() # 报错,子类没有实现父类所有抽象方法

@wraps()语法糖

由于Python的装饰器是通过闭包实现的,所以在装饰函数中,获取导的属性并非原函数,这就不是很完美

def logger(fn): def wrap_function(*args, **kwargs): ''' 这是装饰器函数 ''' print('装饰器中。。。。') return fn(*args, **kwargs) return wrap_function@loggerdef fnA(): ''' fnA中的打印 ''' print('正在运行fnA....')print(fnA.__name__, fnA.__doc__)# 输出:'''wrap_function 这是装饰器函数'''

加上@wraps() 后,修饰内部函数,保证这个内部函数带有传进来这个函数的属性

from functools import wraps    def logger(fn):    @wraps(fn)    def wrap_function(*args, **kwargs):        '''        这是装饰器函数        '''        print('装饰器中。。。。')        return fn(*args, **kwargs)    return wrap_function@loggerdef fnA():    '''    fnA中的打印    '''    print('正在运行fnA....')print(fnA.__name__, fnA.__doc__)# 输出:'''fnA         fnA中的打印'''

类装饰器

通过 __call__ 将类变成调用对象

class Logger: def __init__(self, func): self.func = func def print_console(self): print('console print') def __call__(self, *args, **kwargs): self.print_console() self.func()@Loggerdef fnA(): print('fnA正在运行。。。')fnA()# 输出'''console printfnA正在运行。。。'''

带参数的类装饰器

class Logger:    def __init__(self, prefix):        self.prefix = prefix    def print_console(self):        print('console print', self.prefix)    def notify(self):        self.print_console()    def __call__(self, func):        def wrapper(*args, ** kwargs):            print(self.prefix, 'wrapper before...')						self.notify()            ret = func(*args, ** kwargs)            print(self.prefix, 'wrapper after...')            return ret        return wrapper@Logger(prefix='Logger')def fnA():    print('fnA正在运行。。。')fnA()# 输出'''Logger wrapper before...console print LoggerfnA正在运行。。。Logger wrapper after...'''

有了类装饰器,就可以使用继承了

from functools import wrapsclass Logger: def __init__(self, prefix='logger'): self.prefix = prefix def print_console(self): print('console print', self.prefix) def notify(self): self.print_console() def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print(self.prefix, 'wrapper before...') self.notify() ret = func(*args, **kwargs) print(self.prefix, 'wrapper after...') return ret return wrapperclass EmailLogger(Logger): def __init__(self, prefix='Email', email='abc@test.com'): Logger.__init__(self, prefix) self.email = email def print_email(self): print('email print:', self.email) def notify(self): self.print_email()@EmailLogger(email='test@test.com')def fnA(): print('fnA正在运行。。。')fnA()# 输出:'''Email wrapper before...email print: test@test.comfnA正在运行。。。Email wrapper after...'''

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多