Skip to content

迭代和循环的区别

python 的 for in 语法是比较特殊的, 不同于其他编程语言的 for 语法, python 中的 for in 语法不能用于数字, 只能用于可迭代对象, 那么如何判断一个对象是否是可迭代对象呢?

py
# 迭代: 每次调用迭代器方法, 直接获取值
items = [1, 3, 5, 7, 9]
for item in items:
    print(f"item value is: {item}")

# 循环:
# 1. 首先获取列表的长度
# 2. 列表的索引从 0 开始, 每次循环让 i += 1
#    那么此时 i 每次就是列表的每个元素的索引
# 3. 所以在循环中, 取值需要通过索引 items[i]
items = [1, 3, 5, 7, 9]
i = 0
items_length = len(items)
while i < items_length:
    print(f"item value is: {items[i]}")
    i += 1

可迭代对象与迭代器对象

可迭代对象

简而言之: 实现了可迭代协议的对象, 就可以称为 可迭代对象

或者说: 只要是能被 for in 语法迭代的对象, 都可以称之为可迭代对象

比如: 字符串,列表,元组,集合等等

迭代器对象

不仅要实现可迭代协议, 还必须要在 __iter__ 方法总返回对象本身(也就是返回 self)

那么也就是说, 迭代器对象一定是可迭代对象, 但是可迭代对象不一定是迭代器对象

判断是否是可迭代对象

py
from collections.abc import Iterable

# False
print(f"数字可迭代吗? { isinstance(123, Iterable) }")

# True
print(f"字符串可迭代吗? { isinstance('123', Iterable) }")

# True
# list/tuple/set/dict
print(f"list 可迭代吗? { isinstance([1,2,3], Iterable) }")
print(f"tuple 可迭代吗? { isinstance((1,2,3), Iterable) }")
print(f"set 可迭代吗? { isinstance({1,2,3}, Iterable) }")
print(f"dict 可迭代吗? { isinstance({"a":1,"b":2}, Iterable) }")

判断是否迭代器对象

py
from collections.abc import Iterable, Iterator

print(f"列表是迭代器对象吗?{isinstance([1,2,3], Iterator)}") # False
print(f"元组是迭代器对象吗?{isinstance((1,2,3), Iterator)}") # False
print(f"集合是迭代器对象吗?{isinstance({1,2,3}, Iterator)}") # False

stack = Stack() # Stack 是下面可迭代协议的代码
print(f"stack 是迭代器对象吗?{isinstance(stack, Iterator)}")

可迭代协议

在 Python 中, 可迭代协议定义了一种标准的方式来遍历集合中的元素

要实现迭代器协议, 一个对象必须带有以下两个方法 __iter____next__

  • __iter__ 方法会返回一个带有 __next__ 的对象
  • __iter__ 方法如果返回对象本身self, 那么就可以称之为 迭代器对象, 否则就只能叫 可迭代对象
  • __next__ 方法会返回下一个访问到的元素
py
################################
#    手动实现可迭代协议
################################
from collections.abc import Iterable, Iterator

class Stack:
    def __init__(self):
        self.items = []
        self.index = 0  # 用于迭代器的位置追踪

    # 入栈
    def push(self, item):
        self.items.append(item)

    # 出栈
    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        else:
            raise IndexError("pop from an empty stack")

    # 清栈
    def clear(self):
        self.items = []

    # 判断栈是否为空
    def is_empty(self):
        return len(self.items) == 0

    # 返回栈的长度
    def size(self):
        return len(self.items)

    # 实现迭代器协议(就是返回一个带有: __iter__ 和 __next__ 方法的对象)
    def __iter__(self):
        # 从栈顶开始迭代
        self.index = len(self.items) - 1
        return self

    # 迭代一般是将一组数据, 看做有序的数据集合
    # 所有 __next__ 用于访问数据集合中的下一个元素
    def __next__(self):
        if self.index < 0:
            # 如果索引小于0, 说明栈中元素全部被
            # 访问(迭代)过了, 说明迭代可以结束了
            raise StopIteration
        item = self.items[self.index]
        self.index -= 1  # 移动到下一个位置
        return item


stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)

# 迭代输出栈中的所有元素

# 因为 stack 实现了可迭代协议, 那么 stack 就是一个可迭代对象
# 所以可以这样迭代:
print(isinstance(stack, Iterable))  # True
for item in stack:
    print(f"item is: {item}")

print("-" * 30)

