Prince Home Stay Follish, Stay Hungry

Python装饰器详解

2019-04-16
Prince Wang

在Python中,装饰器是一种极为优雅的给现有函数增加功能的方式,本篇博客总结一些装饰器的常见使用方法与复杂装饰器使用方法,文末给出一些例子帮助读者理解。

  • 参考博客:https://foofish.net/python-decorator.html

基本装饰器

Python中,装饰器是用于给现有函数增加功能的方法,首先我们假设装饰器需要装饰的函数是这样:

def func(a, b):
    return a + b

现在已有函数func,希望通过decorator增加func的功能,比如在func执行前与执行后都print一段文字,我们可以将装饰器的运行过程理解成以下代码:

# 原来函数这么执行
result = func(a, b)
# 有了装饰器以后这么执行
func = decorator(func)
result = func(a, b)

那么如何写一个装饰器呢,一个最简单的装饰器可以定义如下:

def decorator(func):
    def wrapper(a, b):
        print('before')
        result = func(a, b)
        print('after')
        return result
    return wrapper

这样我们就可以通过func = decorator(func)来调用装饰器来装饰func函数了 装饰器的理解有以下几个要点(很重要!)

  • 装饰器是一个本质上是一个函数(或者带有__call__成员函数的类也可以)
  • 装饰器的输入是一个函数
  • 装饰器的返回值也是一个函数(wrapper)
  • 装饰器的输入函数与他返回的wrapper函数的形参必须一致,这样才能保证装饰以后可以没有错误的调用,比如上面的wrapper的输入参数也是a,b

更加复杂的装饰器

有了以上的要点,我们不难写出装饰器来,以下是几个注意的点:

  • 装饰器既然独立装饰各种函数,那么之前的decoroator里面wrapper的函数的形参是固定的(a,b),因此只能装饰入参为两个的函数,其他函数就无能为力了,为了解决这个问题,我们可以利用*args**kwargs来代替a,b,使装饰器可以装饰任意入参的函数:
def decorator(func):
    def wrapper(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result
    return wrapper
  • 装饰器的语法糖,python中使用@符号来表示装饰器以代替func = decorator(func)这代码,如刚才这个装饰器我们可以写成:
@decorator
def func(a, b):
    return a + b
  • 装饰器比较灵活,其实我们可以这么理解,func = XXX(func),这就是一个装饰器,这里的XXX可以是任何“输入参数是一个函数,返回是和func入参一样的函数”的东西,因此装饰器的实现相当灵活,我们在读带有装饰器的代码的时候,可以把@后面的所有东西拿出来(不管他多复杂),然后带入func = XXX(func)中,这样有助于我们理解复杂装饰器的逻辑。
  • 装饰器可以带参数,这就需要将上面的装饰器再包裹一层,让wrapper在调用func的时候用到装饰器的参数。
  • 装饰器也可以是一个类,和带参数的装饰器很像,不过初始化变成了一个类,而调用变成实现__call__函数。
  • 装饰器还可以是一个类的成员函数,总之是那句话,只要是任何“输入是一个函数对象,输出是一个与输入函数入参一样的函数”的东西,都可以成为一个装饰器。

一些简单的例子

  • 将所有的被装饰函数的输入和输出打印log出来的装饰器:
def decorator(fn):
    def wrapper_func(*args, **kwargs):
        print('input params:', args, kwargs)
        res = fn(*args, **kwargs)
        print('output:', res)
        return res
    return wrapper_func

@decorator
def sum(a, b):
    return a + b

@decorator
def div(a, b):
    return a / b

sum(1, 2)
div(1, 2)

>>> input params: (1, 2) {}
>>> output: 3
>>> input params: (1, 2) {}
>>> output: 0.5
  • 带参数的装饰器,用于输出不同的log,warning,error等等(本质是把上面的装饰器用一个函数再包裹一下,加一个参数):
def logging(mode='warning'):
    def decorator(fn):
        def wrapper_func(*args, **kwargs):
            print('{}: input params:'.format(mode), args, kwargs)
            res = fn(*args, **kwargs)
            print('{}: output:'.format(mode), res)
            return res
        return wrapper_func
    return decorator

@logging(mode='warning')
def sum(a, b):
    return a + b

@logging(mode='warning')
def div(a, b):
    return a / b
 
sum(1, 2)
div(1, 2)

>>> warning: input params: (1, 2) {}
>>> warning: output: 3
>>> error: input params: (1, 2) {}
>>> error: output: 0.5
  • 用类做装饰器
class Logger(object):
    
    def __init__(self, mode='warning'):
        self.mode = mode
        
    def __call__(self, fn):
        def wrapper_func(*args, **kwargs):
            print('{}: input params:'.format(self.mode), args, kwargs)
            res = fn(*args, **kwargs)
            print('{}: output:'.format(self.mode), res)
            return res
        return wrapper_func

@Logger('error')
def sum(a, b):
    return a + b

@Logger('warning')
def div(a, b):
    return a / b
  
sum(1, 2)
div(1, 2)
>>> warning: input params: (1, 2) {}
>>> warning: output: 3
>>> error: input params: (1, 2) {}
>>> error: output: 0.5
  • 多重装饰器

装饰器可以不止一个,我们可以通过多个@堆叠的方式来多个嵌套多个装饰器,功能类似fucn = d2(d1(func)),具体语法如下:

# 离函数越近的装饰器越先调用
@Logger('decorator2')
@Logger('decorator1')
def sum(a, b):
    return a + b

>>> decorator2: input params: (1, 2) {}
>>> decorator1: input params: (1, 2) {}
>>> decorator1: output: 3
>>> decorator2: output: 3

Python静态成员函数的实现

我们注意到Python的静态函数前面都要加一个@staticmethod,前面加了@符号,很容易让人想到可能是使用装饰器来实现的,我们来探究一下。

要理解@staticmethod我们先要知道Python中的静态成员函数和普通成员函数的区别,下面是一个例子:

class Point(object):
    
    static_var = 1
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def func(*args):
        print(args)
    
    @staticmethod
    def static_func(*args):
        print(args, Point.static_var)
        
p = Point(1, 2)
p.func(1, 2, 3) # (<__main__.Point object at 0x7f61dccfe6a0>, 1, 2, 3)
p.static_func(1, 2, 3) # (1, 2, 3) 1

现在我们知道了,Python中普通的成员函数在调用的时候会在传入的参数的前面加入一个自身引用,也就是self,而如果在方法上面加入了@staticmethod标志,在调用的时候则不会加入,也就是说,Python使用装饰器“过滤“掉了参数中的第一个,因此,我们可以实现一个简易版的@mystaticmethod装饰器来实现这个功能,下面是代码:

def mystaticmethod(func):
    
    def wrap(*args, **kwargs):
        return func(*args[1:], **kwargs)
    
    return wrap

class Point(object):
    
    static_var = 1
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def func(*args):
        print(args)
    
    @staticmethod
    def static_func(*args):
        print(args, Point.static_var)
    
    @mystaticmethod
    def my_static_func(*args):
        print(args)
        print(args, Point.static_var)
        
p = Point(1, 2)
p.func(1, 2, 3) # (<__main__.Point object at 0x7f61dcd0a588>, 1, 2, 3)
p.static_func(1, 2, 3) # (1, 2, 3) 1
p.my_static_func(1, 2, 3) # (1, 2, 3) 1

效果和自带的一样~DIY完成!


Similar Posts

Comments