深度理解Python迭代器

2023-05-16

我们手动的实现一个for循环:

li1 = list(range(10))

iteratorObject = iter(li1) # ❶
while 1:
try:
print(next(iteratorObject)) # ❷
except StopIteration as e: # ❸
break
❶:执行可迭代对象的__iter__()方法并保存返回的专属迭代器

❷:不断的执行迭代器的__next__()方法

❸:捕获Stopiteration异常

线性可迭代对象与迭代器的实现
如果是一个线性容器的可迭代对象,那么它一定具有索引值,我们可以让它的__iter__()方法返回一个专属的迭代器对

象。

然后专属迭代器对象中记录本次迭代遍历的索引值,根据这个索引值返回可迭代对象中的数据项,当索引值达到可迭代

对象中数据项总个数-1的时候,抛出异常,本次迭代结束:

class linearTypeContainer:
def init(self, array):
if isinstance(array, list) or isinstance(array, tuple):
self.array = array
else:
raise TypeError(“argument array must is linear container”)

def __iter__(self):
    return linearContainer_iterator(self.array)  # ❶

class linearContainer_iterator:
def init(self, array):
self.index = 0
self.array = array
self.len = len(self.array)

def __next__(self):
    if self.index < self.len:
        retDataItem = self.array[self.index]
        self.index += 1
        return retDataItem
    else:
        raise StopIteration

def __iter__(self):  # ❷
    return self

container = linearTypeContainer([i for i in range(10)])
for i in container:
print(i)
print(list(container))
❶:Python中的一切传参均为引用传递

故linearTypeContainer中的self.array和linearContainer_iterator的self.array都是一个对象,并不会额外开辟内

存空间

这也就是为什么可迭代对象创建的专属迭代器不会消耗太多的内存空间原因了。

❷:迭代器对象一定属于可迭代对象范畴,所以在这里我们为迭代器对象linearContainer_iterator类也新增了

iter()方法

这样做的好处在于如果单独的拎出了这个迭代器对象,则它也会支持for循环的遍历:

def __iter__(self):  # ❷
    return self

containerIterator = linearTypeContainer([i for i in range(10)]).iter()

for item in containerIterator:
print(item)

0

1

2

3

4

5

6

7

8

9

如果取消了linearContainer_iterator类的这个__iter__()方法,则不支持for循环的遍历:


# def iter(self): # ❷
# return self

containerIterator = linearTypeContainer([i for i in range(10)]).iter()

for item in containerIterator:
print(item)

TypeError: ‘linearContainer_iterator’ object is not iterable

非线性可迭代对象与迭代器实现
如果是一个非线性容器的可迭代对象,可以先判断它的类型,如果传入的容器是一个字典,则将迭代的数据项集合转换

为元组,里面存储的全部是字典的key即可。

如果传入的容器是一个集合,则将迭代的数据项集合转换为元组,再参照线性可迭代对象与迭代器的实现。

具体实现:

class mappingTypeContainer:
def init(self, mapping):
self.mapping = mapping
self.mappingType = None

    if isinstance(mapping, dict):
        self.mappingType = "dict"

    elif isinstance(mapping, set) or isinstance(mapping, frozenset):
        self.mappingType = "set"

    else:
        raise TypeError("argument mapping must is mapping container")

def keys(self):
    if self.mappingType == "set":
        raise TypeError("instance mapping type is set, no have method keys")
    else:
        return self.mapping

def values(self):
    if self.mappingType == "set":
        raise TypeError("instance mapping type is set, no have method values")
    else:
        return self.mapping.values()

def items(self):
    if self.mappingType == "set":
        raise TypeError("instance mapping type is set, no have method items")
    else:
        return self.mapping.items()

def __str__(self):
    return str(self.mapping)

def __iter__(self):
    return mappingContainer_iterator(tuple(self.mapping))

class mappingContainer_iterator:
def init(self, array):
self.index = 0
self.array = array
self.len = len(self.array)

def __next__(self):
    if self.index < self.len:
        retDataItem = self.array[self.index]
        self.index += 1
        return retDataItem
    else:
        raise StopIteration

def __iter__(self):
    return self

container = mappingTypeContainer({str(“k”) + str(i): str(“v”) + str(i) for i in range(3)})

for item in container.items():
print(item)

print(container)

(‘k0’, ‘v0’)

(‘k1’, ‘v1’)

(‘k2’, ‘v2’)

{‘k0’: ‘v0’, ‘k1’: ‘v1’, ‘k2’: ‘v2’}

container = mappingTypeContainer({i for i in range(3)})

for item in container:
print(item)

print(container)

0

1

2

{0, 1, 2}

迭代器对象的特性
每一次for循环创建出的可迭代对象的专属迭代器都是一次性的,用完后就没用了:

containerIterator = linearTypeContainer([i for i in range(3)]).iter()

for item in containerIterator:
print(item)

0

1

2

for item in containerIterator:
print(item) # ❷
print("?")
❶:直接拿出一个迭代器对象

❷:在第2次循环中,迭代器对象中存储的索引值已经最大了,每次调用iter()都会抛出异常返回出来再被for处理,所

以print()函数根本不会运行

迭代器对象并不存储可迭代对象中的真正迭代数据,而是仅存储长度和索引,所以内存的占用并不多:

