暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

python面向对象编程篇(16):你应该需要掌握了解的-迭代器和生成器

小儿来一壶枸杞酒泡茶 2021-03-19
202

个人其他博客地址:

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

小钟同学 | 文 【原创】| QQ:308711822

  • 1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
  • 2:关于文章内容,有部分内容参考自互联网,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。

前言

在python中还包含其他很多的内置的魔法方法,之前大部分的有相关了解介绍过,但是其中还有一个关于可迭代对象的函数问题。iter()方法。在说__iter__()之前我们需要明确几个概念的意思:

  • 可迭代的对象(iterable)
  • 迭代器(iterator)
  • 生成器(generator)

为什么需要了解这么多概念,因为__iter__()方法它返回是一个迭代器。所以对上述几个概念都需要有所了解。

实践总结

1、什么是迭代?

迭代可以理解为:

  • 重复循环执行一系列运算操作,依次的向前推导的过程,而每一次循环的到的的结果,会作为下一次循环的初始值
  • 对集合元素(可迭代序列)的访问调取的一种方式

2、什么是可迭代的对象(iterable)?

可迭代的对象可以理解为:

不是指某种具体的数据类型,而是指存储了元素的一个容器对象,且容器中的元素为一个拥有__iter__()方法的对象,可以使用for循环遍历的对象(可迭代序列),都可以称为叫做可迭代的对象。

可迭代的对象表示的是:

  • 它是一个数据流
  • 它可以被next()函数调用并不断返回下一个元素的值

在python中,常见可以使用for循环变量的对象有:

  • list
  • dict
  • tuple
  • set
  • file(文件是可迭代对象、也是迭代器)
  • iterator(迭代器)
  • generator(生成器)

从上面看,iterable和iterator是不同的概念。


判断一个对象是否是可迭代对象(重复做一些事很多次)的方法是:
from collections import Iterable
print("字符串是否是可以迭代的对象:",isinstance('abc', Iterable))
print("===========================")
mylist = [1,2,3,4,5]
print("列表是否是可以迭代的对象:",isinstance(mylist,Iterable))
print("===========================")
mydict = {'xiaozhong':'小钟同学'}
print("字典是否是可以迭代的对象:",isinstance(mydict,Iterable))
print("===========================")
mytuple = (1, 2, 3, 4)
print("元组是否是可以迭代的对象:",isinstance(mytuple,Iterable))
print("===========================")
f = open('myuses.dot')
isinstance(f, Iterable) # 文件是可迭代对象、也是迭代器
print("文件是否是可以迭代的对象:",isinstance(mytuple,Iterable))

输出的结果为:

from collections import Iterable
D:/code/python/local_python/AntFlask/sdsd.py:3: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
字符串是否是可以迭代的对象:True
===========================
列表是否是可以迭代的对象:True
===========================
字典是否是可以迭代的对象:True
===========================
元组是否是可以迭代的对象:True
===========================
文件是否是可以迭代的对象:True


另一种方式是否是iterator对象的方法:
:iterator对象都有'__iter__'方法:

hasattr(obj, '__iter__')

补充关于文件迭代:
举例:用迭代器的方式访问文件
for line in open(“myuses.dot”):print(line)
意思是:每次读取一行就输出一行,而不是一次性将整个文件读入,这样可以节约内存

有异常:

 DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working

PS:原因:由于在python3.8中不再支持collections,建议使用collections.abc

from collections import Iterable
改为

from collections.abc import Iterable

修改后就不会再异常提示输出

从上面可以看得出iterable 不是iterator,但是我们的可以把 可迭代的对象转换为一个迭代器, 可以通过内置函数iter()来把iterable转变为迭代器Iterato。

示例如:

list=[1,2,3,4]
it = iter(list)    # 传入的参数是(iterable)返回一个迭代器对象(iterator)
#!/usr/bin/python3
 

import sys  # 引入 sys 模块
list = [1, 2, 3, 4] #iterable变量的创建
it = iter(list)  # 传入的参数是(iterable变量)返回一个迭代器对象(iterator)变量
while True:
    try:
        print(next(it))
    except StopIteration:
        sys.exit()

