python中的三器一闭
python三器一闭:迭代器、生成器、装饰器和闭包
1.迭代器
1.1 什么是迭代
迭代是访问集合元素的一种方式。对list、tuple、str等类型的数据使用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
1.2 什么是可迭代对象
只要是可以通过for...in…的形式进行遍历的,那么这个数据类型就是可以迭代的。
1.3 判断数据是否可迭代
In [50]: from collections.abc import Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [54]: isinstance(mylist, Iterable)
Out[54]: False
In [55]: isinstance(100, Iterable)
Out[55]: False
1.4 什么是迭代器
迭代器是一个可以记住遍历的位置的对象。迭代器对象从第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
1.5 迭代器的本质
我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。
1.6 使用迭代器取数据
from collections.abc import Iterator
nums = [1, 2, 3] # 可迭代对象
nums = iter(nums) # 创建了迭代器
print("nums", isinstance(nums, Iterator)) # 判断是否是迭代器
# 取出迭代器的数据
num1 = next(nums)
print(num1)
num1 = next(nums)
print(num1)
num1 = next(nums)
print(num1)
若迭代的次数超过了可迭代对象的长度, 就会报StopIteration异常
1.7 自定义迭代器
使用__iter__和__next__方法自定义迭代器
只要在类中,定义__iter__方法,那么这个类创建出来的对象一定是可迭代对象
如果类中实现了__iter__方法和__next__方法的对象,就是迭代器。
当我们调用iter()函数提取一个可迭代对象的 迭代器时,实际上会自动调用这个对象的__iter__方法,并且这个方法返回迭代器。
示例:
# 使用迭代器完成学生管理系统
class StuSystem(object):
"""
学生管理系统
"""
def __init__(self):
self.stus = []
self.current_num = 0
def add(self):
"""
添加一个新的学生
:return:
"""
name = input("请输入新学生的姓名:")
tel = input("请输入新学生的手机号:")
address = input("请输入新学生的住址:")
new_stu = dict()
new_stu["name"] = name
new_stu["tel"] = tel
new_stu["address"] = address
self.stus.append(new_stu)
def __iter__(self):
return self
def __next__(self):
if self.current_num < len(self.stus):
ret = self.stus[self.current_num]
self.current_num += 1
return ret
else:
self.current_num = 0
raise StopIteration
# 创建对象
stu = StuSystem()
# 添加信息
stu.add()
stu.add()
stu.add()
stus = [x for x in stu]
print(stus)
总结
- 凡是可作用于for循环的对象都是Iterable 类型;
- 凡是可作用于 next() 函数的对象都是Iterator 类型;
- 集合数据类型如list 、dict、str等是 Iterable但不是Iterator,不过可以通过 iter()函数获得一个Iterator对象。
2.生成器
在python中一边循环一边计算的这种机制,叫做生成器
2.1 创建生产器的方法
1.将列表推导式的[]改为()
2.在函数中使用yield关键字,函数就变成了一个generator
2.2 关键字yield
使用生成器完成斐波那契数列
def fib_generator():
num1 = 1
num2 = 1
while True:
temp_num = num1
num1, num2 = num2, num1+num2
# return temp_num # 方式1代码
yield temp_num
# 方式1代码(方式1不能够生成1,1,2,3,5...斐波那契数列)
# print(fib_generator())
# print(fib_generator())
# print(fib_generator())
# print(fib_generator())
# 方式2代码(可以生成斐波那契数列)
fib = fib_generator()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
函数中只要有yield关键字那么调用函数的时候 就会创建生成器对象
2.3 next和send
next:可以将yield唤醒,会让yield执行暂停取出数据,然后接着进行下面的操作。
send:不但可以取出数据,还可以携带参数。
如果在运行中没有碰到yield则会报错
示例:
def genterator_test():
while True:
print("--1-")
num = yield 100
print("--2--", "num=", num)
g = genterator_test()
print(g.send(None))
print(g.send(11))
print(g.send(22))
总结
- 使用了yield关键字的函数不再是函数,而是生成器
- yield关键字有两点作用:
1.保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
2.将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用。 - 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
- Python3中的生成器可以使用return返回最终运行的返回值
- 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
- 生成器的特点:
1.存储的是生成数据的方式(即算法),而不是存储生成的数据,因此节约内存。
3.装饰器
普通闭包:内部函数将使用的外部变量当做数据来用
将闭包当做装饰器:内部函数将使用的外部变量当做可调用的对象(例如函数)来调用
3.1 装饰器的功能
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
3.2 定义装饰器
def check_login(func):
def inner():
# 验证1
# 验证2
# 验证3
func()
return inner
@check_login
def f1():
print('f1')
3.3 @实现的过程
上例中@check_login内部会执行以下操作:
#步骤1:执行check_login函数
执行check_login函数 ,并将 @check_login 下面的函数作为check_login函数的参数
即:
@check_login
def f1():
等价于如下代码
check_login(f1)
所以,内部就会去执行:
def inner():
#验证 1
#验证 2
#验证 3
f1() # func是参数,此时 func 等于 f1
return inner # 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中
步骤2:f1 = check_login的返回值
将执行完的check_login 函数返回值 赋值 给@check_login下面的函数的函数名f1 即将check_login的返回值再重新赋值给 f1,即:
新f1 = inner
所以,以后业务部门想要执行f1函数时,就会执行新f1函数,在新f1函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者
如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着
def check_login(func):
def inner():
# 验证1
if "admin" != input("请输入用户名:"):
return "用户名不正确"
# 验证2
if "123456" != input("请输入密码:"):
return "密码不正确"
# 验证3
if "7788" != input("请输入手机短信验证码:"):
return "验证码不正确"
func()
return inner
@check_login
def f1():
print('f1')
f1() # 调用f1函数
3.4 使用普通闭包 与 将闭包用作实现装饰器 时,有什么不同
普通闭包:
def who(name):
def do(content):
print("(%s):%s" % (name, content))
return do
zhangsan = who("张三")
lisi = who("李四")
zhangsan("你努力了吗?")
lisi("为啥努力!")
zhangsan("你确定不要努力吗?")
lisi("嗯,确定?")
zhangsan("那可就不要要怪别人努力了啊")
lisi("别人与我何关!")
zhangsan("隔壁那户人家姓xxxx")
lisi("( ⊙ o ⊙ )啊!")
装饰器的例子:
def log(func):
def call():
ret = func()
if ret and isinstance(ret , str):
with open("log.txt", "w") as f:
f.write(ret)
return ret
return call
@log
def print_hello():
return "hello world"
print(print_hello())
小总结:
普通闭包:内部函数将使用的外部变量当做数据来用
将闭包当做装饰器:内部函数将使用的外部变量当做可调用的对象(例如函数)来调用
总结
- 装饰器:能够快速将函数的指向修改,能够在不修改代码的前提下,给函数添加功能的方式
- 装饰器功能:给函数添加功能
- 特点:不修改原函数代码,还能添加功能;只能在原函数运行之前或者之后添加,不能在原函数运行一半时添加
- 实现过程:1. 将原函数的引用当做实参传递到闭包中 2. 修改原函数的指向为闭包中的内部函数
- 装饰器实际上用到了闭包,只不过在给外层函数传递参数时,传递的是需要被装饰的函数引用而已
- 装饰器还用到了引用,即在Python中,a=xx那么无论xx是列表、字典还是对象,一定是a指向它,而不是存储它
4. 闭包
4.1 引用
# 定义函数可以理解为:
# 定义了一个全局变量,其变量名字是函数的名字,即test
# 这个test变量指向了一个代码块,这个代码块是函数
# 其实就是说test保存了一个代码块的地址,即引用
def test():
print("--- in test func----")
test() # 这是调用函数
ret = test # 用另外一个变量 复制了 test这个引用,导致ret变量也指向那个 函数代码块
# 下面输出的2个地址信息是相同的
print(id(ret))
print(id(test))
# 通过引用调用函数
ret()
运行结果:
--- in test func----
140212571149040
140212571149040
--- in test func----
4.2 什么是闭包
就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包
如果一个函数中嵌套了另一个函数并且内部函数用到了外部函数的局部变量或参数
4.3 注意点
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。
4.4 函数、匿名函数、闭包、对象 当做实参时的区别
-
匿名函数能够完成基本的简单功能,,,传递是这个函数的引用 只有功能
-
普通函数能够完成较为复杂的功能,,,传递是这个函数的引用 只有功能
-
闭包能够将较为复杂的功能,,,传递是这个闭包中的函数以及数据,因此传递是功能+数据
-
对象能够完成最为复杂的功能,,,传递是很多数据+很多功能,因此传递是功能+数据
总结
- 闭包定义是在函数内再嵌套函数
- 闭包是可以访问另一个函数局部作用域中变量的函数
- 闭包可以读取另外一个函数内部的变量
- 闭包可以让参数和变量不会被垃圾回收机制回收,始终保持在内存中(而普通的函数调用结束后 会被Python解释器自动释放局部变量)