Python迭代器与生成器

第一章、迭代器与生成器

一、概述

        迭代器(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):
        print("调用__iter__")
        return self

    def __next__(self):
        print("调用__next__")
        if self.current < self.max_value:
            result = self.current
            self.current += 1
            return result
        else:
            raise StopIteration


my_iterator = MyIterator(5)
print(my_iterator.__str__()) # <__main__.MyIterator object at 0x0000028C66AEC5F8>
print("================================================================")
# for num in my_iterator:
#     print(num)  # 输出: 0, 1, 2, 3, 4
"""
================================================================
调用__iter__
调用__next__
0
调用__next__
1
调用__next__
2
调用__next__
3
调用__next__
4
调用__next__
"""

print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))

"""
================================================================
调用__next__
0
调用__next__
1
调用__next__
2
调用__next__
3
调用__next__
4
调用__next__
Traceback (most recent call last):
  File "C:\Users\13476\Desktop\Python\python-fundamentals\迭代器与生成器\迭代器\test_iterator01.py", line 50, in <module>
    print(next(my_iterator))
  File "C:\Users\13476\Desktop\Python\python-fundamentals\迭代器与生成器\迭代器\test_iterator01.py", line 21, in __next__
    raise StopIteration
StopIteration
"""

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) # <generator object my_generator at 0x000001E09F768410>
print(next(gen)) #0
print(next(gen)) #1
print(next(gen)) #2
print(next(gen)) #3
print(next(gen)) #4
print(next(gen)) # StopIteration

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

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

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

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

1. 实现方式

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

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

2、简洁性

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

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

3. 内存占用

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

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

4. 使用情景

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

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

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

第二章、yield关键字

一、概述

        yield 是 Python 中一个非常强大的关键字,用于创建生成器函数。生成器函数是一种特殊类型的函数,它可以在执行过程中暂停并保存当前状态,然后在需要时恢复执行。这使得生成器函数可以按需生成序列,而不需要一次性将所有值存储在内存中,这对于处理大型数据集或无限序列尤为有用。

1、yield和return区别

        yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用 yield 的函数 叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator 每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。

        yield可以简单理解为return操作,但和return又有很大的区别,执行完return,当前函数就终止了,函数内部的所有数据,所占的内存空间,全部都没有了。而yield在返回数据的同时,还保存了当前的执行内容,当你再一次调用这个函数时,他会找到你在此函数中的yield关键字,然后从yield的下一句开始执行。

        当有多个返回值时,用 return 全部一起返回了,需要单个逐一返回时可以用 yield。

2、带有yield的函数是生成器,调用这个函数内部代码并不会立即执行,而是返回一个生成器对象

        带有yield的函数在Python中被称之为generator(生成器),也就是说,当你调用这个函数的时候,函数内部的代码并不立即执行 ,这个函数只是返回一个生成器(Generator Iterator)。

def generator():
    print("Generating")
    for i in range(10):
        yield i * i

gen = generator()
print(gen)
# <generator object generator at 0x00000215DD1705F0>

二、yield执行原理

yield执行原理

1、代码运行顺序 

  1. 在函数第一次调用next(生成器对象)函数时,generator函数从开始执行到yield,并返回yield之后的值。
  2. 在函数第二次调用next(生成器对象)函数时,generator函数从上一次yield结束的地方继续运行,直至下一次执行到yield的地方,并返回yield之后的值。依次类推。

        首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

就这么简单的几行代码就让你明白什么是yield,代码的输出这个:

starting...
4
********************
res: None
4

我直接解释代码运行顺序,相当于代码单步调试:

  1. 程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
  2. 直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环
  3. 程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,
  4. 程序执行print("*"*20),输出20个*
  5. 又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,
  6. 程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

2、实际上将数据返回至调用next函数处

def generator():
    for i in range(10):
        yield i * i


gen = generator()
print(gen) # <generator object generator at 0x000001DB8AB82110>

print("first:", end="")
print(next(gen)) # first:0

print("second:", end="")
print(next(gen)) # second:1

3、yield组合生成器

def foo(num):
    print("starting...")
    while num<10:
        num=num+1
        yield num
    print("over")
for n in foo(0):
    print(n)
"""
starting...
1
2
3
4
5
6
7
8
9
10
over
"""

三、一个yield的生成器

yield案例详解

只要在def函数里面看到有 yield 关键字那么就是生成器

1、案例一 

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')


if __name__ == '__main__':
    g = mygenerater(2)
    # 获取生成器中下一个值
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    """
    开始生成...
    0
    完成一次...
    开始生成...
    1
    """

2、案例二

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')

if __name__ == '__main__':

    g = mygenerater(3)
    # 获取生成器中下一个值
    # result = next(g)
    # print(result)
    # result = next(g)
    # print(result)
    #
    while True:
        try:
            result = next(g)
            print(result)
        except StopIteration as e:
            break
    """
    开始生成...
    0
    完成一次...
    开始生成...
    1
    完成一次...
    开始生成...
    2
    完成一次...
    """

3、案例三

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')


if __name__ == '__main__':

    g = mygenerater(3)
    # 获取生成器中下一个值
    # result = next(g)
    # print(result)
    # result = next(g)
    # print(result)
    #
    # while True:
    #     try:
    #         result = next(g)
    #         print(result)
    #     except StopIteration as e:
    #         break
    #
    # for遍历生成器, for 循环内部自动处理了停止迭代异常,使用起来更加方便
    for i in g:
        print(i)
        """
        开始生成...
        0
        完成一次...
        开始生成...
        1
        完成一次...
        开始生成...
        2
        完成一次...
        """

代码说明:

  • 代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
  • 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration 异常,表示停止迭代异常
  • while 循环内部没有处理异常操作,需要手动添加处理异常操作
  • for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐大家使用。

四、多个yield的生成器

一次只能返回一个,多个yield关键字返回的内容轮流一次返回

1、案例一

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')
        yield i + 100
        print('完成二次...')
        print("生成一轮结束")


if __name__ == '__main__':
    g = mygenerater(3)
    # 获取生成器中下一个值
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    """
    开始生成...
    0
    完成一次...
    100
    """

2、案例二

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')
        yield i + 100
        print('完成二次...')
        print("生成一轮结束")


if __name__ == '__main__':
    g = mygenerater(3)
    # 获取生成器中下一个值
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    """
    开始生成...
    0
    完成一次...
    100
    完成二次...
    生成一轮结束
    开始生成...
    1
    """

3、案例三

def mygenerater(n):
    for i in range(n):
        print('开始生成...')
        yield i
        print('完成一次...')
        yield i + 100
        print('完成二次...')
        print("生成一轮结束")


if __name__ == '__main__':
    g = mygenerater(3)
    # 获取生成器中下一个值
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    result = next(g)
    print(result)
    """
    开始生成...
    0
    完成一次...
    100
    完成二次...
    生成一轮结束
    开始生成...
    1
    完成一次...
    101
    """

 缓存机制

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__ 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值