3、什么是迭代器(iterator)?

一个类如果它实现了__iter__()和__next__()方法,那么它对应的对象都是迭代器。

从定义看,能叫做迭代器的对象,必然包含了几个特征:

  • 它是一个对象
  • 它是一个可以记住遍历容器的位置的对象(容器是把多个元素组织一起的一种数据结构)
  • 对象必须是定义了__iter__()方法和next()方法
  • 它可以使用__next__()对容器对象(可迭代序列)进行循环遍历,在循环遍历的过程只能前进不允许后退
  • 它可以记住遍历位置(索引),便于下次__next__的时候直接从上一次的位置开始
  • 每次调用__next__()该对象时会返回容器的自身的下一个元素
  • 遍历有终结的时候,遍历到最后,没有元素的时抛出StopIteration异常,方便调用者知道迭代已完成
  • 迭代器不能提前知道需要遍历容器对象可迭代序列的长度,它计算是惰性,也可以说是惰性求值(需要时再取值计算,这样可以节约内存)。
  • next()函数的对象都是Iterator类型(它传入的是迭代器的对象,不是可迭代的迭代对象iterable)

首先__iter__()和__next__()是两个相辅相成两个概念。

  • __ iter__返回是迭代器自身
  • __ next__返回是容器中的下一个值

示例如:

list=[1,2,3,4]
it = iter(list)    # 传入的参数是(iterable)返回一个迭代器对象(iterator)
#!/usr/bin/python3
 

import sys  # 引入 sys 模块
list = [1, 2, 3, 4] #iterable变量的创建
it = iter(list)  # 传入的参数是(iterable变量)返回一个迭代器对象(iterator)变量
while True:
    try:
        print(next(it))
    except StopIteration:
        sys.exit()

PS:it = iter(list)  # 传入的参数是(iterable变量)返回一个迭代器对象(iterator)变量

