python基础--闭包、装饰器
装饰器的详细使用
(1)小知识点补充
在这里我们先学一个简单的知识点。
li = ['alex', '银角', '女神', 'egon', '太白']
for i in enumerate(li):
    print(i)
for index, name in enumerate(li, 1):
    print((index, name))
for index, name in enumerate(li, 100):  # 起始位置默认是0,可更改
    print((index, name))
'''
输出的结果为:
(0, 'alex')
(1, '银角')
(2, '女神')
(3, 'egon')
(4, '太白')
(1, 'alex')
(2, '银角')
(3, '女神')
(4, 'egon')
(5, '太白')
(100, 'alex')
(101, '银角')
(102, '女神')
(103, 'egon')
(104, '太白')
'''
(2)闭包
下面讲解闭包这个概念是参考博客太白金星。希望大家有不懂的地方可以问我,可以共同讨论学习。
由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包。
给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求。
例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。
比如大众推出了一款新车:小白轿车。
第一天价格为:100000元,平均收盘价:100000元
第二天价格为:110000元,平均收盘价:(100000 + 110000)/2 元
第三天价格为:120000元,平均收盘价:(100000 + 110000 + 120000)/3 元
........
series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))
从上面的例子可以看出,基本上完成了我们的要求,但是这个代码相对来说是不安全的,因为你的这个series列表是一个全局变量,只要是全局作用域的任何地方,都可能对这个列表进行改变。
series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
print(make_averager(100000))
print(make_averager(110000))
series.append(666)  # 如果对数据进行相应改变,那么你的平均收盘价就会出现很大的问题,数据没有安全性。
print(make_averager(120000))
那么怎么办呢?有人说,你把他放在函数中不就行了,这样不就是局部变量了么?数据不就相对安全了么?
def make_averager(new_value):
    series = []
    series.append(new_value)
    total = sum(series)
    return total / len(series)
print(make_averager(100000))  # 100000.0
print(make_averager(110000))  # 110000.0
print(make_averager(120000))  # 120000.0
这样计算的结果是不正确的,那是因为执行函数,会开启一个临时的名称空间,随着函数的结束而消失,所以你每次执行函数的时候,都是重新创建这个列表,那么这怎么做呢?这种情况下,就需要用到我们讲的闭包了,我们用闭包的思想改一下这个代码。
def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
avg = make_averager()
print(avg(100000))
print(avg(110000))
print(avg(120000))
大家仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。那么此时你们有什么问题?
肯定有学生就会问,那么我的make_averager这个函数只是执行了一次,为什么series这个列表没有消失?反而还可以被调用三次呢?这个就是最关键的地方,也是闭包的精华所在。我给大家说一下这个原理,以图为证:
上面被红色方框框起来的区域就是闭包,被蓝色圈起来的那个变量应该是make_averager()函数的局部变量,它应该是随着make_averager()函数的执行结束之后而消失。但是他没有,是因为此区域形成了闭包,series变量就变成了一个叫自由变量的东西,averager函数的作用域会延伸到包含自由变量series的绑定。也就是说,每次我调用avg对应的averager函数 时,都可以引用到这个自用变量series,这个就是闭包。
闭包的定义:
闭包是嵌套在函数中的函数。
闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
如何判断判断闭包?举例让同学回答:
# 例一:
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()
# 例二:
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()
# 例三:
def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)
以上三个例子,最难判断的是第三个,其实第三个也是闭包,如果我们每次去研究代码判断其是不是闭包,有一些不科学,或者过于麻烦了,那么有一些函数的属性是可以获取到此函数是否拥有自由变量的,如果此函数拥有自由变量,那么就可以侧面证明其是否是闭包函数了(了解):
def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
avg = make_averager()
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
当然还有一些参数,仅供了解:
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
# 函数名.__code__.co_varnames 查看函数的局部变量
print(avg.__code__.co_varnames)  # ('new_value', 'total')
# 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。
# (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)
# cell_contents 自由变量具体的值
print(avg.__closure__[0].cell_contents)  # []
闭包的作用:保存局部信息不被销毁,保证数据的安全性(这是一个十分重要的作用)。
闭包的应用:
- 可以保存一些非全局变量但是不易被销毁、改变的数据。
 - 装饰器。
 
