1. 程式人生 > >五、上手小操作

五、上手小操作

attribute scene logs blog 1.2 reverse 模塊 時也 文件格式

Best Practice

  在編寫自己的腳本時,python對於新開發人員來說是非常有用的,但是您也容易養成一些奇怪的習慣,或者編寫一些不容易理解的腳本。

  對於你自己的工作,這當然很好,但是如果你想和其他人合作,或者把你的工作包括在blender中,我們會鼓勵你進行一些練習。

1 Style Conventions 風格慣例

  對於Blender / Python開發,我們選擇遵循Python建議的風格指南,以避免在我們自己的腳本中混合樣式,並使在其他項目中使用Python腳本更容易。

  如果你想對Blender做貢獻的話,那麽遵循我們的建議就會變得很容易。

  我們遵循pep8 的編碼風格,可以在這裏找到here,下面是一個簡潔的pep8標準列表:

  • camel caps(類名使用駝峰大小寫): MyClass
  • 使用小寫下劃線分隔模塊名: my_module
  • 使用四個空格縮進 (不要使用tabs)
  • 運算符前後加空格. 1 + 1, not 1+1
  • 只使用明確的模塊導入, (no importing *)
  • 不要在一行裏編代碼: if val: body, separate onto 2 lines instead.

  除了pep8,我們還有其他用於blender python腳本的約定:

  • 枚舉使用單引號,字符串使用雙引號

    兩者都是字符串,但在我們的內部API中,枚舉是來自有限集合的惟一項。 eg.

    bpy.context.scene.render.image_settings.file_format = 
    PNG bpy.context.scene.render.filepath = "//render_out"
  • pep8要求每行不超過 79 個字符, 我們感覺這個太嚴格了,所以這一條可選。

  我們周期性地在blender腳本上進行pep8遵從性檢查,在此檢查中添加的腳本添加這一行作為腳本頂部的註釋。

# <pep8 compliant>

  開啟行字符長度檢查

# <pep8-80 compliant>

2 User Interface Layout 用戶界面布局

  在編寫UI布局時要記住一些要點:

  • UI 代碼非常簡單. 布局聲明可以很容易地創建一個合適的布局。

    大體規則是: 不要讓布局聲明的代碼多於你實際要操作屬性的代碼.

布局例子:

  • layout()

    基本的布局是從上到下Top -> Bottom.

    layout.prop()
    layout.prop()
  • layout.row()

    你想在一行中排列多個屬性,使用row().

    row = layout.row()
    row.prop()
    row.prop()
  • layout.column()

    Use column(), 將屬性按列排.

    col = layout.column()
    col.prop()
    col.prop()
    
  • layout.split()

    這可以創建一些復雜的布局. 例如,你可以將當前布局分割成兩個緊挨著的列. 如果你只想排列兩個屬性時,不要使用split() 而是用 row()

    split = layout.split()
    
    col = split.column()
    col.prop()
    col.prop()
    
    col = split.column()
    col.prop()
    col.prop()

聲明的名字:

  • row for a row() layout
  • col for a column() layout
  • split for a split() layout
  • flow for a column_flow() layout
  • sub for a sub layout (a column inside a column for example)

3 Script Efficiency 高效的腳本

3.1 List Manipulation (General Python Tips) 列表操作

3.1.1 Searching for list items 查找

  在Python中,有一些方便的列表函數可以幫助您在列表中搜索。

  即使你沒有遍歷列表,但這其實是Python在幫你做。 因此你要意識到這會降低你的腳本效率。

my_list.count(list_item)
my_list.index(list_item)
my_list.remove(list_item)
if list_item in my_list: ...

3.1.2 Modifying Lists 修改

  在Python中我們可以對列表進行添加和刪除操作,但當列表長度改變時,這些操作是非常慢的。尤其是列表開始的元素,這樣後續的每個元素索引都要改變。

  向列表末尾添加 my_list.append(list_item) or my_list.extend(some_list) 並且快速刪除列表末尾的方法是 my_list.pop() or del my_list[-1].

  使用索引你可以my_list.insert(index, list_item) or list.pop(index)但這樣會很慢

  有時重建列表會很快,但也消耗內存

  比方說你想刪除列表中的所有三角形面。

  不要像下面這樣:

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

    if len(faces[f_idx].vertices) == 3:
        faces.pop(f_idx)  # remove the triangle
  相比之下使用列表推導新建一個列表會更快:
faces = [f for f in mesh.tessfaces if len(f.vertices) != 3]

3.1.3 Adding List Items 添加

  如果想合並兩個列表,不要用下面這個

