【问题标题】:What is the 2nd argument for the iter function in Python?Python 中 iter 函数的第二个参数是什么?
【发布时间】:2016-10-28 02:51:10
【问题描述】:

让我们考虑一个文件:

$ echo -e """This is a foo bar sentence .\nAnd this is the first txtfile in the corpus .""" > test.txt
$ cat test.txt 
This is a foo bar sentence .
And this is the first txtfile in the corpus .

当我想按字符读取文件时,我可以https://stackoverflow.com/a/25071590/610569

>>> fin = open('test.txt')
>>> while fin.read(1):
...     fin.seek(-1,1)
...     print fin.read(1),
... 
T h i s   i s   a   f o o   b a r   s e n t e n c e   . 
A n d   t h i s   i s   t h e   f i r s t   t x t f i l e   i n   t h e   c o r p u s   .

但是使用 while 循环可能看起来有点不合 Python。当我使用fin.read(1) 检查EOF 然后回溯以读取当前字节时。所以我可以做这样的事情How to read a single character at a time from a file in Python?:

>>> import functools
>>> fin = open('test.txt')
>>> fin_1byte = iter(functools.partial(fin.read, 1), '')
>>> for c in fin_1byte:
...     print c,
... 
T h i s   i s   a   f o o   b a r   s e n t e n c e   . 
A n d   t h i s   i s   t h e   f i r s t   t x t f i l e   i n   t h e   c o r p u s   .

但是当我在没有第二个参数的情况下尝试它时,它会抛出一个 TypeError:

>>> fin = open('test.txt')
>>> fin_1byte = functools.partial(fin.read, 1)
>>> for c in iter(fin_1byte):
...     print c,
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'functools.partial' object is not iterable

iter 中的第二个参数是什么? 文档也没有说太多:https://docs.python.org/2/library/functions.html#iterhttps://docs.python.org/3.6/library/functions.html#iter


根据文档:

返回一个迭代器对象。根据第二个参数的存在,第一个参数的解释非常不同。如果没有第二个参数,object 必须是支持迭代协议(iter() 方法)的集合对象,或者它必须支持序列协议(getitem()具有从 0 开始的整数参数的方法)。如果它不支持这些协议中的任何一个,则会引发 TypeError。如果给定第二个参数 sentinel,则 object 必须是可调用对象。在这种情况下创建的迭代器将在每次调用其 next() 方法时调用不带参数的对象;如果返回的值等于 sentinel,则会引发 StopIteration,否则将返回该值。

我猜文档需要一些“解密”:

  • 如果没有第二个参数,对象必须是支持迭代协议(iter() 方法)的集合对象

这是否意味着它需要来自collections?还是说只要对象有__iter__()就可以了?

  • ,或者它必须支持序列协议(getitem() 方法,整数参数从 0 开始)

这是相当神秘的。那么这是否意味着它试图查看序列是否被索引并因此可以查询并且索引必须从 0 开始?这是否也意味着索引需要是连续的,即 0, 1, 2, 3, ... 而不是 0, 2, 8, 13, ...?

  • 如果它不支持其中任何一个协议,则会引发 TypeError。

是的,这部分,我明白了=)

  • 如果给定第二个参数 sentinel,则 object 必须是可调用对象。

好的,现在这有点科幻了。将某些东西称为sentinel 只是 Python 中的一个术语吗? sentinel 在 Python 上是什么意思?而“可调用对象”就像它是一个函数而不是类型对象?

  • 在这种情况下创建的迭代器将在每次调用其 next() 方法时调用不带参数的对象;

这部分我不太明白,也许举个例子会有所帮助。

  • 如果返回值等于sentinel,StopIteration 将被引发,否则返回值。

好的,所以sentinel 这里指的是一些破坏标准?

有人可以帮助破译/澄清上述关于iter的观点的含义吗?

