Python 迭代对象

该文章已生成可运行项目,

Python 迭代机制指南:可迭代对象、不可迭代对象、迭代器与生成器

目录

  1. 可迭代对象(Iterables)
  2. 不可迭代对象(Non-iterables)
  3. 迭代器(Iterators)
  4. 生成器(Generators)
  5. 实际应用
  6. 性能对比
  7. 最佳实践

可迭代对象(Iterables)

定义

可迭代对象是指可以被迭代(逐一返回其中的元素)的任何对象。在Python中,任何实现了 __iter__() 方法或 __getitem__() 方法的对象都是可迭代的。

特点

  • 可以使用 for 循环遍历
  • 可以用于列表推导式和生成器表达式中
  • 可以传递给内置函数 iter() 来获取迭代器
  • 支持 in 操作符来检查成员关系

常见的可迭代对象

  • 列表 (list)
  • 元组 (tuple)
  • 字符串 (str)
  • 字典 (dict)
  • 集合 (set)
  • 文件对象
  • 自定义实现了 __iter__()__getitem__() 方法的类

示例代码

# 常见可迭代对象的使用
my_list = [1, 2, 3, 4, 5]
my_tuple = (10, 20, 30)
my_string = "Hello"
my_dict = {"a": 1, "b": 2, "c": 3}
my_set = {100, 200, 300}

# 使用for循环遍历
for item in my_list:
    print(item)

# 在列表推导式中使用
squared = [x**2 for x in my_tuple]
print(squared)  # [100, 400, 900]

# 使用in操作符
print('H' in my_string)  # True

# 迭代字典
for key in my_dict:  # 默认迭代键
    print(key, my_dict[key])

# 使用字典的方法获取不同迭代视图
print(list(my_dict.keys()))    # ['a', 'b', 'c']
print(list(my_dict.values()))  # [1, 2, 3]
print(list(my_dict.items()))   # [('a', 1), ('b', 2), ('c', 3)]

创建自定义可迭代对象

class CustomIterable:
    def __init__(self, start, end):
        self.start = start
        self.end = end
    
    def __iter__(self):
        # 返回一个迭代器对象
        return CustomIterator(self.start, self.end)

class CustomIterator:
    def __init__(self, start, end):
        self.current = start
        self.end = end
    
    def __iter__(self):
        # 迭代器本身也是可迭代的
        return self
    
    def __next__(self):
        if self.current < self.end:
            value = self.current
            self.current += 1
            return value
        else:
            # 迭代结束时抛出StopIteration异常
            raise StopIteration

# 使用自定义可迭代对象
my_iterable = CustomIterable(1, 5)
for i in my_iterable:
    print(i)  # 输出: 1, 2, 3, 4

不可迭代对象(Non-iterables)

定义

不可迭代对象是指不能被直接迭代的对象。这些对象没有实现 __iter__() 方法,也没有按序号索引的 __getitem__() 方法。

特点

  • 不能直接用于 for 循环
  • 不能用于列表推导式
  • 不能用于生成器表达式
  • 不能传递给 iter() 函数
  • 不支持 in 操作符检查成员关系

常见的不可迭代对象

  • 数字 (int, float, complex)
  • 布尔值 (bool)
  • NoneType (None)
  • 函数和方法
  • 模块
  • 没有实现迭代协议的自定义类

示例代码

# 不可迭代对象示例
number = 42
boolean = True
none_value = None

# 尝试迭代这些对象会引发TypeError
try:
    for i in number:
        print(i)
except TypeError as e:
    print(f"错误: {e}")  # 输出: 错误: 'int' object is not iterable

# 判断对象是否可迭代
def is_iterable(obj):
    try:
        iter(obj)
        return True
    except TypeError:
        return False

print(is_iterable([1, 2, 3]))  # True
print(is_iterable("Hello"))    # True
print(is_iterable(42))         # False
print(is_iterable(None))       # False

将不可迭代对象转换为可迭代

# 将不可迭代对象包装成可迭代对象
class SingleItemIterable:
    def __init__(self, item):
        self.item = item
        self.done = False
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if not self.done:
            self.done = True
            return self.item
        raise StopIteration

# 现在可以迭代任何对象了
for i in SingleItemIterable(42):
    print(i)  # 输出: 42

迭代器(Iterators)

定义

迭代器是一个表示数据流的对象,它实现了迭代器协议,即 __iter__()__next__() 两个方法。迭代器会记住它的迭代状态。

特点

  • 实现了 __iter__()__next__() 方法
  • 可以通过 next() 函数获取下一个元素
  • 当没有更多元素时抛出 StopIteration 异常
  • 是惰性的,只在需要时才计算下一个元素
  • 一次性使用,遍历完后不能重新开始
  • 迭代器本身也是可迭代对象

创建迭代器

# 使用内置iter()函数创建迭代器
my_list = [1, 2, 3, 4, 5]
iterator = iter(my_list)

# 使用next()获取下一个元素
print(next(iterator))  # 1
print(next(iterator))  # 2

# 使用for循环迭代剩余元素
for item in iterator:
    print(item)  # 3, 4, 5