(3)装饰器
在使用装饰器之前,我们先开始了解一下开发封闭原则。那么什么是开发封闭原则呢?
# 开放封闭原则
# 开放:对代码的拓展是开放的。更新地图,加新枪,等等
# 封闭:对源码的修改是封闭的。闪躲用q。就是一个功能,一个函数。
# 别人用赤手空拳打你,用机枪扫你,扔雷.....这个功能不会改变。
1.对扩展是开放的
 我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
2.对修改是封闭的
 就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对函数内部进行修改,或者修改了函数的调用方式,很有可能影响其他已经在使用该函数的用户。
所以装饰器最终最完美的定义就是:在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。
那么装饰器是什么呢?
# 什么叫做装饰器
# 装饰器:装饰,装修,房子本来可以住,如果装修,不影响你住
# 而且体验更加,让你的生活中增加了许多的功能,例如看电视,洗澡
# 器:工具
#  装饰器:完全遵循开放封闭原则。
#  装饰器:在不改变原函数的代码以及调用方式的前提下,为其增加新的功能
(4)标准版装饰器
代码优化:语法糖
根据我的学习,我们知道了,如果想要各给一个函数加一个装饰器应该是这样:
def home(name,age):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(name,age)
    print(f'欢迎访问{name}主页')
def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
home = timer(home)
home('太白',18)
如果你想给home加上装饰器,每次执行home之前你要写上一句:home = timer(home)这样你在执行home函数 home('太白',18) 才是真生的添加了额外的功能。但是每次写这一句也是很麻烦。所以,Python给我们提供了一个简化机制,用一个很简单的符号去代替这一句话。
def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函数的执行效率为{end_time-start_time}')
    return inner
@timer  # home = timer(home)
def home(name,age):
    time.sleep(3)  # 模拟一下网络延迟以及代码的效率
    print(name,age)
    print(f'欢迎访问{name}主页')
home('太白',18)
你看此时我调整了一下位置,你要是不把装饰器放在上面,timer是找不到的。home函数如果想要加上装饰器那么你就在home函数上面加上@home,就等同于那句话 home = timer(home)。这么做没有什么特殊意义,就是让其更简单化,比如你在影视片中见过野战军的作战时由于不方便说话,用一些简单的手势代表一些话语,就是这个意思。
至此标准版的装饰器就是这个样子:
def wrapper(func):
    def inner(*args,**kwargs):
        '''执行被装饰函数之前的操作'''
        ret = func(*args,**kwargs)
        '''执行被装饰函数之后的操作'''
        return ret
    return inner
这个就是标准的装饰器,完全符合代码开放封闭原则。这几行代码一定要背过,会用。
python基础--闭包、装饰器的更多相关文章
- python基础—函数装饰器
		
python基础-函数装饰器 1.什么是装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能. 装饰器的返回值是也是一个函数对象. 装饰器经常用于有切 ...
 - 十. Python基础(10)--装饰器
		
十. Python基础(10)--装饰器 1 ● 装饰器 A decorator is a function that take a function as an argument and retur ...
 - Day11  Python基础之装饰器(高级函数)(九)
		
在python中,装饰器.生成器和迭代器是特别重要的高级函数 https://www.cnblogs.com/yuanchenqi/articles/5830025.html 装饰器 1.如果说装 ...
 - python函数闭包-装饰器-03
		
可调用对象 callable() # 可调用的(这个东西加括号可以执行特定的功能,类和函数) 可调用对象即 callable(对象) 返回为 True 的对象 x = 1 print(cal ...
 - [python基础]关于装饰器
		
在面试的时候,被问到装饰器,在用的最多的时候就@classmethod ,@staticmethod,开口胡乱回答想这和C#的static public 关键字是不是一样的,等面试回来一看,哇,原来是 ...
 - 1.16 Python基础知识 - 装饰器初识
		
