Python迭代器与生成器

发布时间:2024年01月23日

第十四章、迭代器与生成器

一、概述

????????迭代器(Iterator)和生成器(Generator)是 Python 中处理序列数据的重要工具,它们都允许按需逐个访问数据而不是一次性加载所有数据到内存中。这在处理大型数据集或无限序列时非常有用,可以节省内存和提高效率。

二、迭代器(Iterator)

????????迭代器是一种实现了迭代协议的对象,它可以在循环中逐个返回元素。迭代器必须实现两个方法:__iter__()__next__()__iter__() 方法返回迭代器本身,而 __next__() 方法返回下一个元素,如果没有下一个元素则引发 StopIteration 异常。

class MyIterator:
    def __init__(self, max_value):
        self.max_value = max_value
        self.current = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current < self.max_value:
            result = self.current
            self.current += 1
            return result
        else:
            raise StopIteration

my_iterator = MyIterator(5)
for num in my_iterator:
    print(num)  # 输出: 0, 1, 2, 3, 4

1、迭代器对象只要使用过一次其中的内容就没有了,剩一个空的迭代器对象

zip_it = zip([1,2], ["one","two"])
print(zip_it) #<zip object at 0x00000181E1330388>
print(list(zip_it)) #[(1, 'one'), (2, 'two')]
print(list(zip_it)) #[]

my_list = [1,2,3]
it = iter(my_list)
print(tuple(it)) #(1, 2, 3)
print(tuple(it)) #()

三、生成器(Generator)

????????生成器是一种更简洁的创建迭代器的方法,它使用了函数和 yield 关键字。生成器函数在每次调用时返回一个生成器对象,生成器可以在调用 __next__() 方法时执行函数直到遇到 yield 语句,然后暂停并返回值,下一次调用时从上次的位置继续执行。

def my_generator(max_value):
    current = 0
    while current < max_value:
        yield current
        current += 1

gen = my_generator(5)
# for num in gen:
#     print(num)  # 输出: 0, 1, 2, 3, 4

print(gen)
print(next(gen)) #0
print(next(gen)) #1
print(next(gen)) #2
print(next(gen)) #3
print(next(gen)) #4
# print(next(gen))

????????生成器的优势在于它们使用起来更简洁,并且可以按需生成数据而不是一次性生成所有数据。这对于处理大数据集或无限序列非常有用。

????????总结:迭代器和生成器都是用于按需逐个访问数据的工具,它们节省内存并提高了处理效率。迭代器需要实现 __iter__()__next__() 方法,而生成器使用函数和 yield 关键字来生成数据。

四、迭代器和生成器的区别

????????迭代器(Iterator)和生成器(Generator)在实现和使用上有一些区别,虽然它们都用于按需逐个访问数据,但它们的实现方式和特点有所不同:

1. 实现方式

  • 迭代器:迭代器是一个类,必须实现 __iter__()__next__() 方法。__iter__() 方法返回迭代器本身,__next__() 方法返回下一个元素,如果没有下一个元素则引发 StopIteration 异常。迭代器适用于需要自定义状态维护的情况。

  • 生成器:生成器是一种更简洁的创建迭代器的方法,使用函数和 yield 关键字。生成器函数在每次调用时返回一个生成器对象,yield 语句可以暂停函数的执行,并返回一个值,下一次调用时从上次的位置继续执行。生成器适用于需要生成数据的情况,不需要显式地维护状态。

2、简洁性

  • 迭代器:虽然迭代器可以实现自定义的状态维护,但代码通常比较冗长,需要在类中定义多个方法。

  • 生成器:生成器更简洁,只需要使用函数和 yield 关键字,不需要显式地编写类和多个方法。

3. 内存占用

  • 迭代器:由于迭代器需要定义类,并且需要显式地维护状态和数据,可能会占用较多的内存。

  • 生成器:生成器使用的内存更加有效,它按需生成数据,只在需要时生成并返回元素,不会一次性生成全部数据。

4. 使用情景

  • 迭代器:适用于需要自定义状态管理、自定义迭代行为的情况,或者在没有合适的生成器函数时。

  • 生成器:适用于按需生成数据、处理大数据集或无限序列的情况。生成器更加简洁,不需要显式地维护状态,使代码更加清晰易懂。

????????总结:迭代器和生成器都用于按需逐个访问数据,但生成器是一种更简洁、更高效的方法,适用于大多数需要逐个生成数据的情况。如果需要自定义状态维护和迭代行为,可以使用迭代器。

?缓存机制

Python中对于不可变对象,相同的值在堆内存中只创建一份;对于可变对象,相同的值在堆内存中创建多份

Python中有常量池吗,还是对象都是在堆中创建

