python语言基础十


使用装饰器需要注意的地方 : 


装饰器其实【 本质 】 : 源于【闭包的函数】,这个闭包函数 【将一个函数作为参数传入】,然后 【返回一个替代版的函数】 。

两个主要的概念  【闭包】 、 【替换】 。

一、关于闭包 :
    闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(Function Closures),是引用了自由变量的函数。
    这个【被引用的自由变量】将【和这个函数一同存在】,【即使已经离开了创造它的环境也不例外】。
    所以,有另一种说法认为【闭包】是【由函数】和【与其相关的引用环境】【组合而成的实体】,
    这种组合使用一种映射关系将函数的自由变量(在 local 使用但是在 enclosing scope 使用的变量)与函数相关联。


二、 在定义装饰器的时候,装饰器名字对于的函数需要接受一个参数(一般都是一个函数), 
    在装饰器的内层函数里面需要对外部函数进行调用(内层函数不能接受外部的这个函数作为参数)


# deco 函数接收另一个函数作为参数
def deco(func):
    print('我是deco的头')

    # 在 deco 函数内部定义了另外一个函数 wrappend,(wrappend可以访问 deco 函数里面的数据, 但是不能直接修改)
    def wrappend():                            
        print("我是wrappend的头")
        print("我是wrappend的屁股")

        # 在 wrappend 里面访问 外部函数的数据 func, 调用这个函数,并且把他的结果作为返回值
        return func() + 1

    print('我是deco的屁股')

    # 
    return wrappend

def foo():
    print("我要被装饰器装饰")
    return 1

print(foo())
print('割-----------------割')

print(deco(foo)())
print('割-----------------割')
print(type(deco(foo)))                    # 输出 <class 'function'> , 证明deco 返回的是一个函数

输出如下 : 

我要被装饰器装饰
1
割-----------------割
我是deco的头
我是deco的屁股
我是wrappend的头
我是wrappend的屁股
我要被装饰器装饰
2
割-----------------割
我是deco的头
我是deco的屁股
<class 'function'>

解析 : 

首先定义一个函数 deco , 在deco 函数内部再定义一个 wrappend 函数, 此时 deco 函数内部, wrappend 函数外部,
就是 wrappend 的【闭包范围】, wrappend 会记录到环境内的关系,同时根据 LEGB 搜索原则找寻参数。 因此再 return 的
时候可以到 func 函数。
【注】: 此处的 deco 函数的主要作用在于, 对传入的 func 做一定处理后, 返回一个新的函数wrappend 作为替代,
随后定义一个 foo 函数作为传入的参数, 通过输出语句可以看到, 经过 deco 函数装饰的 foo 函数在不改动内部函数的前提下, 
完成了对函数行为的改变, 这样的一个 deco 函数, 就是装饰器的本质起源。


三、 语法糖

    前面介绍的通过装饰器本质的方法使用装饰器依旧存在一个麻烦需要处理,也就是当需要对所有的被装饰函数都进行相同的
    装饰时, 需要在每个调用的位置都调用一次装饰函数, 那么这样无疑增加了代码的修改难度。

    为此便出现了 语法糖 @ , 利用语法糖可以很方便的装饰一个函数, 只需要在被装饰的函数之上使用 @deco 便
    可以将函数 deco 装饰到被装饰的函数 foo 上,而且当再次调用 foo 时, 会首先调用一次 foo=deco(foo), 
    自动完成对函数的装饰 。


def deco(func):
    print('我是deco的头')

    def wrappend():
        print("我是wrappend的头")
        print("我是wrappend的屁股")
        return func() + 1

    print('我是deco的屁股')

    return wrappend

@deco
def foo():
    print("我要被装饰器装饰")
    return 1
print(foo())

同时, 语法糖也是可以叠加的, 调用的顺序与声明的顺序是相反的, 即自下而上。

@deco
@deco
def foo():
    print("我要被装饰器装饰")
    return 1
print(foo())