判断一个对象是否是迭代器(重复做一些事很多次)的方法:
#!/usr/bin/evn python
# coding=utf-8
from collections.abc import Iterator
print("字符串是否是迭代器:",isinstance('abc', Iterator))
print("===========================")
mylist = [1,2,3,4,5]
print("列表是否是迭代器:",isinstance(mylist,Iterator))
print("===========================")
mydict = {'xiaozhong':'小钟同学'}
print("字典是否是迭代器:",isinstance(mydict,Iterator))
print("===========================")
mytuple =(1,3,4)
print("元组是否是迭代器:",isinstance(mytuple,Iterator))
print("===========================")
f = open('myuses.dot'# 文件是可迭代对象、也是迭代器
print("文件是否是迭代器:",isinstance(f,Iterator))
print("===========================")
# 生成器是否是generator迭代器
mygenerator =(x for x in range(10))
print("生成器是否是迭代器:",isinstance(mygenerator,Iterator))

输出结果:

字符串是否是迭代器:False
===========================
列表是否是迭代器:False
===========================
字典是否是迭代器:False
===========================
元组是否是迭代器:False
===========================
文件是否是迭代器:True
===========================
生成器是否是迭代器:True

4、for循环的本质是什么?

for循环遍历iterable的本质是通过iter()函数获取可迭代对象iterable的迭代器(iterator),然后对获取到的迭代器(iterator)不断调用next()函数获取下一个值,当遇到StopIteration的异常时循环结束。

本质:

import sys  # 引入 sys 模块
list = [1, 2, 3, 4] #iterable变量的创建
it = iter(list)  # 传入的参数是(iterable变量)返回一个迭代器对象(iterator)变量
while True:
    try:
        print(next(it))
    except StopIteration:
        sys.exit()

等于:

list = [1, 2, 3, 4] #iterable变量的创建
for u in list:pass

PS:上面的next(it)函数本质上还是调用的__next__()方法

5、可迭代对象 和 迭代器对象定义区别

可迭代对象: 调用对象的__iter__方法能够返回迭代器对象的一种对象。

迭代器对象:是实现了迭代器协议的对象,就是实现了__iter__()和__next__()的对象。

自定义一个迭代器示例:

“迭代器”类两个特性:

  • 1、有__iter__()方法,返回迭代器本身
  • 2、有__next__()方法,返回容器的下一个元素或有条件的进行适时抛出StopIteration异常,防止无限循环
  • 3、需要使用__iter__()返回迭代器对象自身

示例代码:

from collections.abc import Iterator


class IteratorNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        x = self.a
        self.a += 1
        return x


mynum = IteratorNumbers()
print("IteratorNumbers是否是迭代器:",isinstance(mynum, Iterator))
# 转换为迭代器对象----
myiter = iter(mynum) # 这一步是必须的,否则使用next 进行迭代则提示找不到a
print("IteratorNumbers是否是迭代器:",isinstance(myiter, Iterator))
# 开始使用next 进行迭代
print(next(mynum))
print(next(mynum))
print(next(mynum))


运行结果为:

IteratorNumbers是否是迭代器:True
IteratorNumbers是否是迭代器:True
1
2
3

PS:# mynum.iter() 和 iter(mynum)是一样的原来,主要是返回的是迭代器的本身

所以上面的代码也可以是:

from collections.abc import Iterator


class IteratorNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        x = self.a
        self.a += 1
        return x


mynum = IteratorNumbers().__iter__()
print("IteratorNumbers是否是迭代器:",isinstance(mynum, Iterator))
# 开始使用next 进行迭代
print(next(mynum))
print(next(mynum))
print(next(mynum))



使用For循环变量我们的自定义的迭代器对象:

class IteratorNumbers:

    def __init__(self,max):
        self.max = max

    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a<= self.max:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration
        
mynum = IteratorNumbers(3)# 最大循环次数是3
for i in mynum:
    print(i)

输出结果为:
1
2
3

使用迭代器对象进行自身的遍历的示例:


class IteratorNumbers:

    def __init__(self,max):
        self.max = max

    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a<= self.max:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration

mynum = IteratorNumbers(3)
print(mynum.__iter__().__next__())

PS:print(mynum.iter().next())

意思是:我先返回迭代器对象,再调用__next__

6、什么是生成器

定义

两种形式:

  • 函数式生成器:一个使用了 yield 的函数被称为生成器(generator),它是一个函数内部具有yield
    关键字的函数

  • 表达式生成器:使用 (结果 for 变量 in 可迭代对象 if 条件筛选) 的个表达式生成一个generator对象

PS:列表表达式 的结构:[结果 for 变量 in 可迭代对象 if 条件筛选]

缺点:列表生成式一次生成一个列表需若干列表的元素,每个元素都生产占据对应的内存空间,所以需很大的内存空间

生成器不要分配一个列表,只需每次循环一次仅生成一个元素,生成后,可以元素对象看了眼被回收丢弃,下次循环直接再取下一个,这样就不仅可以节省了内存空间,还可以和列表一样进行遍历。

  • 生成器一次仅生成一个元素即节省了内存空间又可以获取完整的列表内容。

特性:

  • 1.生成器它其实是一种非常特殊的迭代器,它内部自动实现了"迭代器协议"(即__iter__
    __next__
    法)

  • 2.这个特殊的生成器在迭代过程中可以改变当前迭代的值。

  • 3.函数内部具有yield
    关键字

  • 4.调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时在从当前位置继续运行

  • 5.它可以一边循环一边计算。只获取元素,下次遍历再取。

  • 6.函数式生成器中yield 的目的是把一个普通函数变成一个具备generator特性的函数,不过注意这个函数类型它还是一个函数,只有把这个函数给与一个变量时候才真正的转正为一个generator对象。

  • 生成器只能使用一次,再次使用需重新初始化一个新的生成器。

表达式生成器示例:

关于表达式生成器需要主要是,外层是一个(),如上面的示例:

# 生成器是否是generator迭代器
mygenerator =(x for x in range(10))
print("生成器是否是迭代器:",isinstance(mygenerator,Iterator))

函数式生成器示例:

from collections.abc import Iterator


def mygenerator_fun():
    yield "第1次-迭代循环"
    yield "第2次-迭代循环"
    yield "第3次-迭代循环"


print("生成器是否是迭代器:", isinstance(mygenerator_fun, Iterator)) # false
mygen = mygenerator_fun()
print("生成器是否是迭代器:", isinstance(mygen, Iterator)) # true

# 第1种遍历方式:
print(mygen.__iter__().__next__())
print(mygen.__iter__().__next__())
print(mygen.__iter__().__next__())
# 下面这个会提示StopIteration因为已经可以迭代的元素了
print(mygen.__iter__().__next__())

# 第2种遍历方式:
print(next(mygen))
print(next(mygen))
print(next(mygen))
# 下面这个会提示StopIteration因为已经可以迭代的元素了
print(next(mygen))

# 第3种遍历的方式
for i in mygen:
    print(i)
# 计算你下面再变量也没数据滴,不会执行滴
for i in mygen:
    print(i)
#
# 第4种遍历的方式
for i in mygenerator_fun():
    print(i)

7、生成器几个应用场景:

  • 协程
  • 上下文管理器
  • yield from

7.1 解决大内存占用问题

没有生成器的情况下:

from memory_profiler import profile

@profile(precision=4)
def get_list_01():
    a = []
    for i in (range(100000)):
        temp = ['小钟'] * 2000
        a.append(temp)

    return a

if __name__ == '__main__':
    a = get_list_01()
    for ele in a:
        pass
        # print(ele)
# print(ele)



输出结果:

Filename: D:/code/python/local_python/AntFlask/sdsd.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
     5  40.5430 MiB  40.5430 MiB           1   @profile(precision=4)
     6                                         def get_list_01():
     7  40.5430 MiB   0.0000 MiB           1       a = []
     8 1637.2656 MiB  -0.5625 MiB      100001       for i in (range(100000)):
     9 1637.2656 MiB 1591.3125 MiB      100000           temp = ['小钟'] * 2000
    10 1637.2656 MiB   4.2852 MiB      100000           a.append(temp)
    11                                                 # yield temp
    12 1637.2656 MiB   0.0000 MiB           1       return a


有了生成器之后:

from memory_profiler import profile




def get_list_02():
    for i in (range(100000)):
        temp = ['小钟'] * 2000
        yield temp

@profile(precision=4)
def ceshi():
    a = get_list_02()
    for ele in a:
        pass
        # print(ele)


if __name__ == '__main__':
    ceshi()
# print(ele)



输出结果:

Filename: D:/code/python/local_python/AntFlask/sdsd.py

Line #    Mem usage    Increment  Occurences   Line Contents
============================================================
    13  40.5547 MiB  40.5547 MiB           1   @profile(precision=4)
    14                                         def ceshi():
    15  40.5547 MiB   0.0000 MiB           1       a = get_list_02()
    16  40.7539 MiB   0.1992 MiB      100001       for ele in a:
    17  40.7539 MiB   0.0000 MiB      100000           pass


参数说明:

line  已分析的代码的行号 Mem usage:表示执行该行后Python解释器的内存使用情况 Increment:表示当前行的内存相对于上一行的差异,即自己本身增长了多少,如果减少了则不显示.

从上面很明细的看得出具体的占用内存的问题!

7.2 首先是装饰器形式上下文管理器


from contextlib import contextmanager


@contextmanager
def session_scope():
    session = GetSession()
    if session.is_closed():
        # session.connection_context()
        session.connect()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

7.3 协程---后面到异步编程的时候再解说一下。

参考资料

https://www.cnblogs.com/LaoYuanStudyPython/p/12950052.html

https://www.cnblogs.com/zf-blog/p/10613533.html

https://www.runoob.com/python3/python3-iterator-generator.html


文章转载自小儿来一壶枸杞酒泡茶,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论