Code前端首页关于Code前端联系我们

如何理解Python迭代对象、迭代器、生成器等数据结构?

terry 2年前 (2023-09-25) 阅读数 47 #后端开发

理解Python数据结构,有很多容器、可迭代对象(iterables)、迭代器、生成器、列表/集合/字典概念(列表、集合、听写理解)等概念混杂在一起,这难免让初学者一头雾水。在本文的帮助下,我试图阐明这些概念以及它们之间的关系。

Python 迭代对象、迭代器、生成器等数据结构如何理解?

Container(容器)

容器是一种将多个元素组织在一起的数据结构。容器的元素可以一一迭代得到。可以使用 in, no 关键字决定元素是否包含在容器中。通常,这样的数据结构将所有元素存储在内存中(有些特列并不将所有元素存储在内存中)。 Python 中常见的容器对象有:

  • list、deque、....
  • set、frozensets、....
  • dict、defaultdict、OrderedDict、Counter、....
  • tuple、namedtuple , ...
  • str

容器更容易理解,因为您可以直接放置它们。把它想象成一个盒子、一座房子、一个壁橱,任何东西都可以放进去。从技术角度来看,当可以询问它是否包含某个元素时,那么该对象就可以被认为是一个容器。例如,列表、集合和元组都是容器对象:

>>> assert 1 in [1, 2, 3]      # lists
>>> assert 4 not in [1, 2, 3]
>>> assert 1 in {1, 2, 3}      # sets
>>> assert 4 not in {1, 2, 3}
>>> assert 1 in (1, 2, 3)      # tuples
>>> assert 4 not in (1, 2, 3)

询问是否包含某个元素 使用 dict in dict key 中的 dict:

>>> d = {1: 'foo', 2: 'bar', 3: 'qux'}
>>> assert 1 in d
>>> assert 'foo' not in d  # 'foo' 不是dict中的元素

询问某个特定子字符串是否在字符串中:

>>> s = 'foobar'
>>> assert 'b' in s
>>> assert 'x' not in s
>>> assert 'foo' in s 

尽管大多数容器都提供某种方式获取其中的每个元素,这不是容器本身提供的属性。 ,但是 Iterable 对象 赋予了容器这种能力。当然,并不是所有的容器都可以迭代,比如:布隆过滤器,虽然布隆过滤器可以用来检测某个元素是否在容器中,但是并不是每一个值都可以从容器中获取,因为布隆过滤器并不能根本不将元素存储在容器中,而是将它们描述为哈希函数的值并将其存储在表中。

可迭代对象(iterable)

正如我刚才所说,很多容器都是可迭代对象。此外,还有其他对象也是可迭代对象,例如打开的文件、套接字等。任何可以返回 Iterator 的对象都可以称为可迭代对象。听起来可能有点令人困惑。不要紧。我们先看一个例子:

>>> x = [1, 2, 3]
>>> y = iter(x)
>>> z = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> next(z)
1
>>> type(x)
<class 'list'="">
>>> type(y)
<class 'list_iterator'="">

这里 x 是一个可迭代对象 可迭代对象,可迭代对象是像容器一样的通俗叫法,并不指具体的数据类型。列表是可迭代对象,字典是可迭代对象,数组也是可迭代对象。 yz 是两个独立的迭代器。迭代器保存一个状态。该状态用于存储当前迭代的位置,以便可以在下一次迭代期间检索它。正确的元素。迭代器具有特定的迭代器类型,例如 list_iteratorset_iterator。可迭代对象实现了 __iter____next__(python3 中的next、‾ __next__),这两个方法对应池塘到内置功能iter()next()。方法 __iter__ 本身返回可迭代对象,使其既是可迭代对象又是迭代器。

执行代码时:

x = [1, 2, 3]
for elem in x:
    ...

实际执行的是:
Python 迭代对象、迭代器、生成器等数据结构如何理解?

提取此代码,你会看到解释器显示

命令❙GET_TERG。 iter(x)FOR_ITER指令是调用next()

def fib():
    prev, curr = 0, 1
    while True:
        yield curr
        prev, curr = curr, curr + prev
 >>> f = fib()
 >>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

方法,但无法在持续可见的元素上获取。指令,因为解释器已经对其进行了优化。

