今日主要内容

  • 闭包
  • 装饰器初识
  • 标准装饰器

一、闭包

(一)什么是闭包

  1. 闭包:内层函数调用外层函数的变量就是闭包(不能是全局变量)

    def func1():
    a = 10
    def func2():
    print(a) # 内层函数调用外层函数的变量,这就是一个闭包 func1()
  2. 检测闭包的方法

    • 函数名.__closure__
    • 若返回对象地址就是一个闭包,返回None就不是一个闭包
    • 注意:.__closure__检测的是函数名,判断这个函数是否是闭包
    def func1():
    a = 10
    def func2():
    print(a) # 调用外层变量a,是闭包
    print(func2.__closure__) # 判断func2是否是闭包 func1() 运行结果:
    (<cell at 0x00000230F21874F8: int object at 0x0000000063258190>,)
    def func1():
    a = 10
    def func2():
    a = 10
    print(a) # 使用自身函数变量a,不是闭包
    print(func2.__closure__) func1() 运行结果:
    None
  3. 如何在全局空间中调用内部函数

    • 外层函数返回内层函数的函数名(内存地址),就可在全局空间中调用内部函数
    def func1():
    a = 10
    def func2():
    print(a)
    return func2 # 返回内层函数的函数名 f = func1() # 此时f就是func2
    f() # 调用内层函数func2
    print(f.__closure__) 运行结果:
    10
    (<cell at 0x00000280A87574F8: int object at 0x0000000063258190>,)

(二)闭包的作用

  1. 保护数据安全
  • 说白话一点,如果数据放在全局变量中(顶格写代码),数据的安全性很低,因为所有人都可以无意间修改它,想解决这个问题我们可以把数据放到函数中,只要不调用这个函数,我的数据就是安全的。但是有个问题,每次执行函数完,解释器就会自动清空此函数开辟的局部空间中所有内容(包括数据开辟的空间),所以每次调用完函数,我的数据就没了,此时就需要利用到了闭包。闭包可以在内层函数调用外层函数中的变量,而且内层函数如果调用了外层函数中的变量,那这个变量将不会消亡,将会常驻内存。所以,我们只需要在全局空间中调用这个闭包的内层函数,就可以使用我的数据了,而且数据的安全性也提高了。
  1. 将变量常驻内存,供后续代码使用

    def outer():
    lst = [1,2,3,4,5]
    def inner():
    return lst
    return inner f = outer()
    f_lst = f()
    print(f_lst) 运行结果:
    [1,2,3,4,5]

(三)闭包的应用

  • 防止数据被误修改
  • 装饰器(与闭包格式相同)

二、装饰器初识

(一)软件开发的六大原则(了解)

  1. 开闭原则(Open Close Principle)——装饰器依据
  2. 里氏代换原则(Liskov Substitution Principle)
  3. 依赖倒转原则(Dependence Inversion Principle)
  4. 接口隔离原则(Interface Segregation Principle)
  5. 迪米特法则(最少知道原则)(Demeter Principle)
  6. 合成复用原则(Composite Reuse Principle)

(二)装饰器依据——开闭原则

  • 开放封闭原则:

    • 对功能扩展开放
    • 对源码修改封闭

