python进阶--闭包和装饰器

闭包和装饰器

闭包

  1. 定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包

  2. 闭包的条件:

    1. 函数嵌套
    2. 内部函数使用外部函数的变量或者参数
    3. 外部函数返回内部函数的引用(即内部函数的内存地址)
  3. 简单的闭包示例代码:

    def func_out():
        num1 = 10
        def func_inner(num2):
            result = num1 + num2
            print(result)
        return func_inner
    
    inner = func_out()
    inner(50)
    
  4. 闭包的作用:

    • 闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁
  5. 闭包的缺点:由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

  6. 闭包的应用场景:

    • 闭包是在不改变原有函数的调用方法和代码的基础上,对原有的功能进行增加
    • 闭包大大的增加了代码的重用性,一个写好的函数可以供多人调用,而且还不会更改代码的结构
  7. 内部函数如何修改外部函数的变量

    方法:使用nonlocal

    示例代码:

    def fun_out():
        num = 10
        def funn_inner():
            nonlocal num
            num = 100
            print(num)
        print(num)
        funn_inner()
        print(num)
        return funn_inner
    
    inner = fun_out()
    inner()
    

装饰器

  1. 定义:就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数

  2. 装饰器的特点:

    1. 不修改已有函数的的源代码
    2. 不修改已有函数的调用方式
    3. 给已有的函数增加额外的功能
  3. 装饰器的示例代码:

    # 写个闭包
    def fun(func):
        def inner():
            print("登录")
            func()
            print("评论成功")
        return inner
    
    # 被装饰的函数
    @fun  # comment = fun(comment) 就相当于@fun
    def comment():
        print("发表评论")
    # comment = fun(comment)
    comment()
    # 装饰器和闭包的区别: 1.装饰器是一种闭包
    #                   2.装饰器的外部函数的参数是被装饰的函数
    #                   3.其实就是把内部函数赋值给与被装饰函数同名的函数名,然后正常调用被装饰的函数
    

    示例说明:

    • 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器。
    • 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。
    • @fun语法糖是另一种装饰器的写法,和传统的写法是一样的。以后我们经常用到的也会是语法糖的格式
  4. 装饰器的使用场景:

    1. 函数执行时间的统计

      import time
      def make_time(fun):
          def inner():
              start = time.time()
              fun()
              end = time.time()
              print(end - start)
          return inner
      @make_time
      def test():
          for i in range(10000000):
              print(i)
      test()
      
    2. 输出日志文件

  5. 装饰带有参数的函数

    • 示例代码:

      def log(fun):
          def add_lig(num1, num2):  # 因为这里相当于用inner来替代test,所以test有几个参数,inner就应该有几个参数
              print("这是一个加法运算")
              fun(num1, num2)
          return add_lig
      
      
      @log
      def test(num1, num2):
          result = num1 + num2
          print(result)
      
      
      test(10, 20)
      # 结论:内部函数的参数必须与被装饰的函数的参数一致,也要与内部函数调用的被装饰的函数的参数一致
      
  6. 装饰带有返回值的函数

    • 示例代码:

      def log(fun):
          def add_lig(num1, num2):  # 因为这里相当于用inner来替代test,所以test有几个参数,inner就应该有几个参数
              print("这是一个加法运算")
              return fun(num1, num2)
          return add_lig
      @log
      def test(num1, num2):
          result = num1 + num2
          return result
      
      
      rs = test(10, 20)
      print(rs)
      # 结论:如果被装饰的函数有返回值,那么内部函数中就必须有返回值.
      
  7. 通用装饰器

    • 通用装饰器就是被装饰函数是可以传递任意参数的函数,

    • 示例代码:

      def log(fun):
          def inner(num, *args, **kwargs):
              print("正在计算中")
              return fun(num,*args, **kwargs)
          return inner
      
      
      def test(num, *args, **kwargs):
          result = num
          for num in args:
              result += num
          for num in kwargs.values():
              result += num
          return result
      
      
      result = test(10, 20, 30, a=20, b=30)
      print(result)
      # 总结:内部函数,被装饰函数,内部函数调用的函数 他们的参数必须一模一样
      
  1. 多装饰器的使用:

    • 多装饰器就是多个装饰器同时装饰一个函数

    • 代码示例:

      # 使用div标签把内容包裹起来
      def make_div(fun):
          print("--------")
          def inner():
              result = "<div>" + fun() + "</div>"
              return result
          return inner
      
      
      # 使用p标签把内容包裹起来
      def make_p(fun):
          print("+++++")
          def inner():
              result = "<p>" + fun() + "</p>"
              return result
          return inner
      @make_div
      @make_p
      def content():
          return "这是多装饰器的使用代码"
      rs = content()
      print(rs)
      # 总结: 多重装饰器先使用靠近被装饰函数的装饰器,类似于穿衣服,从下到上,从里到外
      

      代码说明:当你调用content函数的时候,函数会先执行离它最近的那个装饰器make_P里面的代码,也就是U先打印"--------",然后内容变成了<p>这是多装饰器的使用代码</p>,然后会执行make_div里面的代码,打印出+++++,最后打印<div><p>这是多装饰器的使用代码</p></div>

  2. 由前面的学习内容可以知道装饰器的外部函数能接受一个类型为函数的参数。即引用被装饰的函数。

    那么装饰器的外部函数能不能直接传递其他的参数呢?

    答案是:不能。

    代码如下:

    def decorator(fn, flag):
        def inner(num1, num2):
            if flag == "+":
                print("--正在努力加法计算--")
            elif flag == "-":
                print("--正在努力减法计算--")
            result = fn(num1, num2)
            return result
        return inner
    
    
    @decorator('+')
    def add(a, b):
        result = a + b
        return result
    
    result = add(1, 3)
    print(result)
    
    

    这里运行之后,会报错。那要怎样才能实现装饰器里面传递参数呢?

    在装饰器的最前面再加上一个函数,这个函数里面可以传参。代码如下:

    # 带有参数的装饰器,要在装饰器前面加上一个函数,结束的时候返回一个函数
    def log(n):
        def test(fun):
            def inner(num1, num2):
                if n == "+":
                    print("正在计算加法。")
                    return fun(num1, num2)
                if n == "-":
                    print("正在计算减法")
                    return fun(num1, num2)
            return inner
        return test
    
    
    
    def sum_num(num1,num2):
        result = num1 + num2
        return result
    test = log("+")
    sum_num = test(sum_num)
    @log("-")
    def sub_num(num1,num2):
        result = num1 - num2
        return result
    
    rs = sum_num(20, 10)
    print(rs)
    rs1 = sub_num(20, 10)
    print(rs1)
    
    

    这样就能在装饰器里面传递其他的参数了。

    注意点:

    1. 调用装饰器后@后要调用最外部的那个函数,不再是之前的那个函数了。
    2. 一定要返回之前的那个装饰器函数的引用。

今天的分享就到这里了,如果哪里出错了,还请在评论区指正出来,我会积极改正。并且欢迎探讨python有关的知识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 一、函数的特殊用法 1.变量可以指向函数 代码演示:#abs------>absolute#abs()是一个系统的...
    郑元吉阅读 280评论 0 1
  • 一、函数的特殊用法 1.变量可以指向函数 代码演示:#abs------>absolute#abs()是一个系统的...
    hollow_02f9阅读 661评论 0 0
  • 一、迭代器 1.可迭代对象 可迭代对象【实体】:可以直接作用于for循环的实体【Iterable】可以直接作用于f...
    hollow_02f9阅读 512评论 0 0
  • 【Day 7】 夜起如厕,忽见窗前立一白须老者,惊。 老者忙道,“靓女莫慌,吾乃记忆老君也。见你日日忧思记忆力退化...
    我是凯茜阅读 279评论 2 4
  • 你是否有这样的困扰:你和其他人(同学、同事、朋友、家人)明明执行了同样的行为,得到的结果却截然不同。同坐在一个教室...
    孜姿不倦阅读 5,956评论 2 5