>>> import dis
>>> x = [1, 2, 3]
>>> dis.dis('for _ in x: pass')
  1           0 SETUP_LOOP              14 (to 17)
              3 LOAD_NAME                0 (x)
              6 GET_ITER
        >>    7 FOR_ITER                 6 (to 16)
             10 STORE_NAME               1 (_)
             13 JUMP_ABSOLUTE            7
        >>   16 POP_BLOCK
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

Iterator(迭代器)

那么什么是迭代器呢?它是一个有状态对象,当您调用 next() 方法时,可以将下一个值返回到容器。任何__next__()(在python2中实现)next()

)对象都是迭代器,无论它如何实现。

所以迭代器是一个实现工厂模型的对象,每次你请求下一个值时它都会返回给你。迭代器的例子有很多。例如,函数 itertools 返回迭代器对象。

创建无限序列:

>>> from itertools import count
>>> counter = count(start=13)
>>> next(counter)
13
>>> next(counter)
14

从无限序列创建无限序列:

>>> from itertools import cycle
>>> colors = cycle(['red', 'white', 'blue'])
>>> next(colors)
'red'
>>> next(colors)
'white'
>>> next(colors)
'blue'
>>> next(colors)
'red'

从无限序列创建有限序列:

>>> from itertools import islice
>>> colors = cycle(['red', 'white', 'blue'])  # infinite
>>> limited = islice(colors, 0, 4)            # finite
>>> for x in limited:                         
...     print(x)
red
white
blue
red

>>> from itertools import islice
>>> colors = cycle(['red', 'white', 'blue'])  # infinite
>>> limited = islice(colors, 0, 4)            # finite
>>> for x in limited:                         
...     print(x)
red
white
blue
red

要更直观地认识它的实现者,迭代器,作为斐波那契数列示例:

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1
     def __iter__(self):
        return self
     def __next__(self):
        value = self.curr
        self.curr += self.prev
        self.prev = value
        return value
 >>> f = Fib()
 >>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Fib 既是可迭代对象(因为它实现了 __iter__ 方法)又是迭代器(因为它实现了 __♷❓ex__❓ 方法)。实例变量 prevcurr 用户维护迭代器内的空间。每次调用 next() 方法时,都会完成两件事:

更改下一次调用 next() 方法的状态并返回

当前调用的结果。

迭代器就像一个延迟加载的工厂。在有人需要它并返回它之前,它不会创造任何价值。当没有被调用时,它处于睡眠模式并等待下一次调用。

Generator(生成器)

生成器是Python语言最吸引人的功能之一。生成器实际上是一个特殊的迭代器,但是这个迭代器更加优雅。不再需要像上面的类一样编写方法__iter__()__next__(),只需要一个关键字

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1
     def __iter__(self):
        return self
     def __next__(self):
        value = self.curr
        self.curr += self.prev
        self.prev = value
        return value
 >>> f = Fib()
 >>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

y。生成器必须是迭代器(反之则不然),因此任何生成器即使在延迟加载模式下也会生成值。使用生成器实现斐波那契数列的一个示例是:

def fib():
    prev, curr = 0, 1
    while True:
        yield curr
        prev, curr = curr, curr + prev
 >>> f = fib()
 >>> list(islice(f, 0, 10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

fib 是一个标准的 Python 函数。它的特别之处在于,函数体中、函数的返回值中没有return关键字。该值是一个生成器对象。当执行f=fib()时,返回一个生成器对象。目前,函数体中的代码尚未执行。仅当显式或隐式调用以下内容时才会执行内部代码。 。

Generator 是 Python 中一个非常强大的编程结构。它可以用更少的中间变量编写流代码。此外,与其他容器对象相比,它可以节省内存和CPU。当然,它可以用更少的代码来实现类似的功能。现在您可以再次开始编辑代码。每当你看到类似:

def something():
    result = []
    for ... in ...:
        result.append(x)
    return result

之类的东西时,你都可以用生成器函数来替换它:

def iter_something():
    for ... in ...:
        yield x

生成器表达式(生成器表达式)

生成器表达式是列表推送。生成器版本看起来像列表理解,但它返回生成器对象而不是列表对象。

>>> a = (x*x for x in range(10))
>>> a
 at 0x401f08>
>>> sum(a)
285

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门