python三器一闭
前言:python三器一闭i 包含 迭代器、生成器、装饰器,闭包
迭代器
- 概念:迭代器是一种可以记录可迭代对象遍历的位置,迭代器从第一个元素开始访问,直到访问所有元素访问结束,迭代器只会往前,不会后退的访问
- 可迭代对象: 是一种可以通过for..in 循环的对象,称之为可迭代对象,通常有 字典、列表、字符串,元祖
如何判断一个数据类型是否是可以迭代的
# 可以导入模块 from collections.abc import Iterable
# 然后通过isinstance 来判断出 Iterable 的实例,正确返回True
# Iterator 是判断这个对象是否是迭代器 Iterable 是判断一个对象是否是可迭代的
from collections.abc import Iterable, Iterator
print(isinstance([], Iterable)) # 列表是可以迭代的
print(isinstance({}, Iterable)) # 字典是可以迭代的
print(isinstance("abc", Iterable)) # 字符串是可以迭代的
print(isinstance(100, Iterable)) # 数字是不能迭代的
nums = [11, 33, 22, 55, 66]
print(type(nums))
nums_iter = iter(nums) # 获取可迭代对象中的迭代器
print(type(nums_iter))
# Iterator 是判断这个对象是否是迭代器 Iterable 是判断一个对象是否是可迭代的
print("num is iterator ? {}".format(isinstance(nums, Iterator))) # False
print("num_iter is iterator ?{}".format(isinstance(nums_iter, Iterator))) # True
获取迭代器数据的方式
nums = [11, 33, 22, 55, 66]
print(type(nums))
nums_iter = iter(nums) # 获取可迭代对象中的迭代器
# 获取迭代器中的数据 方式一 使用 next
# 注意使用next获取迭代器的数据时,下标不能越界,否则会抛出异常StopIteration
print(next(nums_iter)) # 11
print(next(nums_iter)) # 22
print(next(nums_iter)) # 33
# 获取迭代器中的数据 方式二 使用 循环,因为它是可迭代的
for x in nums_iter:
print(x)
自定义迭代器类
- 在一个类中有 iter 方法称之为 可迭代对象, 有iter和next方法的称之为迭代器
# 在一个类中 有 __iter__ 方法 这个对象就是可迭代对象,但不是迭代器
# 在一个类中 既实现了 __iter__ 方法 和 __next__ 方法 的对象 既是可迭代对象 也是迭代器
from collections.abc import Iterable
from collections.abc import Iterator
class MyList(object):
"""自定义的一个可迭代对象"""
def __init__(self):
self.items = []
def add(self, val):
self.items.append(val)
def __iter__(self):
return MyIterator()
class MyIterator(object):
"""自定义的供上面可迭代对象使用的一个迭代器"""
def __init__(self):
pass
def __next__(self):
pass
def __iter__(self):
pass
mylist = MyList()
mylist_iter = iter(mylist)
print("mylist是否是可以迭代对象", isinstance(mylist, Iterable)) # True
print("mylist是否是迭代器", isinstance(mylist, Iterator)) # Flase
print("mylist_iter是否是可以迭代对象", isinstance(mylist_iter, Iterable)) # True
print("mylist_iter是否是迭代器", isinstance(mylist_iter, Iterator)) # True
# 可迭代对象不一定是迭代器, 但迭代器第一定是可迭代对象
案例
# 学生管理系统
class Students(object):
"""自定义迭代器"""
def __init__(self):
self.stus = []
# 记录迭代的位置
self.current = 0
def add(self):
"""添加学生信息"""
name = input("请输入学生的姓名:")
phone = input("请输入学生的手机号码")
address = input("请输入学生的地址:")
new_stu = dict()
new_stu["name"] = name
new_stu["phone"] = phone
new_stu["address"] = address
# 将字典添加到列表中
self.stus.append(new_stu)
def __iter__(self):
return self
def __next__(self):
if self.current < len(self.stus):
ret = self.stus[self.current] # 每调用一次,就通过下标访问一个数据
# print("11")
# print(ret)
self.current += 1
return ret
else:
self.current = 0
raise StopIteration
stu_sys = Students()
stu_sys.add()
stu_sys.add()
# stu_sys.add()
# 使用for循环,因为它是迭代器,也是可迭代对象,所以是可迭代的
# for stu in stu_sys:
# print(stu)
# 改列表推导式
x = [x for x in stu_sys]
print(x)
生成器
- 概念:生成器- 一边循环一边生成的机制称之为生成器
- 特点:存储的是生成数据的方式,而不是存储生存的数据,节约内存
创建生成器的方式一
- 将列表推导式中的中括号 改为 大括号[] --->()
# 创建生成器的方式一 将列表推导式中的中括号 改为 大括号
nums = [x for x in range(11)]
print(type(nums)) # <class 'list'>
print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 生成器
nums2 = (x for x in range(5))
print(type(nums2)) # <class 'generator'>
print(nums2) # 返回的是一个生成器对象(<generator object <genexpr> at 0x103289050>),如果想要获取里面的数据的话,可以使用next
# print(next(nums2)) # 0
# print(next(nums2)) # 1
# 也可以通过for循环来获取数据,生成器也是迭代器的一种方式,它自动实现了iter 和 next 方法
# for x in nums2:
# print(x)
创建生成器的方式二
- 在一个函数中 有 yield关键字 的称之为 生成器(generator)
- yield 在一个函数中称之为生成器,并且在函数中执行到了yield的时候会暂停这个函数,并且把返回值返回,然后在继续执行下一次
# 使用生成器完成斐波那契数列
# 斐波那契数列 是从数列中的第三项开始,往后就是每一项的数据等于前两项的之和
def fib(n):
a, b = 1, 0
for _ in range(n):
a, b = b, a + b
yield a
for x in fib(10):
print(x)
# 斐波那契数列
def fib(n):
num1 = 1
num2 = 1
# 记录访问的位置
current = 0
while current <= n:
temp_num = num1
num1, num2 = num2, num1+num2
current += 1
yield temp_num # 此时不能使用return,yield自带iter() next() 方法
# 生产数据,生成器每生产一个都会计算一次
for x in fib(5):
print(x) # 1 1 2 3 5 8
案例
"""
问题:
假如有一个的文件 test.txt 大小约为 10K,之前处理文件的,代码如下所示:
"""
# def get_lines():
# l = []
# with open('test.txt', 'rb') as f:
# for eachline in f:
# l.append(eachline)
# return l
#
#
# def process(temp_data):
# print(temp_data) # 用打印来模拟处理过程
#
#
# if __name__ == '__main__':
# for e in get_lines():
# process(e.decode("utf-8")) # 处理每一行数据
"""
现在要处理一个大小为 10G 的文件,但是内存只有 4G,如果在只修改 get_lines 函数而其他代
码保持不变的情况下,应该如何实现?需要考虑的问题都有哪些?
"""
# 使用生成器
def get_list():
with open("test.txt", "r") as f:
while True:
data = f.read(1000 * 1000) # 每次读取1兆数据
if data:
yield data
else:
return
if __name__ == '__main__':
for x in get_list():
print(x)
总结
# 生成器是一种一边循环一边生成的机制称之为生成器。
# 在一个函数中有 yield 关键字的称之为 生成器
# yield 关键字 的特点 1 能够保存程序的执行状态,并且在函数中执行到了yield的时候会暂停这个函数,
# 并且把返回值返回,然后在继续执行下一次
# 2 能够节省了内存的空间地址。使用yield的好处在于它不会一次性将所有数据计算计算,而是取一次然后就计算一个
# 这样节省了空间和计算的时间
# 生成器的主要一个特点就是,生成器是一种存储生成数据的方式,而不是存储生存的数据。
# return 和 yield 的区别
# 两者都是 返回程序的运行结果
# 不同的是,return 在返回结果后程序会立刻结束
# yield 在程序执行完结果后不会立刻结束,而是等待下一次执行,并且能被记录最后一次计算的数据结果。
闭包
-
概念:在一个函数中嵌套了另外一个函数,并且函数内部引用了外部函数的形参或局部变量的称之为闭包
引入闭包
def make_print(num1): # 外部函数 def inter(num2): # 内部函数 print(num1 + num2) # 300 return inter ret = make_print(100) # 该100 存入 外部函数中 ret(200) # 该200是传入内部函数中
-
闭包中能够修改外部函数中的局部变量,闭包中可以使用 nonlocal 修改变量
def demo_out(): m = 100 # 局部变量 def inter(): # 内部函数修改外部函数 使用 nonlocal nonlocal m m = 300 print("3", m) # 300 print("1", m) # 100 inter() # 返回内部函数中的引用 print("2", m) # 300 demo_out()
案例一
# 下面应用案例是理解闭包的经典题目,模拟了一个人站在原点,然后向X、Y轴进行移动,每次移动后及时打印当前的位置
def create():
# 外部函数中定义一个局部变量
pos = [0, 0] # 坐标原点x,y
def inter(direction, step):
new_x = pos[0] + direction[0] * step
new_y = pos[1] + direction[1] * step
pos[0] = new_x
pos[1] = new_y
return pos
return inter
player = create() # 创建棋子player,起点为原点
print(player([1, 0], 10)) # 向x轴正方向移动10步
print(player([0, 1], 10)) # 向y轴正向移动20步
print(player([-1, 0], 10)) # 向x轴负方向移动10步
# [10, 0]
# [10, 10]
# [0, 10]
案例二
#
# 有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行,就可以使用闭包, 在内部获取文件名,外部引入要在文件提取的关键字
#
# 例如,需要取得文件"result.txt"中含有"163.com"关键字的行,看如下代码
def make_file(keep): # 外部函数
# 内部函数
def file_name(file):
# 读取出文件
read_file = open(file)
ret = read_file.readlines()
read_file.close()
ret_list = [i for i in ret if keep in i]
return ret_list
return file_name
filter_file = make_file("163.com")
file_name = filter_file("result.txt")
print(file_name)
总结
-
闭包概念:在一个函数中嵌套了另外一个函数,并且函数内部引用了外部函数的形参、或局部变量,称之为闭包
-
闭包和普通函数的区别
闭包在函数中执行完结果后,函数外部的局部变量不会被释放,会给内部函数使用
普通函数在执行完结果后不会保存值
-
闭包是在函数内再嵌套函数 闭包可以访问另外一个函数的局部变量或形参, 闭包可以让参数和变量不会被垃圾回收机制回收,始终保持在内存中