四、装饰器传入参数

    装饰器的参数传入主要有两种, 
    一种是能够装饰需要传入参数的函数,
    第二种是装饰器自身参数。

    首先介绍如何使装饰器装饰带参数传入的函数, 最直接的方式是定义返回函数 wrappend时, 
    将其定义成与被装饰函数 func 相同的形参结构, 这样返回的 wrappend 函数便能够接受 func 的参数传入。
    利用装饰器函数和语法糖分别进行验证吗可以得到期望的结果。

def deco(func):
    print('我是deco的头')

    def wrappend(a, b):
        print("我是wrappend的头")
        print("我是wrappend的屁股")
        return func(a, b) + 1

    print('我是deco的屁股')

    return wrappend

@deco
def foo(a, b):
    print("我要被装饰器装饰")
    return a + b

此时 foo = deco(foo()) = wrappend
所以 foo(1, 2) = wrappend(1, 2) = a + b + 1
print(foo(1, 2))


但是上面的方法有局限性, 当我们不知道被装饰的函数需要传入多少参数的时候, 便无法定义内部的返回函数 wrappend 。
同时,当被装饰函数的输入不同时, 装饰器的通用性便被破坏。为此, 可以利用 运算符 * 和 ** 来对任意参数进行元组或
字典的解包, 从而完成一个通用的装饰器。 


def deco(func):
    print('我是deco的头')

    def wrappend(*args, **kwargs):
        print("我是wrappend的头")
        print("我是wrappend的屁股")
        return func(*args, **kwargs) + 1

    print('我是deco的屁股')

    return wrappend


@deco
def foo(a, b, c):
    print("我要被装饰器装饰")
    return a + b + c


print(foo(1, 2, c=10))


同样的, 对于装饰器, 也可以传入参数, 可以利用传入的参数完成诸如装饰器开关的操作(设置为True或False来确定
返回 wrappend 还是返回 原来的foo函数)。要实现这个功能, 则需要在装饰器的外面再多一层函数。
如下的 decora 函数实质上是起到返回装饰器函数 decor 的作用,语法糖修饰的 @decora(3) 实际上等价于 @decor,
因为 decora(3) 返回的是一个函数 decor 。 同时, 在此处也体现了函数必闭包的特性, 
即在内层函数中记录了闭包环境中的参数 c 的值。


def decora(c):
    def deco(func):
        print('我是deco的头')

        def wrappend(*args, **kwargs):
            print("我是wrappend的头")
            print("我是wrappend的屁股")
            return func(*args, **kwargs) + c

        print('我是deco的屁股')

        return wrappend
    return deco


@decora(3)
def foo(a, b, d):
    print("我要被装饰器装饰")
    return a + b + d


print(foo(1, 2, d=10))

--------------------------------------


有一点要特别注意 : 
我们写好一个装饰器函数, 然后用它来装饰另外一个函数的时候, 我们的装饰器函数就已经被调用了, 
我们被装饰的函数的函数名 也已经指向了 装饰器函数所返回的 wrappend 函数了!!!

所以啊所以, 当我们在调用被装饰的函数的时候, 实际上调用的是 wrappend 函数!!, 
然后再在 wrappend 函数里面调用我们之前定义的被装饰器装饰的函数, 我们的装饰器函数并不会执行。

如下 :

def deco(func):
    print('我是deco的头')

    def wrappend(a, b):
        print("我是wrappend的头")
        print("我是wrappend的屁股")
        return func(a, b) + 1

    print('我是deco的屁股')

    return wrappend

@deco
def foo(a, b):
    print("我要被装饰器装饰")
    return a + b


print('-'*50)

print(foo(1, 5))
print('-'*50)

print(foo(1, 5))

输出 : 
我是deco的头
我是deco的屁股
--------------------------------------------------
我是wrappend的头
我是wrappend的屁股
我要被装饰器装饰
7
--------------------------------------------------
我是wrappend的头
我是wrappend的屁股
我要被装饰器装饰
7


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值