Python 装饰器 的产生 演进 让你彻底明白装饰器原理

13 篇文章 0 订阅
1 篇文章 0 订阅

想要给一个已经写好的函数“加点料”,怎么办?

装饰器的产生

最初大约在2000年左右,程序员是这样做的:

#定义好的原始函数
def say():
    print( "hello world!")

#很久很久以前的做法
def debug(func):#接收函数地址
    def wrapper():#替换原函数的新函数地址
        print ("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

say = debug(say)#这就是装饰器的原始模样吧
say()#其实是wrapper()
输出:
[DEBUG]: enter say()#加的料
hello world!

其实就是一个加料的新函数wrapper替换原函数

装饰器的演进1:只是加入@语法糖

def debug(func):#加了@语法糖!
    def wrapper():
        print ("@语法糖 [DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper

@debug
def say():
    print ("hello world!")

say()#其实是wrapper()
输入:
@语法糖 [DEBUG]: enter say()
hello world!
@debug								
def say():
    print ("hello world!")
//等价于:
def say():
    print ("hello world!")
say = debug(say)

装饰器的演进2:函数加入固定参数

def debug(func):#把函数接过来返回包装后的函数!wrapper才是替换的函数
    def wrapper(n):  # 指定一致的参数
        print( "[DEBUG]: enter {}()".format(func.__name__))
        return func(n)
    return wrapper  # 返回包装过的新函数

@debug #可以理解为:用debug中的wapper替换下面的函数
def say(something):
    print (something)

say('hello world!')#其实是wrapper('hello world!')
输出:
[DEBUG]: enter say()
hello world!

装饰器的演进3:函数加入任意参数

def debug(func):#接收被替换函数
    def wrapper(*args, **kwargs):  # 新函数指定任意参数
        print ("[DEBUG]: enter {}()".format(func.__name__))
        print('*args:',end=' ')
        print(*args)
        print('kwargs:',end=' ')
        print(kwargs)
        print('------------------------------------------')
        return func(*args, **kwargs)
    return wrapper  

@debug
def say(*args, **kwargs):
    print(*args,end=' ')
    print(kwargs)

say(1,2,3,'hello world!',key1 = 1,key2 = 2)#实际上是wrapper(1,2,3..)/或者是:debug(say)(1,2,3..)
输出:
[DEBUG]: enter say()
*args: 1 2 3 hello world!
kwargs: {'key1': 1, 'key2': 2}
------------------------------------------
1 2 3 hello world! {'key1': 1, 'key2': 2}

装饰器的演进4:装饰器加入参数

def logging(level):#最外层用于接受第一个参数
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print ("[{}]: enter function {}()".format(level,func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    
    return wrapper

@logging(level='INFO')
def say(something):
    print( "say {}!".format(something))

@logging(level='DEBUG')
def do(something):
    print ("do {}!".format(something))

say('hello')
do("my work")

# 如果没有使用@语法,say('hello') == logging(level='INFO')(say)('hello')

装饰器的演进5:类装饰器

**
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在Python中一般callable对象都是函数,但也有例外。
只要某个对象重载了__call__()方法,那么这个对象就是callable的。
**

class Test():
    def __call__(self):
        print ('call me!')

t = Test()
t()  # call me 想像不像函数

回到装饰器上的概念上来,装饰器要求接受一个callable对象,
并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。
我们可以让类的构造函数__init__()接受一个函数,
然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果

class logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print ("[DEBUG]: enter function {func}()".format(
            func=self.func.__name__))
        return self.func(*args, **kwargs)
@logging
def say(something):
    print ("say {}!".format(something))

say('hello world!')
输出:
[DEBUG]: enter function say()
say hello world!!

‘’’
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。
那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。
然后在重载__call__方法是就需要接受一个函数并返回一个函数。’’’

class logging(object):
    def __init__(self, level='INFO'):
        self.level = level
        
    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print( "[{level}]: enter function {func}()".format(
                level=self.level,
                func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logging(level='INFO')
def say(something):
    print( "say {}!".format(something))

say('hello world!')
输出:
[INFO]: enter function say()
say hello world!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值