Merge branch 'blender2.7' of git.blender.org:blender
[blender.git] / doc / python_api / rst / info_best_practice.rst
1
2 *************
3 Best Practice
4 *************
5
6 When writing your own scripts python is great for new developers to pick up and become productive,
7 but you can also pick up odd habits or at least write scripts that are not easy for others to understand.
8
9 For your own work this is of course fine,
10 but if you want to collaborate with others or have your work included with blender there are practices we encourage.
11
12
13 Style Conventions
14 =================
15
16 For Blender/Python development we have chosen to follow python suggested style guide to avoid mixing styles
17 amongst our own scripts and make it easier to use python scripts from other projects.
18
19 Using our style guide for your own scripts makes it easier if you eventually want to contribute them to blender.
20
21 This style guide is known as pep8 and can be found `here <https://www.python.org/dev/peps/pep-0008/>`_
22
23 A brief listing of pep8 criteria.
24
25 - camel caps for class names: MyClass
26 - all lower case underscore separated module names: my_module
27 - indentation of 4 spaces (no tabs)
28 - spaces around operators. ``1 + 1``, not ``1+1``
29 - only use explicit imports, (no importing ``*``)
30 - don't use single line: ``if val: body``, separate onto 2 lines instead.
31
32
33 As well as pep8 we have other conventions used for blender python scripts.
34
35 - Use single quotes for enums, and double quotes for strings.
36
37   Both are of course strings, but in our internal API enums are unique items from a limited set. eg.
38
39   .. code-block:: python
40
41      bpy.context.scene.render.image_settings.file_format = 'PNG'
42      bpy.context.scene.render.filepath = "//render_out"
43
44 - pep8 also defines that lines should not exceed 79 characters,
45   we felt this is too restrictive so this is optional per script.
46
47 Periodically we run checks for pep8 compliance on blender scripts,
48 for scripts to be included in this check add this line as a comment at the top of the script.
49
50 ``# <pep8 compliant>``
51
52 To enable line length checks use this instead.
53
54 ``# <pep8-80 compliant>``
55
56
57 User Interface Layout
58 =====================
59
60 Some notes to keep in mind when writing UI layouts:
61
62 - UI code is quite simple. Layout declarations are there to easily create a decent layout.
63
64   General rule here: If you need more code for the layout declaration,
65   then for the actual properties, you do it wrong.
66
67 Example layouts:
68
69 - layout()
70
71   The basic layout is a simple Top -> Bottom layout.
72
73   .. code-block:: python
74
75         layout.prop()
76         layout.prop()
77
78 - layout.row()
79
80   Use row(), when you want more than 1 property in one line.
81
82   .. code-block:: python
83
84         row = layout.row()
85         row.prop()
86         row.prop()
87
88 - layout.column()
89
90   Use column(), when you want your properties in a column.
91
92   .. code-block:: python
93
94         col = layout.column()
95         col.prop()
96         col.prop()
97
98 - layout.split()
99
100   This can be used to create more complex layouts.
101   For example you can split the layout and create two column() layouts next to each other.
102   Don't use split, when you simply want two properties in a row. Use row() for that.
103
104   .. code-block:: python
105
106         split = layout.split()
107
108         col = split.column()
109         col.prop()
110         col.prop()
111
112         col = split.column()
113         col.prop()
114         col.prop()
115
116 Declaration names:
117
118 Try to only use these variable names for layout declarations:
119
120 - row for a row() layout
121 - col for a column() layout
122 - split for a split() layout
123 - flow for a column_flow() layout
124 - sub for a sub layout (a column inside a column for example)
125
126
127 Script Efficiency
128 =================
129
130
131 List Manipulation (General Python Tips)
132 ---------------------------------------
133
134
135 Searching for list items
136 ^^^^^^^^^^^^^^^^^^^^^^^^
137
138 In Python there are some handy list functions that save you having to search through the list.
139
140 Even though you are not looping on the list data **python is**,
141 so you need to be aware of functions that will slow down your script by searching the whole list.
142
143 .. code-block:: python
144
145    my_list.count(list_item)
146    my_list.index(list_item)
147    my_list.remove(list_item)
148    if list_item in my_list: ...
149
150
151 Modifying Lists
152 ^^^^^^^^^^^^^^^
153 In python we can add and remove from a list, this is slower when the list length is modified,
154 especially at the start of the list, since all the data after the index of
155 modification needs to be moved up or down 1 place.
156
157 The most simple way to add onto the end of the list is to use
158 ``my_list.append(list_item)`` or ``my_list.extend(some_list)`` and the fastest way to
159 remove an item is ``my_list.pop()`` or ``del my_list[-1]``.
160
161 To use an index you can use ``my_list.insert(index, list_item)`` or ``list.pop(index)``
162 for list removal, but these are slower.
163
164 Sometimes its faster (but more memory hungry) to just rebuild the list.
165
166
167 Say you want to remove all triangular polygons in a list.
168
169 Rather than...
170
171 .. code-block:: python
172
173    polygons = mesh.polygons[:]  # make a list copy of the meshes polygons
174    p_idx = len(polygons)     # Loop backwards
175    while p_idx:           # while the value is not 0
176        p_idx -= 1
177
178        if len(polygons[p_idx].vertices) == 3:
179            polygons.pop(p_idx)  # remove the triangle
180
181
182 It's faster to build a new list with list comprehension.
183
184 .. code-block:: python
185
186    polygons = [p for p in mesh.polygons if len(p.vertices) != 3]
187
188
189 Adding List Items
190 ^^^^^^^^^^^^^^^^^
191
192 If you have a list that you want to add onto another list, rather than...
193
194 .. code-block:: python
195
196    for l in some_list:
197        my_list.append(l)
198
199 Use...
200
201 .. code-block:: python
202
203    my_list.extend([a, b, c...])
204
205
206 Note that insert can be used when needed,
207 but it is slower than append especially when inserting at the start of a long list.
208
209 This example shows a very sub-optimal way of making a reversed list.
210
211
212 .. code-block:: python
213
214    reverse_list = []
215    for list_item in some_list:
216        reverse_list.insert(0, list_item)
217
218
219 Python provides more convenient ways to reverse a list using the slice method,
220 but you may want to time this before relying on it too much:
221
222
223 .. code-block:: python
224
225   some_reversed_list = some_list[::-1]
226
227
228 Removing List Items
229 ^^^^^^^^^^^^^^^^^^^
230
231 Use ``my_list.pop(index)`` rather than ``my_list.remove(list_item)``
232
233 This requires you to have the index of the list item but is faster since ``remove()`` will search the list.
234
235 Here is an example of how to remove items in 1 loop,
236 removing the last items first, which is faster (as explained above).
237
238 .. code-block:: python
239
240    list_index = len(my_list)
241
242    while list_index:
243        list_index -= 1
244        if my_list[list_index].some_test_attribute == 1:
245            my_list.pop(list_index)
246
247
248 This example shows a fast way of removing items,
249 for use in cases where you can alter the list order without breaking the scripts functionality.
250 This works by swapping 2 list items, so the item you remove is always last.
251
252 .. code-block:: python
253
254    pop_index = 5
255
256    # swap so the pop_index is last.
257    my_list[-1], my_list[pop_index] = my_list[pop_index], my_list[-1]
258
259    # remove last item (pop_index)
260    my_list.pop()
261
262
263 When removing many items in a large list this can provide a good speedup.
264
265
266 Avoid Copying Lists
267 ^^^^^^^^^^^^^^^^^^^
268
269 When passing a list/dictionary to a function,
270 it is faster to have the function modify the list rather than returning
271 a new list so python doesn't have to duplicate the list in memory.
272
273 Functions that modify a list in-place are more efficient than functions that create new lists.
274
275
276 This is generally slower so only use for functions when it makes sense not to modify the list in place.
277
278 >>> my_list = some_list_func(my_list)
279
280
281 This is generally faster since there is no re-assignment and no list duplication.
282
283 >>> some_list_func(vec)
284
285
286 Also note that passing a sliced list makes a copy of the list in python memory.
287
288 >>> foobar(my_list[:])
289
290 If my_list was a large array containing 10000's of items, a copy could use a lot of extra memory.
291
292
293 Writing Strings to a File (Python General)
294 ------------------------------------------
295
296 Here are 3 ways of joining multiple strings into one string for writing.
297 This also applies to any area of your code that involves a lot of string joining.
298
299
300 ``String addition`` -
301 this is the slowest option, *don't use if you can help it, especially when writing data in a loop*.
302
303 >>> file.write(str1 + " " + str2 + " " + str3 + "\n")
304
305
306 ``String formatting`` -
307 use this when you are writing string data from floats and ints.
308
309 >>> file.write("%s %s %s\n" % (str1, str2, str3))
310
311
312 ``String join() function``
313 use to join a list of strings (the list may be temporary). In the following example, the strings are joined with a space " " in between, other examples are "" or ", ".
314
315 >>> file.write(" ".join([str1, str2, str3, "\n"]))
316
317
318 Join is fastest on many strings,
319 `string formatting <https://wiki.blender.org/index.php/Dev:Source/Modeling/BMesh/Design>`__
320 is quite fast too (better for converting data types). String arithmetic is slowest.
321
322
323 Parsing Strings (Import/Exporting)
324 ----------------------------------
325
326 Since many file formats are ASCII,
327 the way you parse/export strings can make a large difference in how fast your script runs.
328
329 There are a few ways to parse strings when importing them into Blender.
330
331
332 Parsing Numbers
333 ^^^^^^^^^^^^^^^
334
335 Use ``float(string)`` rather than ``eval(string)``, if you know the value will be an int then ``int(string)``,
336 float() will work for an int too but it is faster to read ints with int().
337
338
339 Checking String Start/End
340 ^^^^^^^^^^^^^^^^^^^^^^^^^
341
342 If you are checking the start of a string for a keyword, rather than...
343
344 >>> if line[0:5] == "vert ": ...
345
346 use...
347
348 >>> if line.startswith("vert "):
349
350 Using ``startswith()`` is slightly faster (approx 5%) and also avoids a possible
351 error with the slice length not matching the string length.
352
353 my_string.endswith("foo_bar") can be used for line endings too.
354
355 If you are unsure whether the text is upper or lower case, use the ``lower()`` or ``upper()`` string function.
356
357 >>> if line.lower().startswith("vert ")
358
359
360 Use try/except Sparingly
361 ------------------------
362
363 The **try** statement is useful to save time writing error checking code.
364
365 However **try** is significantly slower than an **if** since an exception has to be set each time,
366 so avoid using **try** in areas of your code that execute in a loop and runs many times.
367
368 There are cases where using **try** is faster than checking whether the condition will raise an error,
369 so it is worth experimenting.
370
371
372 Value Comparison
373 ----------------
374
375 Python has two ways to compare values ``a == b`` and ``a is b``,
376 the difference is that ``==`` may run the objects comparison function ``__cmp__()`` whereas ``is`` compares identity,
377 this is, that both variables reference the same item in memory.
378
379 In cases where you know you are checking for the same value which is referenced from multiple places, ``is`` is faster.
380
381
382 Time Your Code
383 --------------
384
385 While developing a script it is good to time it to be aware of any changes in performance, this can be done simply.
386
387 .. code-block:: python
388
389    import time
390    time_start = time.time()
391
392    # do something...
393
394    print("My Script Finished: %.4f sec" % (time.time() - time_start))