首页 > 编程语言 >Python面试_可迭代对象&迭代器&生成器

Python面试_可迭代对象&迭代器&生成器

时间:2023-02-09 15:23:10浏览次数:41  
标签:__ 迭代 Python self 生成器 iter 对象


# 可迭代对象&迭代器
> **可迭代对象**:使用内置的iter()函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么对象就是是可迭代的。序列都可以迭代;实现了__getitem__方法,而且其参数是从零开始的索引,这样的对象也可以迭代。
> 
> **迭代器**:实现了无参数的__next__方法,返回序列中的下一个元素,如果没有元素来了,那么抛出StopIteration异常。在Python中,迭代器还实现了__iter__方法(返回本身),因此迭代器也可以迭代。

## 序列都是可迭代的
为了“序列都可以迭代”,我们先看一个实现了序列协议的类:Sentence(通过索引从文本中提取单词):
```
import re

RE_WORD = re.compile('\w+')


class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __getitem__(self, index):
        return self.words[index]

    def __len__(self):
        return len(self.words)


s = Sentence('hello wolrd')
for word in s:
    print(word)
​------------------------------
hello
world
```
从这个例子中我们知道:Sentence实例(也就是一个**序列**)是**可以迭代**的。
## python内置的iter函数
Question思考一个问题,我们自定义的序列Sentence是没有实现__iter__方法返回迭代器,Python为什么也可以从中获取到迭代器呢?
为了理解上面的问题,我们需要了解Python内置的iter函数。
Python解析器需要迭代对象x时,会自动调用iter(x),内置的iter函数有以下作用:
(1)检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器;
(2)如果没有实现__iter__方法,但是实现了__getitem__方法,Python解释器会创建一个迭代器,尝试按顺序(从索引0开始),获取元素;(之所以这样处理是为了向后兼容);
```
s = Sentence('hello wolrd')
it = iter(s)  # 通过iter()获取一个迭代器
print(it)  # <iterator object at 0x7fc53807c5f8>
print(type(it))  # <class 'iterator'>

while True: # 模拟for in 循环
    try:
        word = next(it)
    except StopIteration as e:
        del it
        break
    print(word)

​-----------------------------
<iterator object at 0x7fc53807c5f8>
<class 'iterator'>

hello
world
```
(3)如果尝试失败,Python解释器会抛出TypeError异常,通常会提示“object is not iterable”。

通过理解Python内置的iter函数,我们可以知道:

- **任何Python序列都可迭代(其实标准的序列也都实现了__iter__方法)**
- **Python会从可迭代的对象中获取迭代器**
## 典型的迭代器
> 可迭代对象一定不能是自身的迭代器。也就是说可迭代对象必须实现__iter__方法,但是不能实现__next__方法。
> 另一方面,迭代器的__iter__方法应该返回自身。

为了清楚的说明可迭代的对象和迭代器之间的重要区别,我们再看一个使用迭代模式实现的Sentence类:
```
import re

RE_WORD = re.compile('\w+')


class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __iter__(self):
        return SentenceIterator(self.words)  # 根据可迭代协议,实例化并返回一个迭代器


class SentenceIterator:
    def __init__(self, words):
        self.words = words
        self.index = 0

    def __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

    def __iter__(self):
        return self  # 迭代器返回自身
```

Question1为什么迭代器需要实现__iter__方法?

- 以便于在应该使用可迭代对象的地方使用迭代器,例如for循环
- 迭代器可以通过issubclass(SentenceInterator, abc.Iterator)的测试
Question2为什么可迭代对象不能实现__next__方法,让自身即是可迭代对象,也是一个迭代器呢?

因为这样做是反设计模式的。、
在《设计模式:可复用面向对象软件的基础》一书讲解迭代器设计模式是,在“适用性”一节中说,迭代模式可以用来

- 访问一个聚合对象的内容而无需暴露它内部的接口;
- 支持对聚合对象的多种遍历;
- 为遍历不同的聚合结构提供一个统一的接口;

为了“支持多种遍历”,必须能从同一个可迭代对象中获取多个独立的迭代器,而且各个迭代器都要能维护自身的内部状态,也就是说每次调用iter(my_iterable)都新建一个独立的迭代器。

生成器

生成器函数

  • 生成器函数:函数体内有yield关键字
  • 函数返回值:生成器对象
  • 函数执行顺序:在生成器对象每次调用next()时执行,遇到yield语句返回,再次调用执行时从上次返回的yield语句处继续执行
def generator_func():
    print("step 1")
    yield 1
    print("step 2")
    yield 2
    print("step 3")
    yield 3


def normal_func():
    return 1


if __name__ == '__main__':
    g = generator_func()  # <generator object generator_func at 0x7ff0908cade0>
    n = normal_func()  # n: 1
    # print(dis.dis(generator_func))
    for i in g:
        print("generator obj res: ", i)

