为什么列表会询问__len__?

wim*_*wim 33 python list python-internals

class Foo:
    def __getitem__(self, item):
        print('getitem', item)
        if item == 6:
            raise IndexError
        return item**2
    def __len__(self):
        print('len')
        return 3

class Bar:
    def __iter__(self):
        print('iter')
        return iter([3, 5, 42, 69])
    def __len__(self):
        print('len')
        return 3
Run Code Online (Sandbox Code Playgroud)

演示:

>>> list(Foo())
len
getitem 0
getitem 1
getitem 2
getitem 3
getitem 4
getitem 5
getitem 6
[0, 1, 4, 9, 16, 25]
>>> list(Bar())
iter
len
[3, 5, 42, 69]
Run Code Online (Sandbox Code Playgroud)

为什么list打电话__len__?它似乎没有使用任何明显的结果.一个for循环不会做.迭代器协议中的任何地方都没有提到这个,它只谈及__iter____next__.

这个Python预先为列表预留空间,还是像这样聪明的东西?

(Linux上的CPython 3.6.0)

Jim*_*ard 35

请参阅PEP 424中的"基本原理"部分,部分介绍__length_hint__并提供有关动机的见解:

能够根据估计的预期大小预先分配列表__length_hint__可以是重要的优化.已经观察到CPython比PyPy更快地运行一些代码,纯粹是因为存在这种优化.

除此之外,文档object.__length_hint__验证了这纯粹是一种优化功能:

被叫实施operator.length_hint().应返回对象的估计长度(可能大于或小于实际长度).长度必须是整数>= 0.这种方法纯粹是一种优化,从来不需要正确性.

所以__length_hint__在这里,因为它可以导致一些很好的优化.

PyObject_LengthHint,首先尝试从object.__len__ (如果已定义)获取值,然后尝试查看是否object.__length_hint__可用.如果两者都不存在,则返回8列表的默认值.

listextend,list_init根据Eli在他的回答中所说的,根据这个PEP修改,为任何定义a __len__或a的东西提供优化__length_hint__.

list当然,bytes对象不是唯一受益于此的:

>>> bytes(Foo())
len
getitem 0
...
b'\x00\x01\x04\t\x10\x19'
Run Code Online (Sandbox Code Playgroud)

这样做的bytearray目的,但是,只有当你extend他们:

>>> bytearray().extend(Foo())
len
getitem 0
...
Run Code Online (Sandbox Code Playgroud)

tuple创建中间序列以填充自己的对象:

>>> tuple(Foo())
len
getitem 0
...
(0, 1, 4, 9, 16, 25)
Run Code Online (Sandbox Code Playgroud)

如果有人在徘徊,为什么在课准确'iter'打印,而不是在课后发生: 'len'BarFoo

这是因为如果手头的对象定义了__iter__ Python 将首先调用它来获取迭代器,从而运行它print('iter').如果它回归使用,也不会发生同样的情况__getitem__.

  • 这是一个很好的动力发现! (3认同)
  • @EliBendersky [git blame](https://github.com/python/cpython/blame/master/Objects/listobject.c#L834)在这些情况下是一个奇迹工作者:-D (3认同)

Eli*_*sky 31

list是一个列表对象构造函数,它将为其内容分配一个初始内存片.列表构造函数通过检查传递给构造函数的任何对象的长度提示或长度,尝试找出该初始内存片的大小.请在此处查看PyObject_LengthHintPython 源代码中的调用.这个地方是从列表构造函数中调用的 - list_init

如果您的对象没有__len____length_hint__,那没关系 - 使用默认值8 ; 由于重新分配,它可能效率较低.

  • "*它需要一个大小*"和"*使用默认值*"似乎相互矛盾.而不是"它需要一个大小","它使用大小来预分配内存". (5认同)
  • 这是一个CPython实现细节,还是Python语言的文档部分?因为如果您事先不知道该调用,它会导致无限递归. (4认同)
  • @wim:不是100%肯定,但我认为这是一个CPython实现细节,这也使它成为Python语言的事实上的一部分,因为没有官方的实现不可知的规范.请参阅http://stackoverflow.com/questions/37189968/how-to-have-list-consume-iter-without-calling-len中的其他讨论 - 我认为正确/合理实施的`__len__`不应该导致问题 (3认同)