【问题标题】:What's the difference between __iter__ and __getitem__?__iter__ 和 __getitem__ 有什么区别?
【发布时间】:2013-12-31 07:25:22
【问题描述】:

这发生在我的 Python 2.7.6 和 3.3.3 中。当我定义这样的类时

class foo:
    def __getitem__(self, *args):
        print(*args)

然后尝试在一个实例上迭代(以及我认为会称为 iter 的东西),

bar = foo()
for i in bar:
    print(i)

它只是为 args 加一并永远打印 None 。就语言设计而言,这是故意的吗?

样本输出

0
None
1
None
2
None
3
None
4
None
5
None
6
None
7
None
8
None
9
None
10
None

【问题讨论】:

标签: python python-2.7 python-3.x


【解决方案1】:

__iter__ 是遍历可迭代对象的首选方式。如果未定义,解释器将尝试使用__getitem__ 模拟其行为。看看here

【讨论】:

  • 同意@thefourtheye,正确的链接应该是例如PEP 234
  • 谢谢,我没找到。已编辑。
  • 重要的更正:PEP 没有声明 __iter__ 优于 __getitem__;相反,它定义了两者,并简单地说 __iter__ 在返回到 __getitem__ 方法之前是先尝试。 PEP 的重点是为非序列对象添加迭代支持。没有努力删除现有的序列支持或阻止其使用。
  • 首先尝试__iter__ 的原因是有时可能会发生它的实现允许比旧式迭代器协议更好的性能。从这个意义上说,它也是执行此操作的首选方式。
  • @Faust 重要的是不要使用“首选”这个词——这错误地暗示人们不应该使用 __getitem__ 方法。你是对的,有时 __iter__ 可以有更好的性能。这就是我实现 listiterator 的原因,尽管它已经可以使用 __getitem__ 进行迭代。另一方面,使用 __iter__ 并没有提高 str/unicode 的性能。这就是我没有添加字符串迭代器的原因。
【解决方案2】:

是的,这是一个预期的设计。它被记录,经过充分测试,并被 str 等序列类型所依赖。

__getitem__ 版本是 Python 拥有现代迭代器之前的遗留物。这个想法是任何序列(可索引且具有长度的东西)都可以使用序列 s[0]、s[1]、s[2]、... 自动迭代,直到 IndexErrorStopIteration 被提出。

例如,在 Python 2.7 中,字符串是可迭代的,因为 __getitem__ 方法(​​str 类型没有 __iter__ 方法)。

相比之下,迭代器协议允许任何类可迭代,而不必是可索引的(例如字典和集合)。

以下是如何使用序列的遗留样式创建可迭代类:

>>> class A:
        def __getitem__(self, index):
            if index >= 10:
                raise IndexError
            return index * 111

>>> list(A())
[0, 111, 222, 333, 444, 555, 666, 777, 888, 999]

以下是如何使用 __iter__ 方法创建可迭代对象:

>>> class B:
        def __iter__(self):
            yield 10
            yield 20
            yield 30


>>> list(B())
[10, 20, 30]

对细节感兴趣的朋友,相关代码在Objects/iterobject.c:

static PyObject *
iter_iternext(PyObject *iterator)
{
    seqiterobject *it;
    PyObject *seq;
    PyObject *result;

    assert(PySeqIter_Check(iterator));
    it = (seqiterobject *)iterator;
    seq = it->it_seq;
    if (seq == NULL)
        return NULL;

    result = PySequence_GetItem(seq, it->it_index);
    if (result != NULL) {
        it->it_index++;
        return result;
    }
    if (PyErr_ExceptionMatches(PyExc_IndexError) ||
        PyErr_ExceptionMatches(PyExc_StopIteration))
    {
        PyErr_Clear();
        Py_DECREF(seq);
        it->it_seq = NULL;
    }
    return NULL;
}

在 Objects/abstract.c 中:

int
PySequence_Check(PyObject *s)
{
    if (s == NULL)
        return 0;
    if (PyInstance_Check(s))
        return PyObject_HasAttrString(s, "__getitem__");
    if (PyDict_Check(s))
        return 0;
    return  s->ob_type->tp_as_sequence &&
        s->ob_type->tp_as_sequence->sq_item != NULL;
}

【讨论】:

【解决方案3】:

要获得您期望的结果,您需要有一个有限 len 的数据元素并按顺序返回每个元素:

class foo:
    def __init__(self):
        self.data=[10,11,12]

    def __getitem__(self, arg):
        print('__getitem__ called with arg {}'.format(arg))
        return self.data[arg]

bar = foo()
for i in bar:
    print('__getitem__ returned {}'.format(i)) 

打印:

__getitem__ called with arg 0
__getitem__ returned 10
__getitem__ called with arg 1
__getitem__ returned 11
__getitem__ called with arg 2
__getitem__ returned 12
__getitem__ called with arg 3

或者您可以通过提高IndexError 来表示“序列”的结束(尽管StopIteration 也可以...):

class foo:
    def __getitem__(self, arg):
        print('__getitem__ called with arg {}'.format(arg))
        if arg>3:
            raise IndexError
        else:    
            return arg

bar = foo()
for i in bar:
    print('__getitem__ returned {}'.format(i))   

打印:

__getitem__ called with arg 0
__getitem__ returned 0
__getitem__ called with arg 1
__getitem__ returned 1
__getitem__ called with arg 2
__getitem__ returned 2
__getitem__ called with arg 3
__getitem__ returned 3
__getitem__ called with arg 4

for 循环期望 IndexErrorStopIteration 发出序列结束的信号。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-02
    • 2011-12-12
    • 2010-09-16
    • 2012-03-14
    • 2012-02-06
    • 2011-02-25
    • 2011-11-22
    • 2015-03-26
    相关资源
    最近更新 更多