Python 迭代器

Python 迭代器

从编程来说, 理解 集合容器 的概念是非常重要的. 这就是涉及到 数据结构 了. 类似的还有 线性结构, 树形结构, 图结构, 网络结构等等... 高度抽象讲我不太行, 只能通过一些例子来描述和归纳.

这里的迭代, 迭代器这些概念, 其实就涉及数据结构了, 即数据元素如何存储, 组织, 封装, 功能实现等. 不会再细说这些概念, 可以自行搜索, 这里直接进入主题, 迭代器.

迭代 (过程)

迭代是个动词, 是一个过程. 用来作谓语.

在Python 中, 对一个 集合 对象进行 遍历 的过程就是迭代. (for 循环遍历的过程)

集合对象: list, tuple, dict, set, range, str ..

集合: 跟数学的集合不一样哈. 这里的集合, 里面由多个对象组成一个, 盒子, 或者一个容器.

case1: 迭代过程(for 遍历)

for i in [0, 1, 2, 3, 4]: print(i, end=" ")

print()
for i in range(5): print(i, end=" ")

print()
for key in {'a': 1, 'b': 2, 'c': {'d': 4}}: print(key, end=' ')

print()
for s in "hello youge": print(s, end=" ")

print()
for i in (0, 1, 2, 3, 4): print(i, end=' ')

print()
for i in set((1,2,3)): print(i, end=" ")

print()
for k, v in enumerate([1,2,3]):print((k, v), end=" ")

print()
for i in zip([0,1,2], "abcdefg"): print(i, end=" ")

0 1 2 3 4 
0 1 2 3 4 
a b c 
h e l l o   y o u g e 
0 1 2 3 4 
1 2 3 
(0, 1) (1, 2) (2, 3) 
(0, 'a') (1, 'b') (2, 'c') 

这个过程就是迭代, 能被迭代的这些对象呢, 就是 可迭代对象.

case2: 单链表节点遍历

虽然每天都在使用这个神奇的的 for 关键字, 但为啥可以这样? 根据上篇的 魔法方法, 不用查就能基本推出, 必然跟 __ next __ 方法 和 __ iter __ 相关, 但这两个方法又是如何工作的?

这里举一个链表的栗子, 来尝试模拟 for 循环所做的事情. (只是模拟哈).

  • 节点类: 数据区 和 指针区 (指向下一个元素的地址)
  • 链表类: 头指针
class Node:
    """节点类"""

    def __init__(self, data):
        self.data = data
        self.next = None


class SigleList:
    def __init__(self):
        self.head = None

    def add(self, item):
        """从链表头部插入元素"""
        node = Node(item)
        node.next = self.head
        self.head = node

    def travel(self):
        """遍历节点"""
        # 定义一个游标, 指向头节点
        current = self.head
        # 游标依次往后移动, 即实现遍历节点
        while current != None:
            print(current.data)
            current = current.next
            

if __name__ == '__main__':
    # 模拟 for
    
    my_lst = SigleList()
    
    # add node
    my_lst.add(1)
    my_lst.add(2)
    my_lst.add(3)

    # for ...
    my_lst.travel()

写的是头插法, 自然是倒序的, 输出跟栈差不多的

3
2
1

可以看出 for 循环无非也是做了这样的事情而已. 一个对象能够遍历, 说明里面肯定有 多个元素, 区别在于元素的组织形式不同而已, 从存储角度来看.

  • 连续存储 ( list, range, tuple...)
  • 链式存储 (链表)

我想, 对于 迭代 的过程, 经常用 for 的小伙伴, 到此应该明白了.

迭代对象

迭代, 是个动词; 迭代对象, 是个名词, 通常可用来作主语或宾语.

Python 中 万物皆对象 嘛, 对一个对象, 能够用 for 循环去遍历, 而不报错, 则该对象就是 可迭代对象.

is 可迭代对象

from collections import Iterable

print(isinstance([1, 2, 3], Iterable))

print(isinstance(range(3), Iterable))

print(isinstance({'a': 1, 'c': {'d': 4}}, Iterable))

print(isinstance("hello youge", Iterable))

print(isinstance((1, 2, 3), Iterable))

print(isinstance(set((1, 2, 3)), Iterable))

print(isinstance(enumerate([1, 2, 3]), Iterable))

print(isinstance(zip([1, 2, 3], "abcdefg"), Iterable))


class A:
    pass

a = A()
print(isinstance(a, Iterable))

True
True
True
True
True
True
True
True
False

不可迭代, 也不会抛异常, 只是 False, **为啥本例, 自定义的 类 是不可迭代的呢? **, 很显然, 从type 和万物皆对象的角度来理解, 立马就至少, 咱自定义的类, 必然缺少相应的 魔法方法 __ iter __

  • __ iter ___
from collections import Iterable


class A:
    
    def __iter__(self):
        pass


if __name__ == '__main__':
    a = A()
    print(isinstance(a, Iterable))

# output
True

如果再 实现 __ next __ 即下一个元素的位置, 那这样不就是一个 迭代器 了呀. 对, 就是这样直观.

迭代器

迭代是个动词, 表示一个过程, 用作谓语;

迭代对象是名词, 用作主语或宾语;

迭代器是名词. 是一类抽象概念事物的统称.

