复习集合对象, 迭代, 迭代对象. for 原理, iter 和 next 的实现, 函数中 yield

感觉是这一整周都没有学习和复习了, 一直在忙最近的一个报表开发, 数据处理真的是不容易, 涉及10多张线上, 线下的表, 数据量还挺大, 从 mysql 迁到了 IQ... 主要是数据处理都是要依靠 sql 来进行, 比如去括号呀, 匹配呀这类的... 贼难受, 最有意思的一点是关于匹配, 要进行 4轮匹配, 再全部 union 起来... 虽然性能不太好, 要反复去查表, 但终究功能是可以的, 也看到 sql 的查询强大之处. 相比 Python, 我觉得对于线上大数据量的数据集, 处理就不好弄了, 用 Pandas 读几百万表到 内存 ?? 这就有点难搞, 我也是渐渐觉得, 数据分析, BI, 越发感到 sql 比 Python 更加重要一点, 我当然是两者配合用的呀, 都熟练的.

然后, 还是来继续抄抄书, 学无止境嘛...

不用 for 遍历迭代器

迭代器在 Python 中, 我觉得特别重要, 因为但凡你开始写代码, 都要用到它哇.


  • 迭代, 是一个动词, 用谓语. 对一个集合对象进行遍历的过程就是迭代 (for 循环)
  • 可迭代对象, 能够被 for 循环遍历的对象, 即其构造过程 实现了 __ next __ 方法 和 __ iter __ 方法

这里就不再解释什么指针, 万物皆对象这些原理了, 总之, __ next __ 方法使当前指针, 指向下一个元素的地址, 而 __ iter __ 方法, 返回 当前元素值, 这就是 for 遍历的原理.

需求

遍历一个可迭代对象的所有元素, 但就是不让用 for 循环

方案

用 for 循环的原理, 即 __ next __ 方法 或者 内置 next( ) 函数, 二者其实一样的, 我现更偏 对象.__ next __ ( ) 一些

lst = [1,2,'youge']

it = iter(lst)


# iter() 变为可迭代对象, __next__() 我觉得会更好理解一些
print(it.__next__())

print(next(it))

print(it.__next__())
print(it.__next__())
1
2
youge

# 遍历完了再就会报错哦
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-3-4e885b436bf8> in <module>
6 print(next(it))
7 print(it.__next__())
----> 8 print(it.__next__())

StopIteration:

再以一个文件对象举例, 遍历每行, 用 __ next __ ( ) 遍历, 并捕获 StepIteration 异常.

def manual_iter():
with open("./test.txt") as f:
try:
while True:
line = f.__next__()
print(line)
except StopIteration:
pass
print("over")

# test
manual_iter()
hello, 

nice to see you

do you want to drink with me ?

over

用这种 try..except 来捕捉 StopIteration 的方式, 我工作中是从来没这用过的, 更多是通过 if 的方式来 break .

# 用 next() 是可以传参的

with open('test.txt') as f:
while True:
line = next(f, None)
if line is None:
break
print(line, end = '')
hello, 
nice to see you
do you want to drink with me ?

自定义可迭代的容器

简单理解容器对象, 就 list, tuple, dict, str, zip, enumerate 这些.... 可用来装数据的 "容器"

需求

构建一个自定义容器, 里面包含有列表, 元组等可迭代对象, 并实现在这容器上进行迭代操作

方案

通过在创建类的时候, 实现 __ iter __ 和 __ next __ 方法即可. 当然, 这里的底层结构用 list .

class Node:
def __init__(self, value):
self._value = value,
self._items = []
self._index = 0

def __repr__(self):
'''打印对象时自动触发, 将任意一个对象,包裹为字符串, 跟 eval() 相逆'''
return f'Node({self._value})'

def app_item(self, node):
'''添加元素节点'''
self._items.append(node)

def __iter__(self):
"""返回自己"""
return iter(self._items)

def __next__(self):
"""返回当前节点值, 并指向下一个元素节点"""
if self.index < len(self.items):
cur_value = self.items[self.index]

# 获取当前值后, 指针再往后移动一次
self.index += 1
else:
raise StopIteration
return cur_value


# test
root = Node(0)
node_01 = Node(1)
node_02 = Node(2)
node_03 = Node(3)

root.app_item(node_01)
root.app_item(node_02)
root.app_item(node_03)

# 能够 for 的前提是实现了 __iter__ 和 __next__ 方法哦
for node in root:
print(node)
Node((1,))
Node((2,))
Node((3,))

用生成器创建自定义迭代模式

需求

实现一个自定义迭代模式, 跟普通的内置函数如 range(), reversed() 不一样的方式

方案

用生成器 (元组推导式 或 yield ) 来实现, 这里先来写一个生成摸个范围内浮点数的生成器.

def my_range(start, stop, increment):
'''生成某个范围内的浮点数'''
while start < stop:
yield start
start += increment

# test
for i in my_range(0, 1, 0.25):
print(i)

print(list(my_range(0, 1, 0.15)))
0
0.25
0.5
0.75

[0, 0.15, 0.3, 0.44999999999999996, 0.6, 0.75, 0.9]

反正我个人是越来越喜欢用生成器了, 尤其在写函数的时候, 优先考虑用 yield 而不是 return , 其好处在于更加优雅, 同时节省内存, 且后面还可以运行代码, 简直大爱. 一个函数中, 如果出现 yield 语句, 则将其转为了生成器. 跟普通函数不同在于, 生成器只能用于迭代操作, 不会终止函数, 而 return 则彻底结束函数运行.

def count_down(n):
print('Start to count from', n)
while n > 0:
yield n
n -= 1
print('Done!')

# test
c = count_down(3)

print(c)

print(next(c))
print(next(c))
print(c.__next__())
print(next(c))
<generator object count_down at 0x000002070EE99DB0>
Start to count from 3
3
2
1
Done!
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-28-865f6082c7dc> in <module>
14 print(next(c))
15 print(next(c))
---> 16 print(next(c))

StopIteration:

一个生成器函数的主要特征是它会回应在迭代中使用到的 next 或者, 一旦生成器返回退出, 则迭代终止. 通常的做法是用 用 for 去进行迭代, 不会出现报错的细节呢.

因为, for 的原理, 是一个迭代器的工作过程, 判断是否可迭代, 然后调用 __ next __ 不断取数, 直到异常 StopIteration.

小结

  • 复习了一把迭代, 可迭代对象, 迭代器, 集合对象, 序列对象, 容器等基础概念
  • 迭代的原理, 即是实现了 __ iter __ 和 __ next __ () 方法, 即将返回自身可迭代对象和, 返回当前值并指向下next 节点
  • 函数中用 yield 是真的香, 结合 for 遍历的原理, 不断调用 __ next__ 和 捕捉 StopIteration 异常

耐心和恒心, 总会获得回报的.