在 Python 中,没有像 Java 中的常量池那样的明确概念。Python中的对象通常是在堆中创建的,而不是像 Java 中的常量池那样存储在特定的常量区域。

在 Python 中,整数和短字符串(长度较短的字符串)是被缓存的,这意味着相同的整数或短字符串在内存中只有一份拷贝。这种缓存是为了提高性能和节省内存。这种机制在一定程度上类似于常量池的思想,但并不是完全相同的概念。

当你创建一个对象(比如一个整数、字符串、列表、字典等等),Python 会在堆中为其分配内存。如果你创建了两个具有相同值的整数或短字符串,它们通常会指向相同的内存地址,这是因为缓存的机制。

不可变数据类型的对象在堆内存中只有一份,可变数据类型可以有多份

值得注意的是,Python中的对象在内存中是动态分配和管理的,而不是像C++一样在栈中创建和销毁。这意味着你不需要手动释放对象的内存,Python会自动处理内存管理。

短字符串是多短,长度是多少

在 Python 中,具体定义"短字符串"是指长度较短的字符串,通常是指长度小于等于 20 个字符的字符串。这个长度是根据Python的实现和版本可能会有所不同,但一般情况下都是在这个范围内。

在 Python 3.7 及以后的版本中,对长度小于等于 20 个字符的字符串进行了优化,采用了一个新的策略,即使用一种名为 "Small String Optimization"(SSO)的技术。这种优化策略可以在字符串对象本身的内存空间中存储字符串的值,而不需要额外的内存分配。

SSO技术:就是直接在栈中的变量内存空间中存储字符串对象,而不是在堆内存中存储字符串对象,在栈中的变量内存空间中存储对象的引用,优化堆内存空间

当字符串长度超过 20 个字符时,Python 会使用动态分配的内存来存储字符串的值,这时候就不再使用 SSO 优化。

需要再次强调的是,这些细节可能因 Python 版本和实现而有所不同。在不同的版本中,内部的优化和实现可能会有所改变。如果特别依赖于这些细节,最好查阅特定版本的文档或源代码来获取更准确的信息。

什么叫 在字符串对象本身的内存空间中存储字符串的值,而不需要额外的内存分配

在 Python 中,通常情况下,字符串对象会在堆中分配一块内存来存储字符串的值。这意味着字符串对象本身的内存空间包含指向实际字符串数据的指针。

然而,在 Python 3.7 及以后的版本中,对于长度小于等于 20 个字符的字符串,采用了一种优化技术,即 "Small String Optimization"(SSO),使得字符串对象本身可以直接存储字符串的值,而不需要额外的内存分配。

具体实现方式是:

  1. 对于较短的字符串,Python 会将字符串的值直接存储在字符串对象本身的内存空间中,而不是在堆中分配额外的内存。这样做的好处是避免了为每个短字符串额外分配内存所带来的开销。

  2. 当字符串长度超过 20 个字符时,Python 会切换回传统的方式,在堆中分配内存来存储字符串的值。

这种优化技术可以提高处理短字符串的性能和节省内存空间,因为很多时候我们会使用较短的字符串,而且不需要为它们分配额外的内存空间。

需要注意的是,这是 Python 解释器的内部优化,对于使用 Python 的开发者来说,并不需要关心这些细节,因为 Python 会自动处理这些内存管理的问题。

int1 = 100000000000000000000000000
int2 = 100000000000000000000000000
print(id(int1)) #2462513852704
print(id(int2)) #2462513852704
print("int类型"+"=" * 30)

float1 = 3.14
float2 = 3.14
print(id(float1)) #2366741602768
print(id(float2)) #2366741602768
print("float类型"+"=" * 30)

bool1 = True
bool2 = True
print(id(bool1)) #140737194679144
print(id(bool2)) #140737194679144
print("bool类型"+"=" * 30)

str1 = "aaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaa"
str2 = "aaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaaqaaaaaaaaaaaaaaaaaaaa"
print(id(str1)) #2366742156384
print(str1.__len__())
print(id(str2)) #2366742156384
print(str2.__len__())
print("str类型"+"=" * 30)

tuple1 = ()
print(type(tuple1))
tuple2 = ()
print(type(tuple2))
print(id(tuple1)) #2366740578416
print(id(tuple2)) #2366740578416
print("tuple类型"+"=" * 30)

list1 = []
list2 = []
print(type(list1))
print(type(list2))
print(id(list1)) #2366741664256
print(id(list2)) #2366741900160
print("list类型"+"=" * 30)

set1 = {1,2,3}
set2 = {1,2,3}
print(type(set1))
print(type(set2))
print(id(set1)) #2366742095776
print(id(set2)) #2366742094432
print("set类型"+"=" * 30)

