Welcome to mirror list, hosted at ThFree Co, Russian Federation.

info_best_practice.rst « rst « python_api « doc - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 8c5ae3398bc1a90be69c2f498f112b1456e0c29d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393

*************
Best Practice
*************

When writing your own scripts python is great for new developers to pick up and become productive,
but you can also pick up odd habits or at least write scripts that are not easy for others to understand.

For your own work this is of course fine,
but if you want to collaborate with others or have your work included with blender there are practices we encourage.


Style Conventions
=================

For Blender/Python development we have chosen to follow python suggested style guide to avoid mixing styles
amongst our own scripts and make it easier to use python scripts from other projects.

Using our style guide for your own scripts makes it easier if you eventually want to contribute them to blender.

This style guide is known as pep8 and can be found `here <http://www.python.org/dev/peps/pep-0008>`_

A brief listing of pep8 criteria.

- camel caps for class names: MyClass
- all lower case underscore separated module names: my_module
- indentation of 4 spaces (no tabs)
- spaces around operators. ``1 + 1``, not ``1+1``
- only use explicit imports, (no importing ``*``)
- don't use single line: ``if val: body``, separate onto 2 lines instead.


As well as pep8 we have other conventions used for blender python scripts.

- Use single quotes for enums, and double quotes for strings.

  Both are of course strings but in our internal API enums are unique items from a limited set. eg.

  .. code-block:: python

     bpy.context.scene.render.image_settings.file_format = 'PNG'
     bpy.context.scene.render.filepath = "//render_out"

- pep8 also defines that lines should not exceed 79 characters,
  we felt this is too restrictive so this is optional per script.

Periodically we run checks for pep8 compliance on blender scripts,
for scripts to be included in this check add this line as a comment at the top of the script.

``# <pep8 compliant>``

To enable line length checks use this instead.

``# <pep8-80 compliant>``


User Interface Layout
=====================

Some notes to keep in mind when writing UI layouts:

- UI code is quite simple. Layout declarations are there to easily create a decent layout.

  General rule here: If you need more code for the layout declaration,
  then for the actual properties, you do it wrong.

Example layouts:

- layout()

  The basic layout is a simple Top -> Bottom layout.

  .. code-block:: python

        layout.prop()
        layout.prop()

- layout.row()

  Use row(), when you want more than 1 property in one line.

  .. code-block:: python

        row = layout.row()
        row.prop()
        row.prop()

- layout.column()

  Use column(), when you want your properties in a column.

  .. code-block:: python

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

- layout.split()

  This can be used to create more complex layouts.
  For example you can split the layout and create two column() layouts next to each other.
  Don't use split, when you simply want two properties in a row. Use row() for that.

  .. code-block:: python

        split = layout.split()

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

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

Declaration names:

Try to only use these variable names for layout declarations:

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


Script Efficiency
=================


List Manipulation (General Python Tips)
---------------------------------------


Searching for list items
^^^^^^^^^^^^^^^^^^^^^^^^

In Python there are some handy list functions that save you having to search through the list.

Even though you're not looping on the list data **python is**,
so you need to be aware of functions that will slow down your script by searching the whole list.

.. code-block:: python

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


Modifying Lists
^^^^^^^^^^^^^^^
In python we can add and remove from a list, this is slower when the list length is modified,
especially at the start of the list, since all the data after the index of
modification needs to be moved up or down 1 place.

The most simple way to add onto the end of the list is to use
``my_list.append(list_item)`` or ``my_list.extend(some_list)`` and the fastest way to
remove an item is ``my_list.pop()`` or ``del my_list[-1]``.

To use an index you can use ``my_list.insert(index, list_item)`` or ``list.pop(index)``
for list removal, but these are slower.

Sometimes its faster (but more memory hungry) to just rebuild the list.


Say you want to remove all triangular faces in a list.

Rather than...

.. code-block:: python

   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


It's faster to build a new list with list comprehension.

.. code-block:: python

   faces = [f for f in mesh.tessfaces if len(f.vertices) != 3]