# 因为 __iter__ 方法返回了一个可迭代对象
# 所以可以这样遍历:
print(stack == stack.__iter__())  # false
for item in stack.__iter__():
    print(f"item is: {item}")

print("-" * 30)

print(f"stack 是否是可迭代对象 {isinstance(stack, Iterable)}") # True
print(f"stack 是否是迭代器对象 {isinstance(stack, Iterator)}") # False
py
from collections.abc import Iterable, Iterator

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

    # 入栈
    def push(self, item):
        self.items.append(item)

    # 出栈
    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        else:
            raise IndexError("pop from an empty stack")

    # 清栈
    def clear(self):
        self.items = []

    # 判断栈是否为空
    def is_empty(self):
        return len(self.items) == 0

    # 返回栈的长度
    def size(self):
        return len(self.items)

    # 返回一个带有 __iter__ 和 __next__ 方法的对象
    # 但是这个对象并不是当前实例 self
    # 所以这样的对象, 只能叫 可迭代对象, 不能叫 迭代器对象
    def __iter__(self):
        return StackIterator(self.items)


class StackIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value


stack = Stack2()
stack.push(1)
stack.push(2)
stack.push(3)

# 因为 stack 实现了可迭代协议, 那么 stack 就是一个可迭代对象
# 所以可以这样迭代:
print(isinstance(stack, Iterable))  # True
for item in stack:
    print(f"item is: {item}")

print("-" * 30)

# 因为 __iter__ 方法返回了一个可迭代对象
# 所以可以这样迭代:
print(stack == stack.__iter__())  # True
for item in stack.__iter__():
    print(f"item is: {item}")

print("-" * 30)

print(f"stack 是否是可迭代对象 {isinstance(stack, Iterable)}") # True
print(f"stack 是否是迭代器对象 {isinstance(stack, Iterator)}") # False

对比现实, 理解概念

  1. 可迭代协议 代表一种能力, 实现 可迭代协议 的都可以称之为 可迭代对象
  2. 可迭代对象 只有满足某种特定要求后才能称之为 迭代器对象
  3. 总结来说: 迭代器对象一定是可迭代对象, 可迭代对象不一定是可迭代对象
  • 我可以跑步, 跑步运动员也可以跑步, 我们都实现了 可跑步协议, 都可以称为 可跑步的人
  • 能参加奥运会跑步比赛的人才能称之为 跑步运动员, 而不满足要求的人只能称为 可跑步的人
  • 跑步运动员 一定是 可跑步的人, 但是 可跑步的人 不一定是 跑步运动员

iter 函数

  • iter函数 是一个可以将 可迭代对象 转换成 迭代器对象 的内置函数
py
from collections.abc import Iterable, Iterator

items = [1, 2, 3]
items_iter = iter(items)

print(f"是可迭代对象吗?{isinstance(items, Iterable)}")  # True
print(f"是迭代器对象吗?{isinstance(items, Iterator)}")  # False

print("-" * 30)

print(f"是可迭代对象吗?{isinstance(items_iter, Iterable)}")  # True
print(f"是迭代器对象吗?{isinstance(items_iter, Iterator)}")  # True

next 函数

不通过 for in 语法自动迭代, 手动获取迭代器函数的值

py
it = iter([1, 2, 3])

print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3

# 如果超过就会抛出异常
print(next(it))
print(next(it))

for in 语法的原理

py
# 根据以下特性, 手动实现一个 for in
# 1. for in 只能迭代可迭代对象
# 2. iter 可以将 可迭代对象 转换成 迭代器对象
# 3. next 可以手动迭代, 超出就抛出异常

def for_in(iterable_object, handler):
    try:
        it = iter(iterable_object)
        value = next(it)
        while(value):
            handler(value)
            value = next(it) # 一直向后迭代
    except Exception as e:
        print("迭代结束了", e)

for_in([1, 2, 3], print)

生成器函数

生成器函数其实应该叫 生成可迭代对象函数, 因为他会自动返回一个可迭代对象

py
from collections.abc import Iterable, Iterator

def get_nums(start: int, end: int):
    i = start
    while i <= end:
        # 用 yield 关键字向外返回结果
        yield i
        i += 1
    return i

# 此时的 nums 就是一个可迭代对象
nums = get_nums(1, 9)

print(f"is iterable:{ isinstance(nums, Iterable) }") # True
print(f"is iterator:{ isinstance(nums, Iterator) }") # True

# 此时就直接可以使用 for in 遍历
for num in nums:
    print(f"num value is:{num}")

Released under the MIT License.