五、上手小操作
Best Practice
在編寫自己的腳本時,python對於新開發人員來說是非常有用的,但是您也容易養成一些奇怪的習慣,或者編寫一些不容易理解的腳本。
對於你自己的工作,這當然很好,但是如果你想和其他人合作,或者把你的工作包括在blender中,我們會鼓勵你進行一些練習。
1 Style Conventions 風格慣例
對於Blender / Python開發,我們選擇遵循Python建議的風格指南,以避免在我們自己的腳本中混合樣式,並使在其他項目中使用Python腳本更容易。
如果你想對Blender做貢獻的話,那麽遵循我們的建議就會變得很容易。
我們遵循pep8 的編碼風格,可以在這裏找到here,下面是一個簡潔的pep8標準列表:
- camel caps(類名使用駝峰大小寫): MyClass
- 使用小寫下劃線分隔模塊名: my_module
- 使用四個空格縮進 (不要使用tabs)
- 運算符前後加空格.
1 + 1
, not1+1
- 只使用明確的模塊導入, (no importing
*
) - 不要在一行裏編代碼:
if val: body
, separate onto 2 lines instead.
除了pep8,我們還有其他用於blender python腳本的約定:
-
枚舉使用單引號,字符串使用雙引號
兩者都是字符串,但在我們的內部API中,枚舉是來自有限集合的惟一項。 eg.
bpy.context.scene.render.image_settings.file_format = ‘
-
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))
五、上手小操作