python笔记系列-day20: python中的函数装饰器

目录

函数装饰器

    函数名

闭包

   类比Java的内部类

闭包的效果

一个闭包例子

高阶函数

函数装饰器

定义函数装饰器

偷梁换柱

函数装饰器执行时点

如何功能增强

被修饰函数存在参数情况

那么是否必须要保证参数一致呢?

结合函数的参数收集

函数装饰器的参数


通过本篇的例子让你掌握函数装饰器

函数装饰器

  这个用法很独特,Java中有注解可以标记一些信息,可以通过注解来做一个切点然后使用AOP 进行类的加强

   类的加强也是一种装饰

   那么Python呢,和Javascript一样,函数都被当成了对象,尤其在javascript中函数就是一等公民,通过函数创建了很多‘伪类’

   函数是带有代码执行体的对象,因此将函数作为一个对象理解是很重要的,

   既然函数是对象,那么函数可以有自己的属性,函数对象可以如同其他对象一样传入到其他函数中

                                 也可以如同其他对象被另外一个函数返回

                                 可以如同局部变量一样 在一个函数的内部定义一个函数如同定义一个对象一样

    函数名

          函数名也是一个变量,它引用了函数对象而已

          既然函数名是一个变量,当然可以让这个变量引用其他的值,

           这里说的引用和对象 如果学过java的话就很容易理解了

          编程语言都有一个内存分配问题 ,   栈内存 和 堆内存 ,这个可不是Java独有的

          我们定义的对象在堆内存中存储

          但是存储对象的变量只是一个引用,在栈内存中存储,存放了 堆内存中对象的地址

          在python这种 弱类型语言中 , 这个对象的引用变量是可以引用其他对象的

         所以完全可以将一个函数变量引用为一个字符串对象

         下面的程序中演示了这个函数名作为一个变量的实质:

          

 

闭包

     如同 Javascript一样,函数可以进行嵌套

    嵌套函数就会引来一个东西--闭包

    闭包: 存在外部函数 和 内部函数, 并且内部函数访问了 外部函数的局部变量

    如果从外界获取到了这个 内部函数,并且执行这个函数的时候是仍然可以访问外部函数的局部变量的

    闭包 = 代码块+ 外部环境变量

   类比Java的内部类

    在java中存在一个类似的概念,内部类,可以在一个类内部定义一个类

    内部类可以访问宿主类对象的成员变量和方法

    问什么呢?

    本质上是内部类的构造方法里面会多一个外部类当前对象 this 的一个引用

   而且呢当前内部类外围的环境要被保存起来如同 函数调用中断后保存调用现场一样

   因此你可以认为定义的内部概念会将外部 参数作为构造方法的参数传入给了内部概念

   因此它才可以访问这些东西。

闭包的效果

   内部函数访问了外部函数的变量

  我们知道函数执行完之后会释放局部变量的

  但是我们在外部获取了内部函数对象的引用之后,执行这个内部函数时候又会访问外部函数的那个变量

  但是外部函数已经执行完毕了啊,如何访问呢,其实就是定义内部函数的时候给传入进去的(系统隐藏罢了)

  

一个闭包例子

  在python中写闭包的时候要注意了

  内部函数如果对外部环境的变量赋值的时候一定要注意赋值的位置必须要在 访问的位置之前

  因为一旦赋值之后,表示重新定义了一个变量了,这个时候就不是外围环境的那个变量了

如果你先访问外围环境的变量,后面进行赋值,那么就认为你重新定义了一个变量

此时 就会发生变量名的遮蔽了,那么前面的访问语句就会报错了

(从这节开始尝尝  pycharm 编写的便利)

 

高阶函数

  函数可以作为参数传递给一个函数

  函数可以作为另一个函数的返回值

 

   有了这些概念,我们来看看函数装饰器

 

函数装饰器

 如果理解这个代理模式或者是装饰模式的话,这个函数装饰器就很好理解了

既然是装饰,那么就是函数功能的加强

当然如果你装饰过了头,就可能改变了原来的函数性质了

 

定义函数装饰器

前面说过函数内部是可以定义函数的,宿主函数和内置函数

而且函数可以当做参数传给另外一个函数(可以进行传入函数的回调)

函数装饰器本质是一个高阶函数,这个高阶函数有一个函数入参

然后这个高阶函数的返回值就是 装饰器的结果

要使用函数装饰器的时候 , 只需要  @高阶函数

在定义一个函数的时候加上

装饰结果: 被修饰的函数最终就是 函数装饰器的那个函数返回值,已经不是自己了

例如下面的例子,使用函数装饰器把被修饰函数变成了 字符串

下面例子中: 

1.定义高阶函数 outter 充当函数装饰器

   因此它必须有一个函数入参

2.使用函数装饰器 @outter 来修饰函数  inner

3.被修饰的函数inner 已经变质了,函数变量引用的是  函数装饰器返回的结果

 

   上面的例子就是修饰过了头,改变了函数的引用了

    但是往往修饰的时候,我们其实想要的是函数功能的增强

    也就是被修饰的函数仍然是一个函数可以继续使用,只是执行的功能发生改变了

 