(三)装饰器引入

  • 相信大多数人都玩LOL,我们模拟一次游戏过程:

    • 向平常一样的流程,十连跪...
    def play_lol():
    print("登陆游戏")
    print("开始排位...")
    print("游戏中...")
    print("失败...")
    print("结束游戏") play_lol() 运行结果:
    登陆游戏
    开始排位...
    游戏中...
    Virtory
    结束游戏
    • 受不了了,开一个外挂吧,此时我的函数需要扩展,在前后添加开启关闭外挂的功能
    def play_lol():
    print("开启外挂!") # 添加开启外挂功能
    print("登陆游戏")
    print("开始排位...")
    print("游戏中...")
    print("胜利!!!")
    print("结束游戏")
    print("关闭外挂!") # 添加关闭外挂功能 play_lol() 运行结果:
    开启外挂!
    登陆游戏
    开始排位...
    游戏中...
    胜利!!!
    结束游戏
    关闭外挂!
    • 但此时,违背了开闭原则,对源代码进行了修改。想一个方法,对源代码进行前后包装,不改变源码
    def play_lol():
    print("登陆游戏")
    print("开始排位...")
    print("游戏中...")
    print("胜利!!!")
    print("结束游戏") def new_play(): # 将上面代码前后包装成了一个新的函数,没有改变源码
    print("开启外挂!")
    play_lol()
    print("关闭外挂!") new_play() 运行结果:
    开启外挂!
    登陆游戏
    开始排位...
    游戏中...
    胜利!!!
    结束游戏
    关闭外挂!
    • 功能实现了,而且还没有改变源码,但是有一个问题,我们之前访问调用的是play_lol这个函数,但此时我们访问调用的是new_play()这个函数,相当于改变了调用,还是违背了开闭原则,没有达到扩展的效果,此时我们就需要对这段代码稍作变化
    def play_lol():
    print("登陆游戏")
    print("开始排位...")
    print("游戏中...")
    print("胜利!!!")
    print("结束游戏") def wrapper(fn): # 装饰器雏形
    def inner():
    print("开启外挂!")
    fn()
    print("关闭外挂!")
    return inner func = wrapper(play_lol) # 调用装饰器函数将我基础函数传入进去包装
    play_lol = func # 返回的是包装函数,将包装函数重命名成我原来的函数
    play_lol() # 调用此函数 运行结果:
    开启外挂!
    登陆游戏
    开始排位...
    游戏中...
    胜利!!!
    结束游戏
    关闭外挂!
    • 上述代码就引出了装饰器的雏形,刨析一下:

      • 装饰器雏形:与闭包的格式相同,两层函数构成:

        • 内层函数就是我的包装函数,将扩展的功能和原函数包在一起组成一个函数
        • 外层函数的作用就是给内层函数传参用的,传入的是我原函数的函数名,在内层调用
      • 装饰器的返回值 return inner:装饰器的返回值是内层函数的函数名,真正进行包装扩展的是内层函数
      • 将返回值重命名成原函数名:将返回值重命名成原来的函数名,其实就是把真正作用的包装函数重命名成原来的函数名,所以就解决了调用新函数的问题,真正遵循了开闭原则,再次调用原来的函数其实真正运行的是装饰器内部的inner函数
    def wrapper(fn):    # 装饰器雏形
    def inner():
    print("开启外挂!")
    fn()
    print("关闭外挂!")
    return inner func = wrapper(play_lol) # 调用装饰器函数将我基础函数传入进去包装
    play_lol = func # 返回的是包装函数,将包赚函数重命名成我原来的函数
    play_lol() # 调用此函数
    • 利用语法糖装饰

      • 使用装饰器的两行代码可以转换成语法糖
      func = wrapper(play_lol)  # 调用装饰器函数将我基础函数传入进去包装
      play_lol = func # 返回的是包装函数,将包赚函数重命名成我原来的函数
      • 语法糖
      @wrapper  # 语法糖
      def play_lol():
      print("登陆游戏")
      print("开始排位...")
      print("游戏中...")
      print("胜利!!!")
      print("结束游戏")
    • 此时,装饰器的雏形就出来了

  • 装饰器雏形

    def wrapper(fn):
    def inner():
    """扩展功能"""
    fn()
    """扩展功能"""
    return inner @wrapper
    def func():
    pass func()
  • 游戏模拟继续进行

    • 就算开挂,我们也得选完英雄,才能进入游戏,所以我们给基础函数传个参数,但是我们真正执行的是装饰器内部的inner包装函数,所以也要给inner传入参数
    def wrapper(fn):    # 装饰器雏形
    def inner(hero): # 套到装饰器中内层包装函数参数
    print("开启外挂!")
    fn(hero) # 基础函数参数
    print("关闭外挂!")
    return inner @wrapper
    def play_lol(hero): # 基础函数参数
    print("登陆游戏")
    print("开始排位...")
    print(f"选择英雄:{hero}")
    print("游戏中...")
    print("胜利!!!")
    print("结束游戏") play_lol("盖伦") 运行结果:
    开启外挂!
    登陆游戏
    开始排位...
    选择英雄:盖伦 # 传入的参数
    游戏中...
    胜利!!!
    结束游戏
    关闭外挂!
    • 虽然开挂了,但是还是会遇到巨坑无敌坑的队友,我们得把他记下来举报他,所以要给基础函数填写返回值,同时给真正执行的inner函数填写返回值
    def wrapper(fn):    # 装饰器雏形
    def inner(hero):
    print("开启外挂!")
    ret = fn(hero) # 接收基础函数的返回值
    print("关闭外挂!")
    return ret # 返回包装后的函数的返回值
    return inner @wrapper
    def play_lol(hero): # 基础函数参数
    print("登陆游戏")
    print("开始排位...")
    print(f"选择英雄:{hero}")
    print("游戏中...")
    print("胜利!!!")
    print("结束游戏")
    return "坑比队友:xxx" # 基础函数返回值 print(play_lol("盖伦")) 运行结果:
    开启外挂!
    登陆游戏
    开始排位...
    选择英雄:盖伦
    游戏中...
    胜利!!!
    结束游戏
    关闭外挂!
    坑比队友:xxx # 返回值
    • 到此,我们装饰器标准模式也就出来了
  • 装饰器标准模式(非常重要)

    def wrapper(fn):
    def inner(*args, **kwargs):
    """扩展功能"""
    ret = fn(*args, **kwargs)
    """扩展功能"""
    return ret
    return inner @wrapper
    def func():
    pass func()

