Python中iter函数的第二个参数是什么?

alv*_*vas 1 python arguments iterable built-in

我们考虑一个文件:

$ 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 .
Run Code Online (Sandbox Code Playgroud)

当我想逐个阅读文件时,我可以做/sf/answers/1755011331/:

>>> 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   .
Run Code Online (Sandbox Code Playgroud)

但是使用while循环可能看起来有点unpythonic esp.当我fin.read(1)用来检查EOF然后按顺序回溯以读取当前字节.所以我可以做这样的事情如何从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   .
Run Code Online (Sandbox Code Playgroud)

但是当我在没有第二个参数的情况下尝试它时,它会抛出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
Run Code Online (Sandbox Code Playgroud)

第二个参数是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,否则返回该值.

我想文档需要一些"解密":

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

这是否意味着它需要来自collections?或者只要对象有一个__iter__(),那没关系?

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

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

  • 如果它不支持这些协议中的任何一个,则引发TypeError.

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

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

好的,现在这会得到一点科幻.它只是Python中的一个术语来称呼它sentinel吗?什么sentinel意思是Python?而"可调用对象"就像它是一个函数而不是类型对象?

  • 在这种情况下创建的迭代器将为每个对next()方法的调用调用没有参数的对象;

这部分我真的不明白,也许一个例子会有所帮助.

  • 如果返回的值等于sentinel,则会引发StopIteration,否则返回该值.

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

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

Ant*_*ala 5

使用一个参数,iter必须给出一个具有__iter__特殊方法 __getitem__特殊方法的对象.如果它们都不存在,iter 则会引发错误

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

迭代有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__'
Run Code Online (Sandbox Code Playgroud)

但它仍然是可迭代的:

>>> iter('abc')
<iterator object at 0x7fcee9e89390>
Run Code Online (Sandbox Code Playgroud)

为了使您的自定义类可迭代的,你需要有两种 __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
Run Code Online (Sandbox Code Playgroud)

使用这些:

>>> list(iter(Foo()))
[0, 1, 2, 3, 4]
>>> list(iter(Bar()))
[0, 1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

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

>>> list(Foo())
[0, 1, 2, 3, 4]
>>> for i in Bar():
0
1
2
3
4
Run Code Online (Sandbox Code Playgroud)

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

value = func()
if value == sentinel:
    return
else:
    yield value
Run Code Online (Sandbox Code Playgroud)

例如,要我们扔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]
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

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

或者在的情况下

>>> fin_1byte = iter(functools.partial(fin.read, 1), '')
>>> for c in fin_1byte:
...     print c,
Run Code Online (Sandbox Code Playgroud)

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

>>> from io import StringIO
>>> fin = StringIO(u'ab')
>>> fin.read(1)
u'a'
>>> fin.read(1)
u'b'
>>> fin.read(1)
u''
Run Code Online (Sandbox Code Playgroud)

如果尚未在文件末尾,则返回一个字符.

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

>>> dice = iter(throw, 7)
Run Code Online (Sandbox Code Playgroud)

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

>>> dice = iter(throw, object())
Run Code Online (Sandbox Code Playgroud)

因为

>>> object() != object()
True
Run Code Online (Sandbox Code Playgroud)

请注意,单词sentinel通常用于在某些数据中用作结束标记的值,并且在数据中不会自然发生,如此Java答案中所示.