"""
step 1
generator obj res:  1
step 2
generator obj res:  2
step 3
generator obj res:  3
"""

函数执行

从下面的例子中,让我们来了解函数的执行过程:

import inspect

frame = None


def foo():
    bar()


def bar():
    global frame
    frame = inspect.currentframe()  # 返回当前栈帧对象


foo()
print(frame.f_code.co_name)  # 打印栈帧对象名 --> bar
caller_frame = frame.f_back  # 获取调用栈帧对象
print(caller_frame.f_code.co_name)  # 打印调用栈帧对象名 --> foo

python中函数调用的执行过程:
1)python.exe会通过C实现的函数:PyEval_EvalFrameEx()去执行定义的Python foo()函数

  • 首先会创建一个栈帧对象(FrameObject)
  • 栈帧对象会去执行foo()函数的Python字节码

2)当执行到调用bar()函数时

  • 会再创建一个栈帧对象(FrameObject)
  • 新的栈帧对象会去执行bar()函数的Python字节码

流程如图:
image.png
Python中函数的调用类似递归的过程,所有的堆栈对象都分配在堆内存上(不会随着函数执行完而销毁),这就决定了堆栈对象可以独立于调用者而存在。

生成器函数执行

CPython解释器在编译Python字节码时就会生产生成器对象,C函数PyEval_EvalFrameEx()会在堆栈对象的基础上进行封装为生成器对象(PyGenObject),生成器对象(PyGenObject),gi_frame指向一个新的PyFrameObject,其中保存了生成器对象上次执行的字节码位置(f_lasti),而gi_code则指向PyCodeObject用于保存生成器函数的字节码。

image.png

通过一个例子来看看其具体过程,首先通过dis来打印出生成器函数的Python字节码:

def generator_func():
    yield 1
    name = "zgt"
    yield 2
    age = 18
    return "hello generator"


print(dis.dis(generator_func))

image.png
再通过next()函数来执行生成器对象,并打印其堆栈对象中内容:

g = generator_func()
print(g.gi_frame.f_lasti)   # -1
print(g.gi_frame.f_locals)  # {}

next(g)
print(g.gi_frame.f_lasti)   # 2
print(g.gi_frame.f_locals)  # {}

next(g)
print(g.gi_frame.f_lasti)   # 12
print(g.gi_frame.f_locals)  # {'name':'zgt'}

对比生成器函数的字节码,我们就可以理解Python的生成器的独特aazA处(延时求值)的实现原理。

标签:__,迭代,Python,self,生成器,iter,对象
From: https://www.cnblogs.com/zzggtt/p/17105420.html

相关文章

  • 前端学习案例1-迭代器接口和for...of1
      ......
  • CentOS安装python3
    一、安转相关依赖使用yuminstallgccpatchlibffi-develpython-develzlib-develbzip2-developenssl-develncurses-develsqlite-develreadline-develtk-devel......
  • python之路67 drf从入门到成神 8 接口文档、jwt介绍和原理、drf-jwt快速使用、定制返
    接口文档前后端分离我们做后端,写接口前端做前端,根据接口写app、pc、小程序作为后端来讲、我们很清楚,比如登录接口/api/v1/login/----》post-------》us......
  • 凑个小热闹:python采集《狂飙》评论
    2023年首部爆款剧集《狂飙》一度冲上热搜第一,害的我两倍速熬夜看完了。  “是非面前稍不留神,就会步入万丈深渊,唯有坚守信仰,才能守得初心”  面对这么多广大网......
  • python数据抓取,抓点星星网的内容
    代码:#coding=utf-8importos,sys,reimportrequestsfromwebob.excimportstrip_tagsfromxpinyinimportPinyindefstr2dict(str):dict={}groups1......
  • Python-celery介绍与快速上手
    1.celery介绍:  celery是一个基于Python开发的模块,可以帮助我们在开发过程中,对任务进行分发和处理。            详细介绍取自:Python之celery的简介与使......
  • Python之ruamel.yaml模块详解(一)
    (Python之ruamel.yaml模块详解(一))1ruamel.yaml简介ruamel.yaml是一个yaml解析器;ruamel.yaml是一个用于Python的yaml1.2加载器/转储程序包;它是PyYAML3.11的衍生产品;r......
  • python多维数组的每列的最值
    python代码实现importnumpyasnpdefmaxmin(array):#求每列的最值maxlist=[]minlist=[]foriinrange(len(array[0])):#行数col=[]......
  • python3 时间戳转换
    importtimedeftime_conversion(times):#转换成新的时间格式(2016-05-0520:28:54)dt=time.strftime("%Y-%m-%d%H:%M:%S",time.localtime(times))......
  • Python 分支结构
    阅读目录​​应用场景​​​​if语句的使用​​​​例子1:英制单位英寸与公制单位厘米互换​​​​例子2:百分制成绩转换为等级制成绩​​​​例子3:输入三条边长,如果能构成三......