主页 从一个错误谈Python装饰器 正文

从一个错误谈Python装饰器

作者:bcm   更新时间:2019年11月23日 09:58   


1. 装饰器来源

装饰器的应用是广泛的,当我们想改变一个函数的某些行为,但又不想改变函数的逻辑。或者想做一个通用的逻辑适用于某一类型的函数。例如:python 内置装饰器@property、@classmethod、@staticmethod。

假设有个函数test。

示例一:

def test():
    print('我是 test')

现在有个小需求,我想在test执行的时候,打印它的name

那我们可以这样写:

示例二:

def test():
    print('我是 test')

def my_wrap(func):
    print(func.__name__)
    func()

my_wrap(test)

# test
# 我是test

但实际上我们的需求往往不是这样的,我取了个test的名称是为了做一些特定的事情,现在却搞出一个新的函数来,污染掉了前面的逻辑。

所以,我们又这么做了:

示例三:

def test():
    print('我是 test')


def my_wrap(func):
    print(func.__name__)
    return func


test = my_wrap(test)

test()

# test
# 我是 test

这个时候,在my_wrap当中不再直接调用func(),而是返回一个 func的函数,再把my_warp 赋值给test,这时候调用test就不会再出现之前的尴尬局面了。

然而在python当中有个@的语法糖,就是常见的装饰器了,那么,现在通过@符号来改造 示例三

示例四:

def my_wrap(func):
    print(func.__name__)
    return func

@my_wrap
def test():
    print('我是 test')

test()

# test
# 我是 test

这时候不难发现 @my_wrap 的过程相当于 test = my_wrap(test) , 没错,@符号的作用,就是把当前函数当成参数传递给了 装饰器。

2. 装饰器入门

上面已经了解到了python装饰器的基本用法,但是还不够,此时还要继续学习。接下来改造下示例四。我们去除my_wrap函数的返回值,同时打印下test

def my_wrap(func):
    print(func.__name__)

@my_wrap
def test():
    print('我是 test')

print(test)

# test
# None

这个时候可以发现,装饰器的内容已经打印,说明装饰器函数一旦被装饰,是立即执行的。但是test里面的内容没执行,因为我们并没有打印test 。接下来执行下这个函数

def my_wrap(func):
    print(func.__name__)

@my_wrap
def test():
    print('我是 test')

test()

# test
# TypeError: 'NoneType' object is not callable

什么情况,我们再回顾下没加@符号的样子。

test = my_wrap(test)
test()

这时候发现猫腻了,我们的test实际就是装饰器函数,只不过@符号把装饰器的内容执行完之后,再次赋值给了test。我们的my_wrap函数返回是个NoneType函数,然而NoneType并不是一个可执行对象,所以报错了。

那么,为了让test()优雅的执行下去,只能采用示例四的写法,我们手动把test 返回回去。也就是说,每次在写装饰器的时候,为了让我们本来的函数(test)顺利执行,必须讲装饰器返回一个可执行的函数。这个函数一般是我们传入的函数,或者是我们经过加工的函数。

3. 带参数的函数

如示例四的情况,我们存在参数的时候,是不受影响的。

def my_wrap(func):
    print(func.__name__)
    return func

@my_wrap
def test(x):
    print('我是 test: %s' % (x,))

test(1)

#test
#我是 test: 1

但是上边的这个装饰器并不完美,我如果想在func执行完成之后再打印一句话,怎么办呢?

我们很容易想到下面这种方式。

def my_wrap(func):
    print(func.__name__)
    func(x)
    print('我执行完了')

@my_wrap
def test(x):
    print('我是 test: %s' % (x,))

test(1)

#TypeError: test() missing 1 required positional argument: 'x'

问题出现了,在my_wrap里面的 x 从哪里传进去呢????

这个时候,就再回忆下这个过程。依旧是 test = my_wrap(test) ,这时候就要再次看下我们的装饰器入门了。一旦加了装饰器,我们的test实际就是装饰器函数。那么这个 x 必须由装饰器来接收,也就是我们的装饰器必须是个可执行的函数了。

def my_wrap(func):
    def innder_test(x):
        print(func.__name__)
        func(x)
        print('我执行完了')
    return innder_test

@my_wrap
def test(x):
    print('我是 test: %s' % (x,))

test(1)

# test
# 我是 test: 1
# 我执行完了

我们再分解下这个函数 。 首先还是 test = my_wrap(test) ,相当于  test  == inner_test 。 所以这个过程就不难理解了,总之装饰器替代了我们原来的函数。

4. 带参数的装饰器

假设装饰器每次执行,都要接收一个参数,就像flask的路由那样,每次都要把router传进去,我们现在也传入一个router。但当前的方法似乎没有办法,因为my_wrap似乎只能接收一个func的参数,那么我们能不能把,my_wrap 外面再包装一个函数,把my_wrap返回给它。那么我们的test 还是拿到my_wrap。

如下所示:

def outer_test(router):
    def my_wrap(func):
        def innder_test(x):
            print(func.__name__)
            func(x)
            print('我执行完了', router)

        return innder_test

    return my_wrap

@outer_test('/index')
def test(x):
    print('我是 test: %s' % (x,))

test(1)

这个执行相当于 test = outer_test('index')(test),到这里是不是很容易理解了。

但是有没有想过,我如果没有加外层的outer_test(),而是直接传递了参数会怎么样。

def my_wrap(router, func):
    def innder_test(x):
        print(func.__name__)
        func(x)
        print('我执行完了', router)

    return innder_test


@my_wrap('/index')
def test(x):
    print('我是 test: %s' % (x,))

test(1)

# TypeError: my_wrap() missing 1 required positional argument: 'func'

正如所料,这种情况下,test并没有被传递给 my_wrap 装饰器。 我们本想是这样一个过程 test = my_wrap('/index',test),很不幸test是一个在@装饰器之后出现,我们直接书写 @my_wrap(‘/index’,test)是一种不被允许的行为。但是怎能就此罢休呢。我们通过上边的例子已经知道,我们直接写@my_wrap的时候,test是会被传递进去的,而装饰器加完()之后,它真正的装饰器实际在内部被返回。

我们先让这个装饰器函数不报错,那就书写为下面的形式。

def my_wrap(router, func=None):
    print(router, func)

    def innder_test(x):
        func(x)

    return innder_test
# /index None
# TypeError: 'NoneType' object is not callable

这块出现了熟悉的报错,func确实是None,那么能否在此处判断一些,如果func是None,就重新返回一个新的函数,来替代my_warp。

def my_wrap(router, func=None):
    print(router)

    def inner_wrap(func):
        def save_x(x):
            func(x)
        return save_x

    if func is None:
        return inner_wrap

@my_wrap('/index')
def test(x):
    print('我是 test: %s' % (x,))

test(1)

# /index
# 我是 test: 1

这种写法和写在包装器外边包装器函数大同小异,但内部的还可以使用偏函数来替代。

from functools import partial

def my_wrap(router, func=None):
    if func is None:
        return partial(my_wrap, router)

    print(router)

    def inner_test(x):
        func(x)

    return inner_test

@my_wrap('/index')
def test(x):
    print('我是 test: %s' % (x,))

test(1)

# /index
# 我是 test: 1

结束语

装饰器是不是很简单呢


目录

关于站点

变饼档 变饼档 头条号 微信号

@变饼档

网站备案/许可证号 陕ICP备17000772号-1

联系邮箱 942242856@qq.com


@技术支持

前端 layui,jquery,vue

后端 django,django-simpleui