Python装饰器详解

装饰器的作用与原理

相当于java中的注解

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

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

我们来看一个简单应用

def log(fn):
    def wrap_function():
        print('准备运行函数fn')
        fn()
        print('函数fn运行结束')
    return wrap_function

@log
def fnA():
    print('正在运行fnA....')

fnA()

# 输出:
'''
准备运行函数fn
正在运行fnA....
函数fn运行结束
'''

与下面的代码是等价的

def log(fn):
    def wrap_function():
		    print('准备运行函数fn')
		    fn()
		    print('函数fn运行结束')
		return wrap_function

def 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 

@log
def 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_function

def deB(fn):
    def wrap_function(*args, **kwargs):
        print('deB原函数入参...', args)
        print('deB准备运行函数fn')
        fn(*args, **kwargs)
        print('deB函数fn运行结束')

    return wrap_function

def deC(fn):
    def wrap_function(*args, **kwargs):
        print('deC原函数入参...', args)
        print('deC准备运行函数fn')
        fn(*args, **kwargs)
        print('deC函数fn运行结束')

    return wrap_function

@deA
@deB
@deC
def fnA(a, b):
    print('正在运行fnA...', a, b)

fnA('入参A', '入参B')

# 输出:
'''
deA原函数入参... ('入参A', '入参B')
deA准备运行函数fn
deB原函数入参... ('入参A', '入参B')
deB准备运行函数fn
deC原函数入参... ('入参A', '入参B')
deC准备运行函数fn
正在运行fnA... 入参A 入参B
deC函数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._var1

b = 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

@logger
def 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

@logger
def 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()

@Logger
def fnA():
    print('fnA正在运行。。。')

fnA()

# 输出
'''
console print
fnA正在运行。。。
'''

带参数的类装饰器

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 Logger
fnA正在运行。。。
Logger wrapper after...
'''

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

from functools import wraps
class 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 wrapper

class 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.com
fnA正在运行。。。
Email wrapper after...
'''

相关文章

干货 | 一文讲清楚Python之装饰器

概念介绍装饰器(decorator),又称“装饰函数”,即一种返回值也是函数的函数,可以称之为“函数的函数”。其目的是在不对现有函数进行修改的情况下,实现额外的功能。最基本的理念来自于一种被称为“装饰...

Python修饰器,终极解释来了!

修饰器在本质上就是一个函数,可接收其他函数作为参数。接下来,让我们看看如何自定义一个修饰器#这个修饰器可在被修饰函数之前被执行 def xiushi1(func): print("定义...

Python 装饰器:如何改进代码的功能和可读性

什么是装饰器?用装饰器来扩展另一个函数的功能。通过使用装饰器,可以添加其他功能。为了定义装饰器,需要定义一个函数,该函数具有一个调用 wrapper 的函数和一个调用 func, 的参数,该参数是我们...

Python的装饰器还是不会?来看看这篇文章(建议收藏)

点赞、收藏、加关注,下次找我不迷路哈喽,各位 Python 爱好者们!今天咱们要一起来攻克 Python 里一个超实用却又让不少新手犯迷糊的知识点 —— 装饰器。别担心,全程用大白话,搭配例子,还有...

python装饰器详解

装饰器储备知识1:*args和**kwargs储备知识2:名称空间与作用域储备知识3:函数对象储备知识4:函数的嵌套定义储备知识 5:闭包函数喜欢编程的小伙伴可以加小编的Q群1026782549,进群...

Python中自带的三个装饰器

说到装饰器,就不得不说python自带的三个装饰器:1、@property 将某函数,做为属性使用@property 修饰,就是将方法,变成一个属性来使用。2、@classmethod 修饰类的方式带...