Python中的装饰器就是函数,作用就是包装其他函数,为他们起到修饰作用.在不修改源代码的情况下,为这些函数额外添加一些功能,像日志记录,性能测试等.一个函数可以使用多个装饰器,产生的结果与装饰器的位 ...
 - 【Python基础】装饰器的解释和用法
		
装饰器的用法比较简单,但是理解装饰器的原理还是比较复杂的,考虑到接下来的爬虫框架中很多用到装饰器的地方,我们先来讲解一下. 函数 我们定义了一个函数,没有什么具体操作,只是返回一个固定值 请注意一下缩 ...
 - 【Python】 闭包&装饰器
		
python中的函数本身就是对象,所以可以作为参数拿来传递.同时其允许函数的层级嵌套定义,使得灵活性大大增加. 闭包 闭包的定义:将函数的语句块与其运行所需要的环境打包到一起,得到的就是闭包对象.比如 ...
 - python基础之 装饰器,内置函数
		
1.闭包回顾 在学习装饰器之前,可以先复习一下什么是闭包? 在嵌套函数内部的函数可以使用外部变量(非全局变量)叫做闭包! def wrapper(): money =10 def inner(num) ...
 - python基础-----函数/装饰器
		
函数 在Python中,定义一个函数要使用def语句,依次写出函数名.括号.括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回. 函数的优点之一是,可以将代码块与主程 ...
 
随机推荐
- HTML5实现DTMF(电话拨号按键信号)解码、编码,代码简单易于移植
			
目录 一.前言 1.1 HTML5实现DTMF的一些动机 1.2 一些有效场景 (1) 10086 (2) 软电话 (3) 小玩具 二.DTMF频率按键对照表 三.DTMF信号解码 得到按键值 3.1 ...
 - 使用docker创建redis容器
			
1.拉取redis镜像
 - python+opencv检测图像清晰度
			
直接上代码,list_jian.txt为待检测图像路径列表 import cv2 import numpy as np import os for path in open("list_ji ...
 - C# 获取枚举的描述Description
			
方法类: public static class EnumExtensions { #region Enum /// <summary> /// 获取枚举变量值的 Description ...
 - 在 XUnit 中使用依赖注入
			
在 XUnit 中使用依赖注入 Intro 之前写过一篇 xunit 的依赖注入相关的文章,但是实际使用起来不是那么方便 今天介绍一个基于xunit和微软依赖注入框架的"真正"的依 ...
 - 小书MybatisPlus第1篇-整合SpringBoot快速开始增删改查
			
Mybatis Plus官方文档已经很完善了,为什么还要写一个这样的文档? 官方文档注重知识结构的整理,没有注重学习者的学习顺序 官方文档中的案例注重API描述,比较适合学会mybatis plus之 ...
 - JavaScript中数组去重的几种方法
			
JavaScript中数组去重的几种方法 正常情况下,数据去重的工作一般都是由后端同事来完成的,但是前端也要掌握好处理数据的能力,万一去重的工作交给我们大前端处理,我们也不能怂呀.现在我总结了一些去重 ...
 - 2020阿里巴巴官方最新Redis开发规范!
			
本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明. 键值设计 命令使用 客户端使用 相关工具 通过本文的介绍可以减少使用Redis过程带来的问题. 一.键值设计 1.key名设计 ...
 - web页面弹出遮罩层,通过js或css禁止蒙层底部页面跟随滚动
			
场景概述 弹窗是一种常见的交互方式,而蒙层是弹窗必不可少的元素,用于隔断页面与弹窗区块,暂时阻断页面的交互.但是,在蒙层元素中滑动的时候,滑到内容的尽头时,再继续滑动,蒙层底部的页面会开始滚动,显然这 ...
 - selenium 怎么查找定位鼠标移上去显示,移开鼠标就消失的内容
			
场景:鼠标移动到一级菜单上二级菜单才显示,移开鼠标二级菜单就消失,如何查找定位二级菜单 操作: 1.打开F12,点击sources 2.鼠标移动到一级菜单“工单管理” 3.按下键盘“Ctrl+\”,暂 ...