【问题讨论】:

  • 在我看来,文档准确地解释了第二个参数是什么。解释从“如果给出第二个参数,哨兵,”开始,一直到段落的结尾。你没有得到什么?
  • 文档很有用:iter
  • 别管骗子的Java标签;它普遍适用。
  • 如果@Antti 没有击中我的欺骗目标,你就会得到答案。我不知道他为什么这样做。
  • iter(func, sentinel) 的第二种形式只是重复调用func() 直到它返回sentinel 然后iter() 引发StopIteration。这意味着func() 通常是有状态的或随机的。在您的示例中,fin.read 将一次返回 1 个字符,并在到达文件末尾时返回空字符串 ''

标签: python arguments iterable built-in


【解决方案1】:

对于一个参数,iter 必须被赋予一个具有 __iter__ 特殊方法的对象, __getitem__ 特殊方法。如果它们都不存在,iter 引发错误

>>> iter(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not iterable

有 2 种迭代协议。旧协议依赖于调用__getitem__ 来获取从0 到引发IndexError 的连续整数。新协议依赖于从__iter__ 返回的迭代器。

在 Python 2 中,str 甚至没有 __iter__ 特殊方法:

Python 2.7.12+ (default, Sep 17 2016, 12:08:02) 
[GCC 6.2.0 20160914] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 'abc'.__iter__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__iter__'

但它仍然是可迭代的:

>>> iter('abc')
<iterator object at 0x7fcee9e89390>

要使您的自定义类可迭代,您需要 任一 __iter____getitem__ 为不存在的项目引发 IndexError

class Foo:
    def __iter__(self):
        return iter(range(5))

class Bar:
    def __getitem__(self, i):
        if i >= 5:
            raise IndexError
        return i

使用这些:

>>> list(iter(Foo()))
[0, 1, 2, 3, 4]
>>> list(iter(Bar()))
[0, 1, 2, 3, 4]

通常不需要显式的 iter,因为 for 循环和期望 iterables 的方法将隐式创建迭代器:

>>> list(Foo())
[0, 1, 2, 3, 4]
>>> for i in Bar():
0
1
2
3
4

对于 2 参数形式,第一个参数必须是实现 __call__ 的函数或对象。第一个参数是不带参数调用的;返回值由迭代器产生。当迭代中的函数调用返回的值等于给定的 sentinel 值时,迭代停止,就像通过:

value = func()
if value == sentinel:
    return
else:
    yield value

例如,要获得骰子上的值之前我们抛出6,

>>> import random
>>> throw = lambda: random.randint(1, 6)
>>> list(iter(throw, 6))
[3, 2, 4, 5, 5]
>>> list(iter(throw, 6))
[1, 3, 1, 3, 5, 1, 4]

为了进一步澄清,每次在迭代器上使用next() 时,都会在不带参数的情况下调用给定函数(或具有__call__ 特殊方法的给定对象):

>>> def throw_die():
...     die = random.randint(1, 6)
...     print("returning {}".format(die))
...     return die
...
>>> throws = iter(throw_die, 6)
>>> next(throws)
returning 2
2
>>> next(throws)
returning 4
4
>>> next(throws)
returning 6
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

(即throw被称为throw(),如果返回值不等于6,则产生)。

或者在

的情况下
>>> fin_1byte = iter(functools.partial(fin.read, 1), '')
>>> for c in fin_1byte:
...     print c,

从文件末尾读取文件返回空字符串(如果是二进制文件,则返回空字节):

>>> from io import StringIO
>>> fin = StringIO(u'ab')
>>> fin.read(1)
u'a'
>>> fin.read(1)
u'b'
>>> fin.read(1)
u''

如果还没有到文件末尾,则返回一个字符。

这也可以用来从重复的函数调用中创建一个无限迭代器:

>>> dice = iter(throw, 7)

由于返回的值永远不会等于 7,因此迭代器将永远运行。一个常见的习惯用法是使用匿名object 来确保对于任何可能的值都不会成功进行比较

>>> dice = iter(throw, object())

因为

>>> object() != object()
True

请注意,sentinel 一词通常用于在某些数据中用作结束标记的值,并且该值不会自然地出现在数据中,如 this Java answer

【讨论】:

    猜你喜欢
    • 2016-03-25
    • 2020-12-23
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    • 2012-05-04
    • 1970-01-01
    • 2011-10-22
    相关资源
    最近更新 更多