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
ループはそれを行いません。これは イテレータプロトコル のどこにも言及されていません。これは__iter__
と__next__
について話しているだけです。
これはPython事前にリスト用のスペースを予約するのですか、それともそのような賢いものですか?
(Linux上のCPython 3.6.0)
__length_hint__
を導入し、動機に関する洞察を提供する PEP 424の根拠セクション を参照してください。
__length_hint__
によって推定されるように、予想されるサイズに基づいてリストを事前に割り当てることができることは、重要な最適化になる可能性があります。 CPythonは、純粋にこの最適化が存在するために、PyPyよりも高速にコードを実行することが確認されています。
それに加えて、ドキュメント object.__length_hint__
の場合 は、これが純粋に最適化機能であることを確認します。
operator.length_hint()
を実装するために呼び出されます。オブジェクトの推定長さを返す必要があります(実際の長さよりも長い場合も短い場合もあります)。長さは整数>= 0
でなければなりません。 このメソッドは純粋に最適化であり、正確性のために必要になることはありません。
したがって、__length_hint__
は、いくつかの優れた最適化をもたらす可能性があるため、ここにあります。
PyObject_LengthHint
、 最初にobject.__len__
から値を取得しようとします(定義されている場合) 次に、object.__length_hint__
が使用可能かどうかを確認しようとします。どちらも存在しない場合は、リストのデフォルト値8
を返します。
Eliが回答で述べたようにlist_init
から呼び出されるlistextend
は、このPEPに従って変更され、__len__
または__length_hint__
のいずれかを定義するすべてのものに対してこの最適化を提供します。
もちろん、これから恩恵を受けるのはlist
だけではありません bytes
オブジェクトはそうします :
>>> bytes(Foo())
len
getitem 0
...
b'\x00\x01\x04\t\x10\x19'
したがって bytearray
オブジェクトを実行しますが、extend
オブジェクトを実行する場合のみ :
>>> bytearray().extend(Foo())
len
getitem 0
...
およびTuple
オブジェクトを作成します 中間シーケンスto 自分自身にデータを入力します:
>>> Tuple(Foo())
len
getitem 0
...
(0, 1, 4, 9, 16, 25)
誰かがさまよっている場合、なぜ正確に'iter'
が出力されるのかbefore'len'
in class Bar
であり、class Foo
のように後ではありません。
これは、手元のオブジェクトが__iter__
Python 最初に呼び出すイテレータを取得する を定義している場合、それによってprint('iter')
も実行されるためです。 __getitem__
の使用にフォールバックした場合、同じことは起こりません。
list
は、メモリの初期スライスをその内容に割り当てるリストオブジェクトコンストラクタです。リストコンストラクターは、コンストラクターに渡されたオブジェクトの長さのヒントまたは長さをチェックすることにより、メモリの最初のスライスに適したサイズを見つけようとします。 Python ソースはこちら の PyObject_LengthHint
の呼び出しを参照してください。この場所はリストコンストラクターから呼び出されます--- list_init
オブジェクトに__len__
または__length_hint__
がない場合は、問題ありません--a デフォルト値8 が使用されます。再割り当てのために効率が低下する可能性があります。
注:答えを用意しました [SO]:__ len__が呼び出され、__ 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-(マスター)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による)。リストが(他の)リストまたはタプルから作成された場合に非常に役立ちます。
たとえば、10要素を使用して反復可能(__ len __を定義しない)からリストを作成します、すべてを一度に割り当てる代わりに、〜41(log1.125(1000 / 8)
)操作(割り当て、データシフト、割り当て解除)は、新しいリストがいっぱいになると(ソースの反復可能な要素で)増加するためにのみ必要です。
言うまでもなく、「最新の」反復可能オブジェクトの場合、改善は適用されなくなります。