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
演示:
>>> 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]
为什么list
致电__len__
?它似乎没有使用任何明显的结果。 for
循环不会这样做。 iterator protocol中的任何地方均未提及此问题,{{3}}仅涉及__iter__
和__next__
。
这个Python是否预先为列表预留空间,或者像这样聪明的东西?
(Linux上的CPython 3.6.0)
答案 0 :(得分:31)
查看介绍__length_hint__
的{{3}},并提供有关动机的见解:
能够根据
__length_hint__
估计的预期大小预先分配列表,这可能是一项重要的优化。据观察, CPython比PyPy更快地运行一些代码,纯粹是因为存在这种优化。
除此之外,文档Rationale section from PEP 424验证了这纯粹是一种优化功能:
被要求实施
operator.length_hint()
。应返回对象的估计长度(可能大于或小于实际长度)。长度必须是整数>= 0
。 此方法纯粹是优化,并且永远不需要正确性。
所以__length_hint__
在这里是因为它可以带来一些不错的优化。
PyObject_LengthHint
,for object.__length_hint__
然后尝试查看object.__length_hint__
是否可用。如果两者都不存在,则为列表返回默认值8
。
listextend
,正如Eli在其回答中所述,从list_init
调用,根据此PEP进行了修改,为定义__len__
或{{{ 1}}。
__length_hint__
并非唯一受益于此的人,当然,first tries to get a value from object.__len__
(if it is defined):
list
>>> bytes(Foo())
len
getitem 0
...
b'\x00\x01\x04\t\x10\x19'
创建do bytearray
objects but, only when you extend
them的和>>> bytearray().extend(Foo())
len
getitem 0
...
个对象填充自己:
tuple
如果有人在徘徊,为什么在课程 这是因为如果手头的对象定义了>>> tuple(Foo())
len
getitem 0
...
(0, 1, 4, 9, 16, 25)
'iter'
之前打印 {em>,而不是在课程'len'
之后打印{<1}}:< / p>
Bar
an intermediary sequence to,那么也会运行Foo
。如果它回归到使用__iter__
,则不会发生同样的情况。
答案 1 :(得分:30)
list
是一个列表对象构造函数,它将为其内容分配一个初始内存片。列表构造函数通过检查传递给构造函数的任何对象的长度提示或长度,尝试找出该初始内存片的大小。请参阅Python PyObject_LengthHint
中对source here的调用。这个地方是从列表构造函数中调用的 - list_init
如果您的对象没有__len__
或__length_hint__
,那没关系 - 使用default value of 8;由于重新分配,效率可能会降低。
答案 2 :(得分:0)
注意:我为[SO]: Why __len__ is called and the result is not used when iterating with __getitem__?准备了答案,该答案在我编写时被标记为重复(正好是这个问题),因此不再可能发布在那里,由于我已经有了它,我决定将它发布到这里(稍作调整)。
这是您代码的修改版本,可以使事情更加清晰。
code00.py :
#!/usr/bin/env python3
import sys
class Foo:
def __getitem__(self, item):
print("{0:s}.{1:s}: {2:d}".format(self.__class__.__name__, "getitem", item))
if item == 6:
raise IndexError
return item ** 2
class Bar:
def __iter__(self):
print("{0:s}.{1:s}".format(self.__class__.__name__, "iter"))
return iter([3, 5, 42, 69])
def __len__(self):
result = 3
print("{0:s}.{1:s}: {2:d}".format(self.__class__.__name__, "len", result))
return result
def main():
print("Start ...\n")
for class_obj in [Foo, Bar]:
inst_obj = class_obj()
print("Created {0:s} instance".format(class_obj.__name__))
list_obj = list(inst_obj)
print("Converted instance to list")
print("{0:s}: {1:}\n".format(class_obj.__name__, list_obj))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main()
print("\nDone.")
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q041474829]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32 Start ... Created Foo instance Foo.getitem: 0 Foo.getitem: 1 Foo.getitem: 2 Foo.getitem: 3 Foo.getitem: 4 Foo.getitem: 5 Foo.getitem: 6 Converted instance to list Foo: [0, 1, 4, 9, 16, 25] Created Bar instance Bar.iter Bar.len: 3 Converted instance to list Bar: [3, 5, 42, 69] Done.
如所见,在构造列表时会调用 __ len __ 。浏览[GitHub]: python/cpython - (master) cpython/Objects/listobject.c:
n = PyObject_LengthHint(iterable, 8);
)PyObject_LengthHint (在 abstract.c 中),进行检查:
Py_ssize_t
PyObject_LengthHint(PyObject *o, Py_ssize_t defaultvalue)
// ...
if (_PyObject_HasLen(o)) {
res = PyObject_Length(o);
// ...
因此,这是一项优化功能,适用于定义 __ len __ 的可迭代对象。
当可迭代对象具有大量元素时,这特别方便,以便立即分配它们,从而跳过列表增长机制(虽然不检查是否仍然适用,但有一次是): “ 已满时空间增加了约12.5% ”(根据David M. Beazley的说法)。当列表由(其他)列表或元组构造而成时,这非常有用。
例如,使用具有 1000 元素的可迭代对象(未定义 __ len __ )构造一个列表,而不是分配所有内容一次,仅需要增加 〜41 ( log1.125(1000 / 8)
)操作(分配,数据移位,释放)新列表填充后(带有可迭代来源中的元素)。
不用说,对于“现代”可迭代对象,改进不再适用。