dict1 = {}
dict2 = {}
print(type(dict1))
print(type(dict2))
print(id(dict1)) #2366741854976
print(id(dict2)) #2366741855232
print("dict类型"+"=" * 30)
# print("a" + "b")
# print("a","b",sep="")

占位符

在Python中,pass 是一个占位符,用于在语法上占据一个语句块的位置,但实际上不执行任何操作。它在代码中起到一个占位的作用,用于暂时跳过某个语句块的执行,或者在你还没有实现具体代码时作为一个空的占位符。

pass 语句本身不做任何事情,不进行任何操作,仅仅是为了满足Python的语法要求。通常,它用于以下几种情况:

  1. 空函数或类:当你定义一个函数或类,但函数或类体内还没有实现具体代码时,可以使用 pass 作为占位符,使代码能够正常执行,不会因为缺少实现而报错。
def my_function():
    pass  # 这里暂时没有实现具体代码

循环或条件语句的占位:有时你可能需要在循环或条件语句中占据一个位置,但暂时不需要执行任何操作,这时可以使用 pass

for item in my_list:
    if condition:
        pass  # 暂时不需要执行任何操作
    else:
        # 其他逻辑

异常处理中的占位:在异常处理块中,你可能希望在某些情况下暂时不处理异常,可以使用 pass 占位,使代码能够继续执行。

try:
    # 一些可能会引发异常的代码
except SomeException:
    pass  # 暂时不处理异常

????????总结:pass 是一个Python中的占位符,用于在代码中占据一个语句块的位置,但不执行任何操作。它可以用于暂时跳过代码块的执行,或者作为一个空的占位符,以便在实现具体代码之前使代码能够正常执行。

sys模块

在 Python 中,sys.exit() 是一个用于退出程序的函数,它位于 sys 模块中。它可以被用于终止 Python 脚本的执行,并且可以传递一个可选的退出状态码作为参数。当没有指定退出状态码时,默认为 0,表示程序正常退出。当指定了其他非零状态码时,通常表示程序在退出时发生了某种错误或异常。

下面是 sys.exit() 的基本用法示例:

import sys

def main():
    print("Starting the program...")
    # Your program logic here
    
    # Exit the program with a normal status code (0)
    sys.exit()

if __name__ == "__main__":
    main()

你也可以传递一个整数参数给 sys.exit() 来指定退出状态码。例如:

import sys

def main():
    print("Starting the program...")
    # Your program logic here
    
    # Exit the program with a custom status code (e.g., 1)
    sys.exit(1)

if __name__ == "__main__":
    main()

需要注意的是,使用 sys.exit() 可能会在退出之前执行一些清理操作,例如关闭文件或释放资源。这可以帮助确保在程序终止时,已分配的资源得到正确释放。

总之,sys.exit() 是一个用于终止 Python 程序并返回一个退出状态码的便捷方法。在实际开发中,你可以根据需要在适当的时候使用它来控制程序的退出。

python源码编译与反编译

总结python源文件编译、反编译、加密混淆_py2exe反编译_wanzheng_96的博客-CSDN博客

del关键字删除对象(__del__方法)

在 Python 中,__del__ 是一个特殊的方法,用于定义对象在被销毁(垃圾回收)之前要执行的操作。当对象不再被引用或引用计数为零时,垃圾回收机制会自动调用该对象的 __del__ 方法。这个方法可以用来执行一些清理操作,例如释放资源、关闭文件、断开网络连接等。

__del__ 方法的语法如下:

def __del__(self):
    # 在对象被销毁前执行的操作

以下是一个简单的示例,展示了如何使用 __del__ 方法来执行一些清理操作:

class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename, 'r')
    
    def read_content(self):
        return self.file.read()
    
    def __del__(self):
        if self.file:
            self.file.close()
        print(f"FileHandler for {self.filename} has been deleted")

# 创建对象
handler = FileHandler('example.txt')
content = handler.read_content()
print(content)

# 对象销毁时将调用 __del__ 方法
del handler

在这个示例中,我们定义了一个 FileHandler 类,它在初始化时打开一个文件,并在 __del__ 方法中关闭文件。当对象被销毁时(通过 del 关键字),垃圾回收机制会自动调用 __del__ 方法来执行资源释放操作。

需要注意的是,尽管 __del__ 方法提供了在对象销毁时执行操作的机会,但它的使用可能会比较复杂,因为在何时、如何调用 __del__ 方法可能受到垃圾回收机制的影响。在一般情况下,最好的做法是使用上下文管理器(with 语句)来确保资源的正确释放,而不依赖于 __del__ 方法。

文章来源:https://blog.csdn.net/Fddadd/article/details/135772920
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。