class linearContainer_iterator:
def init(self, array):
self.index = 0 # ❶
self.array = array # ❷
self.len = len(self.array) # ❸

...

❶:占用额外的内存空间

❷:引用对象,并不开辟内存

❸:占用额外的内存空间

惰性求值与及早求值
迭代器对象中对于返回的数据项,是进行实时演算的,这种实时演算的特性求值方式被称为惰性求值,即你需要的时候

我算出来后再给你:

def __next__(self):
    if self.index < self.len:
        retDataItem = self.array[self.index]
        self.index += 1
        return retDataItem
    else:
        raise StopIteration

除开惰性求值,还有一种及早求值的方案,即使你要1个,我也把所有的都给你。

如Python2中的range()、map()、filter()、dict.items()、dict.keys()、dict.values(),它们均返回的是一个纯粹

的列表,这样的设计是不合理的。

因为返回的列表会占用很大的内存空间,而Python3中则统一优化为惰性求值方案,即返回一个可迭代对象。

要命的问题
①:Python中的所有自带容器类型为何不自己设置成迭代器?

而是在for循环时实例出一个专属的迭代器?

直接在这些自带类型的底层实现__next__()方法不好吗?

这样岂不是更加减少了内存的消耗,少定义了类和实例化了类吗?

答:这真是一个要命的问题,这个问题我也想过很久,最后是在stackoverflow提问并且获得了良好的解答才记录下来

的。

因为确实是可以实现的,如下所示,只需要在加上❶处代码即可:

class linearTypeContainer:
def init(self, array):
if isinstance(array, list) or isinstance(array, tuple):
self.array = array
else:
raise TypeError(“argument array must is linear container”)

    self.index = 0
    self.len = len(self.array)

def __iter__(self):
    return self

def __next__(self):
    if self.index < self.len:
        retDataItem = self.array[self.index]
        self.index += 1
        return retDataItem
    else:
        self.index = 0  # ❶
        raise StopIteration

container = linearTypeContainer(list(range(5)))

for item in container:
print(item)

for item in container:
print(item)

for item in container:
print(item)
但是这样做在某种特殊情况下会出现问题:

container = linearTypeContainer(list(range(5)))

for item in container:
print(item)
if container.index == 3:
break

print("*"*20)

for item in container:
print(item)

0

1

2

********************

3

4

你会发现如果第一次for循环到了1半的时候退出,第二次for循环会接着根据第一次for循环进行继续。

能够解决一下吗?只需要加上一个标志位即可:

class linearTypeContainer:
def init(self, array):
if isinstance(array, list) or isinstance(array, tuple):
self.array = array
else:
raise TypeError(“argument array must is linear container”)

    self.index = 0
    self.len = len(self.array)
    self.iter = False # ❶

def __iter__(self):
    if self.iter: # ❷
        self.index = 0
    self.iter = True
    return self

def __next__(self):
    if self.index < self.len:
        retDataItem = self.array[self.index]
        self.index += 1
        return retDataItem
    else:
        self.index = 0
        raise StopIteration

container = linearTypeContainer(list(range(5)))

for item in container:
print(item)
if container.index == 3:
break

print("*" * 20)

for item in container:
print(item)

0

1

2

********************

0

1

2

3

4

❶:判断是不是一次新的调用

❷:如果是新的调用,则将index重新置为0即可

那么为何Python不这样设计呢?我们应该更多的考虑多线程的情况下,多个for循环使用同一个迭代器它是否是线程安

全的,上面的示例中这个共享迭代器并不是线程安全的,此外它也不支持嵌套循环,如下所示,这样会造成无限循环:

container = linearTypeContainer(list(range(5)))

for item in container:
print(item)
for j in container:
print(j)
综上各个方面的考虑,Python将内置的数据类型,都设置了在for循环时返回专属迭代器的做法,这是非常好的设计,

但是对于有些内置的对象,则是将它本身做成了迭代器,如文件对象。

②:Python2中返回的及早求值对象,就没有一点好处吗?真的是浪费内存百无一用?

答:也不是,你可以发现Python3中所有返回的及早求值对象,都不支持索引操作,但是Python2中返回的由于是列表,

它能够支持索引操作,在某些极度极端的情况下这确实是个优势,但是Python3的惰性求值对象需要这种优势吗?你手

动将它转换为list不香吗?这样提供给了你更多操作性的同时优化了内存占用,何乐而不为呢?

③:你能实现一个返回惰性求值的对象吗?

答:能啊!你看,我实现一个Range吧,其实就是传参位置和自带的不一样,但是它是线程安全的且支持嵌套循环的:

class Range:
def init(self, stop, start=0, step=1):
self.start = start
self.stop = stop
self.step = step
self.current = None

def __iter__(self):
    return Range_iterator(self.stop, self.start, self.step)

class Range_iterator:
def init(self, stop, start, step):
self.start = start
self.stop = stop
self.step = step
self.current = self.start

def __next__(self):
    if self.current < self.stop:
        retDataItem = self.current
        self.current += self.step
        return retDataItem
    raise StopIteration

for i in Range(10):
print(i)
for j in Range(10):
print(j)
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度理解Python迭代器 的相关文章

随机推荐