# 注意:迭代器已经遍历完毕,再次使用next()会抛出StopIteration异常
try:
    print(next(iterator))
except StopIteration:
    print("迭代器已耗尽")

自定义迭代器

class CountDown:
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

# 使用自定义迭代器
countdown = CountDown(5)
for num in countdown:
    print(num)  # 输出: 5, 4, 3, 2, 1

可迭代对象与迭代器的区别

  1. 可迭代对象

    • 实现了 __iter__() 方法,用于返回迭代器
    • 可以多次迭代
    • 例如:列表、元组、字符串等
  2. 迭代器

    • 实现了 __iter__()__next__() 方法
    • 跟踪迭代的状态
    • 是一次性的,迭代完毕后不能重新开始
    • 是惰性的,按需生成元素
# 演示区别
my_list = [1, 2, 3]  # 可迭代对象

# 可以多次迭代可迭代对象
for i in my_list:
    print(i, end=' ')  # 1 2 3
print()

for i in my_list:
    print(i, end=' ')  # 1 2 3
print()

# 而迭代器是一次性的
iterator = iter(my_list)
for i in iterator:
    print(i, end=' ')  # 1 2 3
print()

for i in iterator:
    print(i, end=' ')  # 不会输出任何内容,因为迭代器已耗尽
print("(迭代器已耗尽)")

生成器(Generators)

定义

生成器是一种特殊的迭代器,使用函数和yield语句创建。生成器可以懒惰地生成值,避免一次性在内存中创建整个序列。