for l in some_list:
    my_list.append(l)

  而是要這樣操作:

my_list.extend([a, b, c...])

  插入有時也是需要的, 但是與在長列表後面添加相比還是很慢

  下面這個例子展示了一個次佳的列子來將列表倒置.

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

  Python使用切片操作來提供更簡便的操作,但你可能需要花點時間掌握它,一旦你掌握它 你就會非常依賴它:

some_reversed_list = some_list[::-1]

3.1.4 Removing List Items 刪除

  使用 my_list.pop(index),而不是 my_list.remove(list_item)

  這要求你有元素的索引,但是更快。因為remove() 會搜索整個列表

  下面這個例子說明了 remove將在一個循環中進行操作, 然後pop一個元素,這就是上面解釋了為什麽pop刪除更快。

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)

  下面這個例子展示了更快的刪除方式, 可以在不破壞腳本功能的情況下改變列表順序.這種方法先將你要刪除的元素交換至最後.

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()

  當在一個長列表中刪除時,這會有很好的速度。

3.1.5 Avoid Copying Lists 避免復制

  當向一個函數傳遞 list/dictionary, 直接對列表進行操作要比返回一個新的列表快的多,因為這樣Python不需要在內存中復制一份參數.

  修改列表的函數比創建新列表的函數更有效

  下面這個很慢,只有在不修改列表時才使用。

>>> my_list = some_list_func(my_list)

  而下面這個就很快,因為沒有重新分配內存並且沒有復制操作

>>> some_list_func(vec)

  還要註意,通過切片列表會復制python內存中的列表。

>>> foobar(my_list[:])

  如果 my_list 包含10000個元素, 復制它將會消耗很多額外的內存.

3.2 Writing Strings to a File (Python General) 將字符串寫入文件

  這裏有三種方法可以將多個字符串連接到一個字符串中。 這也適用於代碼中涉及大量字符串連接的任何領域。

  String addition - 這是最慢的, 不要使用它 尤其是在循環中寫入數據時.

>>> file.write(str1 + " " + str2 + " " + str3 + "\n")

  String formatting -當你要把浮點和整形數寫入字符串時,用這個方法。

>>> file.write("%s %s %s\n" % (str1, str2, str3))

  String join() function用於加入字符串列表 (可以是一個臨時列表). 下面這個例子在字符串間添加了” ”,也可以添加 “” or ”, ”.

>>> file.write(" ".join([str1, str2, str3, "\n"]))

  Join 在多個字符串間操作很快, string formatting 也很快 (尤其在轉換數據類型時). String arithmetic 最慢.

3.3 Parsing Strings (Import/Exporting) 解析字符串

  由於許多文件格式都是ASCII格式的,所以解析/導出字符串的方式會對腳本運行的速度產生很大的影響。

  在將字符串導入Blender時,有一些方法可以解析字符串。

3.3.1 Parsing Numbers 解析數據

  使用float(string)而不是eval(string),如果知道值將是int(string),float()也會為int工作,但是使用int()讀取ints更快。

3.3.2 Checking String Start/End

  如果你要檢查某個字符串是不是以某個關鍵字開頭,不要這樣操作...

>>> if line[0:5] == "vert ": ...

  而是...

>>> if line.startswith("vert "):

  使用startswith() 會稍微更快 (approx 5%) and也避免了切片的長度與字符串長度不匹配的問題.

  my_string.endswith(“foo_bar”) 也可以用來檢查字符串的結尾.

  如果不確定字母大小寫, use the lower() or upper() string function.

>>> if line.lower().startswith("vert ")

3.4 Use try/except Sparingly 少量使用異常檢查

  try語句有助於節省編寫錯誤檢查代碼的時間。

  但是,如果每次都必須設置一個異常,那麽try要明顯慢一些,所以要避免在代碼中執行多次循環並運行的區域使用try。

  有些情況下,使用try比檢查條件是否引起錯誤要快,所以這是值得嘗試的。

3.5 Value Comparison 值比較

  Python有兩種方法來比較值a == b和a is b,不同的是= =可以運行對象比較函數__cmp__(),而is比較標識,這兩個變量在內存中引用相同的項。

  如果您知道您正在檢查從多個地方引用的相同值,則 is 更快。

3.6 Time Your Code 檢測你代碼的性能

  在開發腳本時,最好能讓它意識到性能上的任何變化,這可以簡單地完成。

import time
time_start = time.time()

# do something...

print("My Script Finished: %.4f sec" % (time.time() - time_start))

五、上手小操作