在 Pyhton 中呢, 万物皆对象, int, list, str... 都是类, 实现了 __ iter __ 方法, 和 __ next __ 的类对象, 称为迭代器.

class MyIterator:
    
    def __iter__(self):
        return self
    
    def __next__(self):
        # Todo: get the next value when the object is called
    	pass

MyIterator类就是一个迭代器, 即能够被 next() 函数调用, 并不断返回下一个值 的对象称为迭代器 (Iterator)

各种神器: 装饰器, 迭代器, 生成器, 触发器(sql)...

理解: 实现了 __ iter__ , __ next__的类是一个迭代器, 迭代器 能够对 可迭代对象 进行 迭代 操作.

case1: iter() 和 next()

from collections import Iterable


def for_in(obj):
    """用next实现 for 循环"""
    if isinstance(obj, Iterable):
        # iter() 将该可迭代对象, 作为迭代器
        iterator = iter(obj)

        # 迭代器通过 next() 方法返回下一个数据
        while True:
            try:
                print(next(iterator))
            except StopIteration:
                print("Done!")
                break
    else:
        print(f"this object '{obj}' is not iterable.")


if __name__ == '__main__':
    for_in([1,2,3])
    for_in(range(2))
    for_in("hi")
    for_in(666)
1
2
3
Done!
0
1
Done!
h
i
Done!
this object '666' is not iterable.

case2 定义 __ iter __ 和 __ next __

上面在说 for 的时候, 举的栗子是, 单链表的遍历, 用 链式存储 展现的. 这里来演示一个 连续存储 的栗子.

  • 一块连续的内存空间
  • 元素之间的位置通过 与首元素位置的 "偏移量" 相对位置来记录

在编程中, 这基本元素就是 数组. 在 Python 中, 已经对数组进行了高级封装, 即 list. 能够动态扩容 和 存储不同类型的数据.

数组: 是一个特别重要的编程元素. 存储同类型的数据. 在数学中, 被称为向量, 物理中称为矢量, 应用非常广

列表: Python 中 已经对数组有高级封装了. 在实现一些数据结构时, 通常会将 list 作为 底层结构 (忽略其底层)

灵魂发问: 请用 Python 实现一个列表, 或者字典....

for 循环的本质, 就是先调用 __ iter __ 返回self, 然后再不断调用 __ next __ 直到异常 raise StopIteration

  • 先执行 __ iter __ 方法, return self
  • 然后不断调用 __ next __ 方法, 直到 捕捉到异常 StopIteration
class A:
    def __next__(self):
        print("__next__ is called.")

        for i in range(3): print(i)

        # 没有 raise 则会一直重复调用 __next__
        raise StopIteration

    def __iter__(self):
        print("__iter__ is called.")
        return self  # 不能是其他对象


if __name__ == '__main__':
    a = A()
    for i in a: # 调 __tier__ 一次,重复调 __next__ 直至
        pass

__iter__ is called.
__next__ is called.
0
1
2

于是, 我们就可以通过 重写 __ iter __ 和 __ next __ 方法来实现自己的迭代器了呀.

class Iterator:
    def __init__(self, lst):
        self.items = lst
        self.index = 0

    def __next__(self):
        if self.index < len(self.items):
            cur_data = self.items[self.index]

            self.index += 1  # 移动游标
        else:
            raise StopIteration
        return cur_data


class MyList:
    def __init__(self):
        self.items = []

    def append(self, item):
        self.items.append(item)

    def __iter__(self):
        iterator = Iterator(self.items)
        return iterator

    def travel(self):
        for i in self.items:
            print(i)

写这么多呢, 其实也是为了从多角度说明 for 循环 的原理. 即, 首先会去判断输入的对象是否为可迭代对象. 然后拿到该迭代器, 不断调用**__ next __ **访问下一个数据, 直到结束.

在来一个实际的栗子.

class Fib:
    """用迭代器实现费氏数列"""
    def __init__(self, count):
        self.a = 0
        self.b = 1

        self.index = 0
        self.count = count

    def __next__(self):
        data = self.a

        if self.index < self.count:
            self.a, self.b = self.b, self.a + self.b
            self.index += 1
        else:
            raise StopIteration
        return data
    
    # 必须要有 __iter__, for 首先会掉, 然后再重复调 __next__
    
    def __iter__(self):
        return self
    
    
if __name__ == '__main__':
    fib = Fib(5)
    for i in fib:
        print(i)
0
1
1
2
3

小结

  • 迭代是个过程, 代码体现为 for 遍历的过程.
  • 能够被 next() 函数调用, 并不断返回下一个值 的对象称为迭代器 (Iterator)
  • 原理是实现 __ iter __ 和 __ next __ 方法
  • for 的原理, 是一个迭代器的工作过程, 判断是否可迭代, 然后调用 __ next __ 不断取数, 直到异常 StopIteration

迭代器就先到这了, 具体的应用呢, 其实是反映在 for 这个都很熟悉, 另一个我目前接触的是 生成器, 即我常被弄混 的 return 和 yeild. 先到这吧, 每篇将重点讲阐明即可, 尽量控制在 2500 - 4000 之间的字数为宜.

posted @ 2020-02-07 20:41  致于数据科学家的小陈  阅读(275)  评论(0编辑  收藏  举报