特点

  • 使用 yield 语句而不是 return
  • 自动实现迭代器协议(__iter____next__
  • 保存函数执行状态,下次调用时从上次暂停的地方继续
  • 惰性计算,按需生成元素
  • 内存效率高,适合处理大数据集和无限序列
  • 一次性使用,不能重置

生成器函数

def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1

# 使用生成器函数
counter = count_up_to(5)
print(next(counter))  # 1
print(next(counter))  # 2

# 在for循环中使用
for num in counter:
    print(num, end=' ')  # 3 4 5
print()

生成器表达式

# 生成器表达式(类似列表推导式,但使用圆括号)
squares_gen = (x**2 for x in range(1, 6))

# 遍历生成器
for square in squares_gen:
    print(square, end=' ')  # 1 4 9 16 25
print()

生成器的高级特性

1. send() 方法:向生成器发送值
def echo_generator():
    value = yield "Ready"
    while True:
        value = yield f"Echo: {value}"

gen = echo_generator()
print(next(gen))      # Ready(启动生成器)
print(gen.send("Hello"))  # Echo: Hello
print(gen.send(42))       # Echo: 42
2. throw() 方法:向生成器内部抛出异常
def number_generator():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError:
        yield 'Exception handled!'

gen = number_generator()
print(next(gen))  # 1
print(gen.throw(ValueError))  # Exception handled!
3. close() 方法:关闭生成器
def countdown_generator():
    print("Starting countdown")
    try:
        yield 3
        yield 2
        yield 1
    finally:
        print("Generator closed!")

gen = countdown_generator()
print(next(gen))  # Starting countdown, 3
gen.close()       # Generator closed!
4. yield from:简化生成器代码的语法
def subgenerator():
    yield 1
    yield 2
    yield 3

def main_generator():
    yield 'Start'
    yield from subgenerator()  # 委托给子生成器
    yield 'End'

for item in main_generator():
    print(item)  # Start, 1, 2, 3, End

无限生成器示例

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 获取斐波那契数列的前10个数
fib = fibonacci()
for _ in range(10):
    print(next(fib), end=' ')  # 0 1 1 2 3 5 8 13 21 34
print()

实际应用

处理大文件

def read_large_file(file_path):
    with open(file_path, 'r') as f:
        for line in f:  # 文件对象是可迭代的,每次产生一行
            yield line.strip()

# 分析一个大日志文件(不必一次性加载整个文件)
def count_errors(log_file):
    error_count = 0
    for line in read_large_file(log_file):
        if "ERROR" in line:
            error_count += 1
    return error_count

# 假设使用
# print(count_errors("huge_log_file.txt"))

数据流处理管道

def read_data(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

def parse_data(lines):
    for line in lines:
        try:
            yield int(line)
        except ValueError:
            continue

def filter_data(numbers):
    for num in numbers:
        if num > 0:
            yield num

def process_data(file_path):
    lines = read_data(file_path)
    numbers = parse_data(lines)
    positive_numbers = filter_data(numbers)
    return positive_numbers

# 使用处理管道
# for item in process_data("data.txt"):
#     print(item)

自定义序列迭代器

class DateRange:
    """日期范围迭代器,产生两个日期之间的所有日期"""
    
    import datetime
    
    def __init__(self, start_date, end_date):
        self.start_date = start_date
        self.end_date = end_date
    
    def __iter__(self):
        current_date = self.start_date
        while current_date <= self.end_date:
            yield current_date
            current_date += self.datetime.timedelta(days=1)

# 示例使用
# from datetime import date
# start = date(2023, 1, 1)
# end = date(2023, 1, 10)
# for d in DateRange(start, end):
#     print(d)

性能对比

内存使用

下面是列表方式和生成器方式处理数据的内存使用对比:

import sys

# 列表方式(一次性加载到内存)
def get_squares_list(n):
    return [x**2 for x in range(n)]

# 生成器方式(按需生成)
def get_squares_generator(n):
    for x in range(n):
        yield x**2

# 比较内存占用
list_size = sys.getsizeof(get_squares_list(1000000))
gen_size = sys.getsizeof(get_squares_generator(1000000))

print(f"列表占用内存: {list_size} 字节")
print(f"生成器占用内存: {gen_size} 字节")
# 生成器占用的内存通常远小于列表

执行速度

import time

def measure_time(func, *args):
    start = time.time()
    result = func(*args)
    if not isinstance(result, (list, tuple)):
        # 如果是生成器,需要遍历以确保执行
        for _ in result:
            pass
    end = time.time()
    return end - start

def sum_squares_list(n):
    return sum([x**2 for x in range(n)])

def sum_squares_generator(n):
    return sum(x**2 for x in range(n))

n = 10000000
list_time = measure_time(sum_squares_list, n)
gen_time = measure_time(sum_squares_generator, n)

print(f"列表方式耗时: {list_time:.5f} 秒")
print(f"生成器方式耗时: {gen_time:.5f} 秒")
# 对于简单操作,列表推导式有时更快
# 对于复杂或内存密集型操作,生成器通常更快

最佳实践

何时使用迭代器/生成器

  1. 处理大数据集:当数据集太大,无法一次性加载到内存中时
  2. 处理无限序列:例如实时数据流或需要按需生成的序列
  3. 惰性求值:当计算成本高,且可能不需要所有结果时
  4. 实现数据处理管道:将处理步骤分解为独立的生成器函数

何时使用列表/其他容器

  1. 需要多次迭代:当数据需要被多次遍历时
  2. 需要随机访问:当需要通过索引访问元素时
  3. 数据集较小:当内存不是问题时
  4. 需要使用特定的容器方法:如排序、计数等

迭代器和生成器的通用模式

  1. 链式处理:将多个生成器连接形成处理管道

    def process_pipeline(data):
        step1 = (transform1(x) for x in data)
        step2 = (transform2(x) for x in step1 if condition(x))
        return step2
    
  2. 批处理模式:以固定大小的批次处理数据

    def batch_process(items, batch_size=100):
        batch = []
        for item in items:
            batch.append(item)
            if len(batch) >= batch_size:
                yield batch
                batch = []
        if batch:  # 处理最后一个不完整的批次
            yield batch
    
  3. 无限生成器与取样模式:从无限序列中获取有限样本

    def take(n, iterable):
        """从迭代器中获取前n个元素"""
        return list(islice(iterable, n))
    

避免常见的错误

  1. 多次使用同一个迭代器:迭代器是一次性的,使用后就耗尽了

    # 错误示例
    iterator = iter([1, 2, 3])
    list(iterator)  # [1, 2, 3]
    list(iterator)  # [],迭代器已耗尽
    
    # 正确做法
    my_list = [1, 2, 3]
    list(iter(my_list))  # [1, 2, 3]
    list(iter(my_list))  # [1, 2, 3],每次创建新的迭代器
    
  2. 无意中消耗迭代器:调试时打印迭代器可能会消耗它

    # 错误示例
    gen = (x for x in range(5))
    print(list(gen))  # 打印并消耗迭代器
    for x in gen:  # 这个循环不会执行,因为gen已被消耗
        print(x)
    
    # 正确做法
    gen = (x for x in range(5))
    # 如果需要查看内容又不想消耗迭代器,创建一个副本
    gen, debug_gen = itertools.tee(gen)
    print(list(debug_gen))  # 使用副本进行调试
    for x in gen:  # 原始生成器仍然可用
        print(x)
    
  3. 混淆迭代器和可迭代对象:理解它们的区别很重要

    # 可迭代对象可以多次迭代
    iterable = [1, 2, 3]
    for _ in range(2):
        for item in iterable:
            print(item, end=' ')
        print()
    
    # 迭代器只能被迭代一次
    iterator = iter([1, 2, 3])
    for item in iterator:
        print(item, end=' ')  # 1 2 3
    print()
    for item in iterator:
        print(item, end=' ')  # 不会打印任何内容
    

总结

  • 可迭代对象是能够一次返回一个元素的对象,它们实现了 __iter__() 方法或序列的 __getitem__() 方法。
  • 不可迭代对象不能直接用于循环或其他期望可迭代对象的上下文中。
  • 迭代器是实现了 __iter__()__next__() 方法的对象,能够跟踪迭代状态,一次返回一个值。
  • 生成器是使用 yield 语句创建的特殊迭代器,能够简化迭代器的创建并提高内存效率。

迭代器和生成器特别适合处理大型数据集或创建数据处理管道,它们让你能够以更少的内存消耗处理更多的数据。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值