Python基础(十三)的更多相关文章

  1. Python基础(十三) 为什么说python多线程没有真正实现多现程

    Python中的多线程没有真正实现多现程! 为什么这么说,我们了解一个概念,全局解释器锁(GIL). Python代码的执行由Python虚拟机(解释器)来控制. Python在设计之初就考虑要在主循 ...

  2. python基础(十三) cmd命令调用

    python cmd命令调用 关于python调用cmd命令: 主要介绍两种方式: 1.python的OS模块. OS模块调用CMD命令有两种方式:os.popen(),os.system(). 都是 ...

  3. python 基础(十三) time模块

    日期和时间 一.time模块 import time 时间戳: 时间戳是指格林威治时间1970年1月1日0时0分0秒至现在的秒数   s(秒).ms(毫秒).μs(微秒).ns(纳秒), ​ 其中:1 ...

  4. python基础十三之内置函数

    内置函数 操作字符串代码 eval和exec print(eval('1+2')) # 简单的计算 有返回值 exec('for i in range(10):print(i)') # 简单的流程控制 ...

  5. 二十三. Python基础(23)--经典类和新式类

    二十三. Python基础(23)--经典类和新式类 ●知识框架   ●接口类&抽象类的实现 # 接口类&抽象类的实现 #①抛出异常法 class Parent(object):    ...

  6. 十三. Python基础(13)--生成器进阶

    十三. Python基础(13)--生成器进阶 1 ● send()方法 generator.send(value) Resumes the execution, and "sends&qu ...

  7. Python基础学习笔记(十三)异常

    参考资料: 1. <Python基础教程> 2. http://www.runoob.com/python/python-exceptions.html Python用异常对象(excep ...

  8. (Python基础教程之十三)Python中使用httplib2 – HTTP GET和POST示例

    Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...

  9. 第十三章 Python基础篇结束章

    从2019年3月底开始学习Python,4月份开始在CSDN发博客,至今不到半年,老猿认为博客内容中关于Python基础知识的内容已经基本告一段落,本章进入Python基础知识结束章节,对Python ...

  10. Day1 - Python基础1 介绍、基本语法、流程控制

    Python之路,Day1 - Python基础1   本节内容 Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼 ...

随机推荐

  1. LeetCode刷题 - (01)两数之和

    题目描述 给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元 ...

  2. Foxmail管理多个邮箱

    使用Foxmail管理邮箱还是很方便的. 1. 下载Foxmail. 2. 双击,输入想关联的邮箱名称和密码,收取邮件即可. 3. 如果想关联多个账号,可点击Foxmail右上角的菜单栏,选择账户管理 ...

  3. HDU 4280 Island Transport(无向图最大流)

    HDU 4280:http://acm.hdu.edu.cn/showproblem.php?pid=4280 题意: 比较裸的最大流题目,就是这是个无向图,并且比较卡时间. 思路: 是这样的,由于是 ...

  4. 51nod 1376 最长递增子序列的数量(不是dp哦,线段树 +  思维)

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1376 题解:显然这题暴力的方法很容易想到就是以每个数为结尾最 ...

  5. HDU 2561 二小整数

    第二小整数 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Subm ...

  6. 牛客网 Wannafly挑战赛 C 列一列 简单题 (题目有点坑)

    链接:https://www.nowcoder.com/acm/contest/71/C来源:牛客网 题目描述 小W在计算一个数列{An},其中A1=1,A2=2,An+2=An+1+An.尽管他计算 ...

  7. jQuery - 03. each、prevaAll、nextAll、获取属性、修改属性attr/val/text()、jq.height/width、offset()./position()./scrol Left/Top 、事件绑定bind、delegate、on、事件解绑、事件对象、多库共存

    each 方法 $ ( selector).each(function( index,element) {  } );   参数一表示当前元素在所有匹配元素中的索引号 参数二表示当前元素(DOM对象) ...

  8. 012 模块1-turtle库的使用

    目录 一.概述 二.turtle库基本介绍 2.1 turtle库概述 2.2 标准库 2.3 turtle的原(wan)理(fa) 2.4 turtle的魅力 三.turtle绘图窗体布局 3.1 ...

  9. 自己在WEB学习过程中遇到的问题

    问题一: 下面这两段代码差别不大,为何test1结果不同: 代码1: public void doGet(HttpServletRequest request, HttpServletResponse ...

  10. JavaBean的学习

    一.什么是JavaBean JavaBean是一个遵循特定写法的Java类,它通常具有如下特点: 这个Java类必须具有一个无参的构造函数 属性必须私有化. 私有化的属性必须通过public类型的方法 ...