偷梁换柱

  上面说了被修饰函数被装饰成了函数修饰器的返回值

既然还是想要这个函数效果,那么就让函数装饰器返回一个函数对象即可

但是此时被修饰的函数,那个函数名实际引用的是函数装饰器返回的那个函数了不是自己了。

函数装饰器执行时点

 既然函数装饰器也是函数,那么函数装饰器什么时候执行呢?

 》》 当被修饰函数定义完之后执行

例如下面的例子:

可以看到当函数装饰器定义完毕,修饰被定义函数的时候,

一旦被修饰函数定义完毕,那么函数装饰器就已经执行了

此时我们定义的被修饰函数 fb 已经不是自己了

而是函数装饰器的内置函数了,被偷梁换柱了

 

如何功能增强

 被换了肯定不好,不然被修饰函数定义有何用。

那么如何进行功能的增强呢?

答案就在那个 函数装饰器的入参函数上。

其实呢当使用 @高阶函数  来修饰另外一个函数的时候4

被修饰的函数就会当成参数传递给了高阶函数了

那么在高阶函数中进行入参函数的回调就可以找回自我(直接返回函数入参那么我还是我)

或者是再函数装饰器内置函数中做回调,这样可以进行功能的加强

例如下面的例子

 

被修饰函数存在参数情况

  如果被修饰的函数有参数怎么办?

 既然函数装饰器的内置函数就是我们的修饰结果

当被修饰函数有参数的时候,只要给那个内置函数放同样的参数即可,这样就可以接受到了

为什么呢?

别忘了函数是对象,这个对象和其他对象不同之处就是它有这个函数体和入参

所以入参也是对象的一部分,既然被修饰的函数作为了参数对象给了高阶函数,那么高阶函数就可以获取到

这个入参函数的属性了,因此可以将参数传递给它的内置函数的

例如下面的例子

 

那么是否必须要保证参数一致呢?

不是。

但是必须要保证被修饰函数的参数都能传染到 而且函数装饰器的内置函数参数调用时候都能有值

因为当你调用被修饰函数的时候本质上是调用的 函数装饰器的内置函数的

如果你按照函数装饰器的内置函数的参数列表来传入值的话肯定不会有问题的

例如下面的 3 个例子

1.高阶函数的内置函数的参数比修饰函数多一个 报错

例如 高阶函数的内置函数的参数比修饰函数多一个 , 报错了,因为调用的内置函数是 3个参数

使用被修饰函数只给了 两个参数

 

2.将上面函数装饰器内置函数的第三个参数弄成默认参数是可以的,说明可以不一致的

  因为是默认参数,所以不会有错

  

 

3. 函数装饰器的内置函数参数比被修饰函数少,报错

   内置函数只有一个参数,但是给了两个

 

一般情况下只要保证 内置函数的参数列表和被修饰函数的一样就可以了

这样你就可以按照 被修饰的函数的参数来使用 那个背后的 内置函数了

 

结合函数的参数收集

  前面介绍过函数的参数是可以进行收集的

  因此函数装饰器的内部函数完全可以使用 参数收集

  

 

 

函数装饰器的参数

通过上面的例子可以看出这个函数装饰器和Java中的注解使用起来很相似的

注解是可以有值的,那么函数装饰器呢?

答案也是肯定的

上面的函数装饰器就是 高阶函数 , 有一个入参  func

那么函数装饰器的参数是不是就是给 这个入参列表里面再加入一个 参数呢?

不是的

这里面玩的就是一个函数,所有的东西都应该往函数上靠拢

既然函数装饰器是一个高阶函数

现在想给函数装饰器传入参数,那么不妨构造一个函数,这个函数的返回值是我们的高阶函数即可

 

@函数(参数)

def 被修饰函数(参数)

@后面如果是一个高阶函数的时候,就会把被修饰函数传入给高阶函数

  如果后面不是,那么就执行后面这个函数看结果是不是高阶函数,如果是也是传入

所以呢,我们可以借助构造函数来完成函数装饰器的参数

例如,我们做一个日志打印的函数装饰器,它有一个参数标记是否输出到文件

          如果为True 的时候输出到文件,如果为False 的时候就输出到控制台

 

详细代码

函数装饰器有参数.py

import os
def log(isFout=False):
    def fa(func):
        print('exec fa()...')
        def f(*args):
            print('>>>>>(a=%s,b=%s)' % (args[0], args[1]))
            r = func(args[0], args[1])
            if isFout:
                f = open('add.log','a')
                print('计算 %s + %s 的值, 结果= %s' % (args[0], args[1],r) ,file=f)
                print('计算结果已经输出到文件 %s\\add.log 中了 ' % os.getcwd())
                f.close()
            else:
                print('计算 %s + %s 的值, 结果= %s' % (args[0], args[1], r))
        return f
    return fa

@log()
def add(x,y):
    return x + y

@log(True)
def add1(x,y):
    return x + y

print('add(2,3)...')
add(2,3)
print('add(5,7)...')
add1(5,7)

运行结果

 

 

 

 

 

 

 

   

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值