diff options
Diffstat (limited to 'doc/python_api/rst/info_best_practice.rst')
-rw-r--r-- | doc/python_api/rst/info_best_practice.rst | 245 |
1 files changed, 111 insertions, 134 deletions
diff --git a/doc/python_api/rst/info_best_practice.rst b/doc/python_api/rst/info_best_practice.rst index 8b64bacd50e..36bfc6e33ed 100644 --- a/doc/python_api/rst/info_best_practice.rst +++ b/doc/python_api/rst/info_best_practice.rst @@ -3,38 +3,34 @@ 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. - +When writing your own scripts Python is great for new developers to pick up and become productive, +but you can also pick up bad practices 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. +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 <https://www.python.org/dev/peps/pep-0008/>`_ - -A brief listing of pep8 criteria. +For Blender Python development we have chosen to follow Python suggested style guide to avoid mixing styles +among 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. -- 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. +This style guide is known as `pep8 <https://www.python.org/dev/peps/pep-0008/>`__ +and here is 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 wildcard importing ``*``) +- Don't use multiple statements on a single line: ``if val: body``, separate onto two lines instead. -As well as pep8 we have other conventions used for blender python scripts. +As well as pep8 we have additional 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. + Both are of course strings, but in our internal API enums are unique items from a limited set, e.g: .. code-block:: python @@ -42,14 +38,14 @@ As well as pep8 we have other conventions used for blender python scripts. 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. + we have decided that this is too restrictive so it 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. +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. +To enable line length checks use this instead: ``# <pep8-80 compliant>`` @@ -59,85 +55,79 @@ 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. +UI code is quite simple. Layout declarations are there to easily create a decent layout. +The general rule here is: If you need more code for the layout declaration, +than for the actual properties, then you are doing it wrong. - General rule here: If you need more code for the layout declaration, - then for the actual properties, you do it wrong. -Example layouts: +.. rubric:: Example layouts: -- layout() +``layout()`` + The basic layout is a simple top-to-bottom layout. - The basic layout is a simple Top -> Bottom layout. + .. code-block:: python - .. code-block:: python + layout.prop() + layout.prop() - layout.prop() - layout.prop() +``layout.row()`` + Use ``row()``, when you want more than one property in a single line. -- layout.row() + .. code-block:: python - Use row(), when you want more than 1 property in one line. + row = layout.row() + row.prop() + row.prop() - .. code-block:: python +``layout.column()`` + Use ``column()``, when you want your properties in a column. - row = layout.row() - row.prop() - row.prop() + .. code-block:: python -- layout.column() + col = layout.column() + col.prop() + col.prop() - Use column(), when you want your properties in a column. +``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. + Do not use split, when you simply want two properties in a row. Use ``row()`` instead. - .. code-block:: python + .. code-block:: python - col = layout.column() - col.prop() - col.prop() + split = layout.split() -- layout.split() + col = split.column() + col.prop() + col.prop() - 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. + col = split.column() + col.prop() + col.prop() - .. code-block:: python - split = layout.split() - - col = split.column() - col.prop() - col.prop() - - col = split.column() - col.prop() - col.prop() - -Declaration names: +.. rubric:: 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) +: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 +Searching for List Items ^^^^^^^^^^^^^^^^^^^^^^^^ In Python there are some handy list functions that save you having to search through the list. - -Even though you are not looping on the list data **python is**, +Even though you are 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 @@ -150,23 +140,21 @@ so you need to be aware of functions that will slow down your script by searchin Modifying Lists ^^^^^^^^^^^^^^^ -In python we can add and remove from a list, this is slower when the list length is modified, + +In Python you 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. +modification needs to be moved up or down one 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]``. +The fastest way to add onto the end of the list is to use +``my_list.append(list_item)`` or ``my_list.extend(some_list)`` and +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 polygons in a list. - -Rather than... +Sometimes it's faster (but less memory efficient) to just rebuild the list. +For example if you want to remove all triangular polygons in a list. +Rather than: .. code-block:: python @@ -179,7 +167,7 @@ Rather than... polygons.pop(p_idx) # remove the triangle -It's faster to build a new list with list comprehension. +It's faster to build a new list with list comprehension: .. code-block:: python @@ -189,14 +177,14 @@ It's faster to build a new list with list comprehension. Adding List Items ^^^^^^^^^^^^^^^^^ -If you have a list that you want to add onto another list, rather than... +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... +Use: .. code-block:: python @@ -205,9 +193,7 @@ Use... 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. - +This example shows a very suboptimal way of making a reversed list: .. code-block:: python @@ -219,7 +205,6 @@ This example shows a very sub-optimal way of making a reversed list. 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] @@ -228,12 +213,10 @@ but you may want to time this before relying on it too much: Removing List Items ^^^^^^^^^^^^^^^^^^^ -Use ``my_list.pop(index)`` rather than ``my_list.remove(list_item)`` - +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). +Here is an example of how to remove items in one loop, +removing the last items first, which is faster (as explained above): .. code-block:: python @@ -247,7 +230,7 @@ removing the last items first, which is faster (as explained above). 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. +This works by swapping two list items, so the item you remove is always last: .. code-block:: python @@ -260,64 +243,59 @@ This works by swapping 2 list items, so the item you remove is always last. my_list.pop() -When removing many items in a large list this can provide a good speedup. +When removing many items in a large list this can provide a good speed-up. Avoid Copying Lists ^^^^^^^^^^^^^^^^^^^ -When passing a list/dictionary to a function, +When passing a list or 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. +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. +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. +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. +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. +If my_list was a large array containing 10,000'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 one string for writing. -This also applies to any area of your code that involves a lot of string joining. - - -``String addition`` - -this is the slowest option, *don't use if you can help it, especially when writing data in a loop*. +Here are three ways of joining multiple strings into one string for writing. +This also applies to any area of your code that involves a lot of string joining: ->>> file.write(str1 + " " + str2 + " " + str3 + "\n") +String concatenation + This is the slowest option, do **not** use if you can avoid it, especially when writing data in a loop. + >>> file.write(str1 + " " + str2 + " " + str3 + "\n") -``String formatting`` - -use this when you are writing string data from floats and ints. +String formatting + Use this when you are writing string data from floats and ints. ->>> file.write("%s %s %s\n" % (str1, str2, str3)) + >>> file.write("%s %s %s\n" % (str1, str2, str3)) +String joining + 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 ", ". -``String join() function`` -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 ", ". + >>> file.write(" ".join((str1, str2, str3, "\n"))) ->>> file.write(" ".join([str1, str2, str3, "\n"])) - -Join is fastest on many strings, -`string formatting <https://wiki.blender.org/index.php/Dev:Source/Modeling/BMesh/Design>`__ -is quite fast too (better for converting data types). String arithmetic is slowest. +Join is fastest on many strings, string formatting is quite fast too (better for converting data types). +String concatenation is the slowest. Parsing Strings (Import/Exporting) @@ -333,36 +311,35 @@ 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 is faster to read ints with int(). +``float()`` will work for an int too but it is faster to read ints with ``int()``. Checking String Start/End ^^^^^^^^^^^^^^^^^^^^^^^^^ -If you are checking the start of a string for a keyword, rather than... +If you are checking the start of a string for a keyword, rather than: >>> if line[0:5] == "vert ": ... -use... +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. +Using ``startswith()`` is slightly faster (around 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. +``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 the ``lower()`` or ``upper()`` string function. +If you are unsure whether the text is upper or lower case, use the ``lower()`` or ``upper()`` string function: >>> if line.lower().startswith("vert ") -Use try/except Sparingly ------------------------- +Error Handling +-------------- 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, +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, @@ -382,7 +359,7 @@ In cases where you know you are checking for the same value which is referenced Time Your Code -------------- -While developing a script it is good to time it to be aware of any changes in performance, this can be done simply. +While developing a script it is good to time it to be aware of any changes in performance, this can be done simply: .. code-block:: python @@ -391,4 +368,4 @@ While developing a script it is good to time it to be aware of any changes in pe # do something... - print("My Script Finished: %.4f sec" % (time.time() - time_start)) + print("My Script Finished: {:.4f} sec".format(time.time() - time_start)) |