【问题标题】:Why __len__ is called and the result is not used when iterating with __getitem__? [duplicate]为什么在使用 __getitem__ 迭代时调用了 __len__ 并且没有使用结果? [复制]
【发布时间】:2019-09-11 10:21:09
【问题描述】:

考虑以下示例:

import random


class Class1:
    def __getitem__(self, item):
        print('getitem', item)
        result = random.randint(0, 10)
        if not result:
            raise IndexError
        return result


class Class2(Class1):
    def __len__(self):
        print('len', 3)
        return 3


print(list(Class1()))
print(list(Class2()))

输出:

getitem 0
getitem 1
[10]
len 3
getitem 0
getitem 1
getitem 2
getitem 3
getitem 4
[8, 10, 2, 10]

因此,当迭代 Class1() 时,没有 __len__ 但代码可以正常工作。当Class2() 中有__len__ 时,它被调用但结果3 根本没有使用,得到3 个项目后继续迭代。我的问题是:为什么__len__ 被调用?如果结果被忽略,就没有理由调用它。

【问题讨论】:

  • 猜测:__len__the definition of a sequence type 的一部分,所以它只是被称为检查它是否是鸭子类型......?
  • @deceze 我不确定。如果您将Class1 更改为子类int(没有__len__),那么list(Class1()) 仍然有效,所以list(...) 不关心是否调用__len__,因为它不存在(但它确实关心如果引发异常)
  • 我相信它仅用于预先分配适当大小的目的地列表,作为优化;如果您在其他上下文中迭代 Class2 实例(例如 for 循环)__len__ 不会被调用。
  • @jonrsharpe 我认为你是对的。如果 Class2.__len__ 更改为返回负数,则 list(Class2()) 失败 ValueError: __len__() should return >= 0
  • @DeepSpace 这是__len__ 的一般要求; "an integer >= 0".

标签: python iteration


【解决方案1】:

PEP 424: A method for exposing a length hint中有一些提示:

CPython 目前在几种类型上定义了 length_hint 方法, 比如各种迭代器。这种方法然后被其他各种使用 函数(例如列表)根据估计来预先确定列表的大小 由 length_hint 返回。没有大小的类型,因此 不应该定义 len,然后可以定义 length_hint,以允许 估计或计算大小(例如许多迭代器)。

还有:

能够根据预期大小预先分配列表,如 length_hint 估计,可能是一个重要的优化。 已经观察到 CPython 运行某些代码的速度比 PyPy 快,纯粹是 因为存在这种优化。

所以看来list 调用__len__ 是为了预先分配列表。之后,您的列表可以随心所欲地增长。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 2017-06-22
    • 1970-01-01
    • 2020-08-22
    • 2018-12-19
    • 2013-06-01
    • 2018-12-20
    相关资源
    最近更新 更多