Adding List Items
^^^^^^^^^^^^^^^^^

If you have a list that you want to add onto another list, rather than...

.. code-block:: python

   for l in some_list:
       my_list.append(l)

Use...

.. code-block:: python

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


Note that insert can be used when needed,
but it is slower than append especially when inserting at the start of a long list.

This example shows a very sub-optimal way of making a reversed list.


.. code-block:: python

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


Python provides more convenient ways to reverse a list using the slice method,
but you may want to time this before relying on it too much:


.. code-block:: python

  some_reversed_list = some_list[::-1]


Removing List Items
^^^^^^^^^^^^^^^^^^^

Use ``my_list.pop(index)`` rather than ``my_list.remove(list_item)``

This requires you to have the index of the list item but is faster since ``remove()`` will search the list.

Here is an example of how to remove items in 1 loop,
removing the last items first, which is faster (as explained above).

.. code-block:: python

   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)


This example shows a fast way of removing items,
for use in cases where you can alter the list order without breaking the scripts functionality.
This works by swapping 2 list items, so the item you remove is always last.

.. code-block:: python

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


When removing many items in a large list this can provide a good speedup.


Avoid Copying Lists
^^^^^^^^^^^^^^^^^^^

When passing a list/dictionary to a function,
it is faster to have the function modify the list rather than returning
a new list so python doesn't have to duplicate the list in memory.

Functions that modify a list in-place are more efficient than functions that create new lists.


This is generally slower so only use for functions when it makes sense not to modify the list in place.

>>> my_list = some_list_func(my_list)


This is generally faster since there is no re-assignment and no list duplication.

>>> some_list_func(vec)


Also note that passing a sliced list makes a copy of the list in python memory

>>> foobar(my_list[:])

If my_list was a large array containing 10000's of items, a copy could use a lot of extra memory.


Writing Strings to a File (Python General)
------------------------------------------

Here are 3 ways of joining multiple strings into 1 string for writing

This really applies to any area of your code that involves a lot of string joining.


Python’s string addition, *don't use if you can help it, especially when writing data in a loop.*

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


String formatting. Use this when you're writing string data from floats and ints

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


Python’s string joining function. To join a list of strings

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


join is fastest on many strings,
`string formatting <http://docs.python.org/py3k/library/string.html#string-formatting>`__
is quite fast too (better for converting data types). String arithmetic is slowest.


Parsing Strings (Import/Exporting)
----------------------------------

Since many file formats are ASCII,
the way you parse/export strings can make a large difference in how fast your script runs.

There are a few ways to parse strings when importing them into Blender.


Parsing Numbers
^^^^^^^^^^^^^^^

Use ``float(string)`` rather than ``eval(string)``, if you know the value will be an int then ``int(string)``,
float() will work for an int too but it's faster to read ints with int().


Checking String Start/End
^^^^^^^^^^^^^^^^^^^^^^^^^

If you're checking the start of a string for a keyword, rather than...

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

Use...

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

Using ``startswith()`` is slightly faster (approx 5%) and also avoids a possible
error with the slice length not matching the string length.

my_string.endswith("foo_bar") can be used for line endings too.

If you are unsure whether the text is upper or lower case use ``lower()`` or ``upper()`` string function.

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


Use try/except Sparingly
------------------------

The **try** statement is useful to save time writing error checking code.

However **try** is significantly slower than an **if** since an exception has to be set each time,
so avoid using **try** in areas of your code that execute in a loop and runs many times.

There are cases where using **try** is faster than checking whether the condition will raise an error,
so it is worth experimenting.


Value Comparison
----------------

Python has two ways to compare values ``a == b`` and ``a is b``,
the difference is that ``==`` may run the objects comparison function ``__cmp__()`` whereas ``is`` compares identity,
that both variables reference the same item in memory.

In cases where you know you are checking for the same value which is referenced from multiple places, ``is`` is faster.


Time Your Code
--------------

While developing a script it's good to time it to be aware of any changes in performance, this can be done simply.

.. code-block:: python

   import time
   time_start = time.time()

   # do something...

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