MRが楽しい

MRやVRについて学習したことを書き残す

Blender 2.8のPython APIドキュメントを少しずつ読み解く ベストプラクティス その2

本日は Blender2.8 の調査枠です。
今回から Blender 2.8 の Python API ドキュメントを少しずつ読みつつ試していきます。

Blender 2.8 Python API Documentation

以下のページを日本語訳しつつ実際に試して記事を進めていきます。
docs.blender.org
docs.blender.org

今日は「ベストプラクティス」の「スクリプト効率」のリスト操作です。
f:id:bluebirdofoz:20191019061222j:plain

リスト操作(一般的なPythonのヒント)

リストアイテムの検索

Pythonには、リストを検索する手間を省く便利なリスト関数がいくつかあります。
リストデータでループしていなくても、リスト全体を検索してスクリプトの速度を低下させる関数を知る必要があります。

my_list.count(list_item)
my_list.index(list_item)
my_list.remove(list_item)
if list_item in my_list: ...
リストの変更

Pythonではリストの追加と削除ができますが、特にリストの先頭に対する処理を行うと遅くなります。
変更したインデックスの後の全てのデータを1箇所上または下に移動する必要があるためです。

リストの最後に要素を追加する最も簡単な方法は my_list.append(list_item) または my_list.extend(some_list) を使用することです。
アイテムを削除する最も速い方法は my_list.pop() または del my_list[-1] です。

インデックスを使用するには、my_list.insert(index, list_item) または list.pop(index) がリストの削除に使用できます
しかし、これらはより低速です。

場合によってはリストを再構築するだけで高速になります。(ただし、再構築にはより多くのメモリが必要です)
一例として、リスト内のすべての三角形ポリゴンを削除するとします。以下は非効率な例です。

polygons = mesh.polygons[:]  # make a list copy of the meshes polygons
p_idx = len(polygons)     # Loop backwards
while p_idx:           # while the value is not 0
    p_idx -= 1

    if len(polygons[p_idx].vertices) == 3:
        polygons.pop(p_idx)  # remove the triangle

リスト内包表記を使用して新しいリストを作成する方が高速です。

polygons = [p for p in mesh.polygons if len(p.vertices) != 3]
リスト項目の追加

リストを別のリストに追加したい場合です。
以下は非効率な例です。
>|python}
for x in some_list:
my_list.append(x)
|

以下のように行うべきです。
>|python}
my_list.extend([a, b, c...])
|

リストの挿入は使用できますが、特に長いリストの先頭に挿入する場合は、追加よりも遅いことに注意してください。
以下の例は、逆リストを作成する最適でない方法です。

reverse_list = []
for list_item in some_list:
    reverse_list.insert(0, list_item)

Pythonは、sliceメソッドを使用してリストを反転するより便利な方法を提供します。
しかし利用する前に時間を計りたい場合もあります。

some_reversed_list = some_list[::-1]
リストアイテムの削除

my_list.remove(list_item) よりも my_list.pop(index) を使用するべきです。

これにはリストアイテムのインデックスが必要ですが、remove() より高速です。remove() はリストを検索するためです。
1つのループで最後の項目を最初に削除する方法の例を次に示します(上記で説明したように)。

list_index = len(my_list)

while list_index:
    list_index -= 1
    if my_list[list_index].some_test_attribute == 1:
        my_list.pop(list_index)

次の例は、スクリプトの機能を損なうことなくリストの順序を変更できる場合に使用できます。
アイテムを素早く削除する方法を示しています。
2つのリストアイテムを交換することで機能するため、削除するアイテムは常に最後になります。

pop_index = 5

# swap so the pop_index is last.
my_list[-1], my_list[pop_index] = my_list[pop_index], my_list[-1]

# remove last item (pop_index)
my_list.pop()

大きなリストから多くのアイテムを削除する場合、これにより高速化を実現できます。

リストのコピーを避ける

リスト/辞書を関数に渡すとき、新しいリストを返すよりも関数がリストを変更する方が高速です。
これはPythonがメモリ内のリストを複製する必要がないためです。、

リストをインプレースで変更する関数は、新しいリストを作成する関数よりも効率的です。

以下の例は一般に遅いので、リストをその場で変更しない場合にのみ関数に使用します。

>>> my_list = some_list_func(my_list)

再割り当てやリストの重複がないため、以下は一般に高速です。

>>> some_list_func(vec)

また、スライスリストを渡すとPythonメモリにリストのコピーが作成されることに注意してください。

>>> foobar(my_list[:])

my_listが10,000ほどのアイテムを含む大きな配列である場合、コピーは多くの余分なメモリを使用する可能性があります。

bluebirdofoz.hatenablog.com