{"id":18639297,"url":"https://github.com/ldunham1/coding_guidelines","last_synced_at":"2026-03-05T12:32:44.343Z","repository":{"id":49380534,"uuid":"291386917","full_name":"ldunham1/coding_guidelines","owner":"ldunham1","description":"Suggested Coding Guidelines for Technical Artists using Python. ","archived":false,"fork":false,"pushed_at":"2024-06-03T15:26:56.000Z","size":77,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-11T16:49:04.109Z","etag":null,"topics":["coding-standards","python","readability"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ldunham1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-30T02:42:01.000Z","updated_at":"2024-07-23T11:31:40.000Z","dependencies_parsed_at":"2024-01-20T14:26:41.039Z","dependency_job_id":"f6131183-4d9d-4004-bbf4-75037c1e0a7f","html_url":"https://github.com/ldunham1/coding_guidelines","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ldunham1/coding_guidelines","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ldunham1%2Fcoding_guidelines","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ldunham1%2Fcoding_guidelines/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ldunham1%2Fcoding_guidelines/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ldunham1%2Fcoding_guidelines/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ldunham1","download_url":"https://codeload.github.com/ldunham1/coding_guidelines/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ldunham1%2Fcoding_guidelines/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30124482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T11:11:57.947Z","status":"ssl_error","status_checked_at":"2026-03-05T11:11:29.001Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coding-standards","python","readability"],"created_at":"2024-11-07T05:47:59.940Z","updated_at":"2026-03-05T12:32:44.324Z","avatar_url":"https://github.com/ldunham1.png","language":null,"readme":"# Python Coding Guidelines for Technical Artists\n\n![Python-Image](https://www.python.org/static/community_logos/python-logo-master-v3-TM-flattened.png)\n\n## Introduction:\nThese guidelines have been designed for writing Python code as Technical Artist/Animator. It focuses on contextual logic and \nreadability from the experiences of managing multiple code bases and teams.\n\nRemember, they are **Guidelines** \u003cins\u003enot\u003c/ins\u003e **Rules**. The goal is to improve \ncode readability and stability using logical reasoning and consistency and not to \"check boxes\" (ala \"pep8-ers\").\n \n[PEP 8](https://www.python.org/dev/peps/pep-0008/) is the basis of these guidelines.\n\n\n## Contents:\n- [General](#general)\n  - [Python 2/3](#python-2-or-3)\n  - [Which IDE?](#which-ide)\n- [Naming](#naming)\n  - [Public object naming](#object-naming)\n  - [Private object naming](#private-object-naming)\n  - [General naming conventions](#general-naming-conventions)\n  - [Naming Exceptions](#naming-exceptions)\n- [Imports](#imports)\n- [Line Length](#line-length)\n- [Line Breaks and Continuation](#line-breaks-and-continuation)\n- [Slicing](#index-slicing)\n- [Separators](#separators)\n- [Exceptions](#exceptions)\n- [Try/Finally](#try-finally)\n- [Logging](#logging)\n- [Docstrings](#docstrings)\n  - [Doctests](#doctests)\n- [Testing](#testing)\n\n---\n\n## General:\n\nThe flow of a module should follow;\n1. Module docstring (if any).\n3. [Imports](#importing).\n4. Content.\n5. \\_\\_all__ declaration (if used).\n6. `if __name__ == '__main__': ` (if used).\n\nWhere logical and possible, objects should be declared **before** they are used. \nWe want to improve the developer's chances of encountering an object before \nit was used - improving readability.\n```python\n# --------------------------------------------------------------------------\n# Yes\ndef ensure_strings(items):\n    return list(map(str, items))\n\n\ndef join_names(names, delim=', '):\n    str_names = ensure_strings(names)\n    return delim.join(str_names)\n\n\nclass Foo(object):\n\n    def bar(self):\n        name_list = [\n            'Lee',\n            'Mike',\n            'Agathe',\n        ]\n        print(join_names(name_list))\n\n\n# --------------------------------------------------------------------------\n# No\nclass Foo(object):\n\n    def bar(self):\n        name_list = [\n            'Lee',\n            'Mike',\n            'Agathe',\n        ]\n        print(join_names(name_list))\n\n\ndef join_names(names, delim=', '):\n    str_names = ensure_strings(names)\n    return delim.join(str_names)\n\n\ndef ensure_strings(items):\n    return list(map(str, items))\n```\n\n### Python 2 or 3:\n\nWhere possible and \u003cins\u003ewhen it makes sense\u003c/ins\u003e, write Python 2 and 3 compatible code. \nIn VFX and Games, projects usually lock to a major version of an Application during production.\nSome of these projects may continue development for several years using a single Application version due to \nthe potential disruption of updating.\nAs most Applications are only now just moving to Python 3, it makes very little sense to completely disregard \nPython 2 until productions have had a chance to move on. \n\n\u003e :warning: Never assume you know the extent in which your code will be used. People will always surprise you.\n\n\n### Which IDE?:\n\nUnless you are forced otherwise, choose use whichever IDE bests suits you and helps improve \nyour code quality. \nThat being said, I would **highly** recommend investing in an IDE with a minimum set of features.\n- Remote Debugging.\n    Connect to another process and real-time debug your Python code.\n- Conditional Breakpoints, Variable Watching, Expression Evaluation.\n    Various \"essential\" debugging features when remote debugging.\n- Version Control Integration; Git, Perforce (if necessary).\n    Often taken for granted how much time can be saved not manually executing git commands or p4 calls.\n- Code Completion (type matching), PEP8 Inspections \u0026 highlighting.\n    A must have for any Python IDE.\n- Local History.\n    Provides a significant workflow iteration boost being able to immediately check what has changed in the last few minutes from working to non-working code.\n\nI've developed professionally with;\n- [Notepad++](https://notepad-plus-plus.org/)\n- [Wing IDE](https://wingware.com/)\n- [Eclipse](https://www.eclipse.org/ide/) (with [PyDev](https://www.pydev.org/))\n- [PyCharm](https://www.jetbrains.com/pycharm/)\n- [Visual Studio](https://visualstudio.microsoft.com/)\n- [Visual Studio Code](https://code.visualstudio.com/)\n\nand I've dabbled with;\n- [Sublime](https://www.sublimetext.com/)\n- [Atom](https://github.com/atom)\n\nI personally work well with PyCharm and often end up showing colleagues various features that compliment their own workflow (or even functionality they didnt even consider to exist - Diff Local History, for example).\n\n---\n\n## Naming:\n\nThese general naming conventions used to help discern intended usage and aid readability.\n\n#### Object naming\n\n```python\n# ../package_name/module_name.py\n\nCONSTANT_NAME = ...\nvariable_name = ...\n\n\ndef function_name(arg):\n    ...\n\n\nclass ClassName(object):\n    ...\n\n    def method_name(self, ...):\n        ...\n```\n\n#### Private object naming\n\nPrivate objects are simply prefixed with a single underscore.\n```python\n# ../_package_name/_module_name.py\n\n_CONSTANT_NAME = ...\n_variable_name = ...\n\n\ndef _function_name(arg):\n    ...\n\n\nclass _ClassName(object):\n    ...\n\n    def _method_name(self, ...):\n        ...\n```\n\n#### General naming conventions\n\nUse descriptive names wherever possible, favouring readability over convenience. \nThis being said - avoid over-descriptive or unnecessary long names, as this is usually \nunreadable when used.\n\nSingle letter variable names are only suitable representing an integer and _ for the \nthrowaway variable.\n\u003e !! \u003cins\u003eDO NOT\u003c/ins\u003e use `_` as a typical variable !!\n```python\n# `i` is used to represent an iterating integer.\nfor i in range(10):\n    # `_` is not used at all.\n    for _ in range(i):    \n        print(True)\n```\n\n\nUse reverse notation to improve readability and auto-complete.  \nThis is true for variables, namespaces, functions \u0026 methods etc.\n\n```python\n# --------------------------------------------------------------------------\n# Yes\nlayer_enabled = ...\nlayer_disabled = ...\nlayer_mode_additive = ...\nlayer_mode_override = ...\n\n\n# --------------------------------------------------------------------------\n# No\nenabled_layer = ...\ndisabled_layer = ...\nadditive_layer_mode = ...\noverride_layer_mode = ...\n```\n\n### Naming Exceptions:\n##### [PEP8](https://peps.python.org/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds)\n\u003e A style guide is about consistency. Consistency with this style guide is important. \n\u003e Consistency within a project is more important. Consistency within one module or function is the most important.  \n\u003e However, know when to be inconsistent – sometimes style guide recommendations just aren’t applicable. \n\u003e When in doubt, use your best judgment. Look at other examples and decide what looks best. And don’t hesitate to ask!  \n\u003e \n\u003e In particular: do not break backwards compatibility just to comply with this PEP!\n\n\nLike [PEP 8's Overriding Principle](https://www.python.org/dev/peps/pep-0008/#overriding-principle), the consensus for public objects should reflect existing usage \u003cins\u003efor consistency\u003c/ins\u003e.\n\u003e Names that are visible to the user as public parts of the API should follow conventions that reflect usage rather than implementation.\n\n\nIn cases of Qt (PyQt, PySide and DCCs like Maya and Motionbuilder) camelCase is standard, so any extensions \nwill conform better using the same convention.   \n\n\u003e ..NOTE:: Worth pointing out the obvious, if there is already a convention for this exception in your \n\u003e environment, use that for consistency. \n```python\n# --------------------------------------------------------------------------\n# Yes\nclass QCustomWidget(QWidget):\n\n    def setParent(self, parent):\n        ...\n\n    def conformingMethod(self):\n        ...\n\n...\n\nwidget.setParent(None)\nwidget.conformingMethod()\n\n# --------------------------------------------------------------------------\n# No\nclass CustomWidget(QWidget):\n\n    def setParent(self, parent):\n        ...\n\n    def non_conforming_method(self):\n        ...\n\n...\n\nwidget.setParent(None)\nwidget.non_conforming_method()\n```\n\nA common retort to this recommendation is \"using snake_case allows us to see easily see custom code\".  \nWell \nyeah, until you need to override. This it's a moot point.\n\n---\n\n## Imports:\n\nImports are grouped, sorted and indented logically to improve readability.\n\nSeparate imports by source into 3 distinct groups.\n  1. Standard library imports\n  2. Third-party imports\n  3. Local imports.\n\nSort each group using [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order) \n\u003cins\u003eby line\u003c/ins\u003e, meaning `from sys import ...` is placed before `import abc`.\n\nImplicit relative imports are placed first in their respective groups.\n\n\n```python\nfrom itertools import (\n    imap,\n    permutations,\n)\nimport abc\nimport sys\n\nfrom numpy import arange\nimport six\n\nfrom . import some_module\nfrom package_name import other_module\n\n\nclass Foo(object):\n    ...\n```\n\n---\n\n## Line length:\n\n80-120 characters is usually fine. Just avoid lines that need horizontal scrolling on a \"standard\" screen size (24\").\n\nUse parentheses for line continuations.\n\n```python\nfull_name_list = [\n    'Bob.Something',\n    'Mark.Else',\n    'John.Blogger',\n]\n\nlist_comp = [\n    name  # Return variable\n    for name in full_name_list  # For loop\n    if name.startswith('Bob')  # Condition\n]\ndict_comp = {\n    firstname: surname \n    for full_name in full_name_list\n    for firstname, surname in full_name.split('.')\n}\n\n# Use aligned indentation to identify groups.\nfor name, age in zip(list_comp,\n                     [10, 20, 30]):\n    ...\n```\n\n---\n\n## Line breaks and continuation:\n\n### Comments\nUse empty line breaks before a comment and before a closing return. \nIn general use them where they help identify context and to avoid a wall of text.\n\n```python\ntry:\n    def main():\n\n        # \"Bob\" is a special person, treat them well.\n        if name == 'Bob':\n            something_nice(name)\n\n        # If we're not \"Bob\" then just ignore them.\n        # They won't take offense, they totally understand.\n        else:\n            print('Not \"Bob\"')\n\n        return True\n\n# Account for \"name\" not being available\nexcept NameError:\n    pass\n```\n\nUse parenthesis for line continuation.\n\u003e :warning: Just say NO to backslashes for line continuation. \n\n```python\n# Long strings\nsilly = (\n    'super'\n    'califragilistic'\n    'expialidocious'\n)\n\n# Long strings with format\nmonty = (\n    'Always look on '\n    'the {0} '\n    'side of {1}.'\n).format('bright', 'life')\n```\n\n### Conditions\nUse newlines with an additional indent level to make long conditions easier to digest.  \nWhen you have multiple lines or confusing conditions, use variables instead.\n\n```python\nif (age \u003e= 25 and age \u003c= 55) and (credit_score \u003e 700 or credit_score \u003e 650) and ((annual_income \u003e= 50000 and employment_years \u003e= 3) or annual_income \u003e= 80000):\n    print('huh?')\n\n# --------------------------------------------------------------------------\n# Better\nif ((age \u003e= 25 and age \u003c= 55) and \n        (credit_score \u003e 700 or credit_score \u003e 650) and \n        ((annual_income \u003e= 50000 and employment_years \u003e= 3) or annual_income \u003e= 80000)):\n    print('huh?')\n\n# --------------------------------------------------------------------------\n# Best\nmiddle_aged = age \u003e= 25 and age \u003c= 55\nacceptable_credit = credit_score \u003e 700 or credit_score \u003e 650\nacceptable_income = (annual_income \u003e= 50000 and employment_years \u003e= 3) or annual_income \u003e= 80000\nif middle_aged and acceptable_credit and acceptable_income:\n    print('huh?')\n```\n\nIn the instances where the conditions need to be evaluated sequentially, multiple if conditions might \nbe more readable in the end.\n\n\n### Functions/Methods\nFor functions or methods with many arguments, use inline line continuation to improve readability.\n```python\ndef make(name,\n         size,\n         colour,\n         alliance=None,\n         awards=0,\n         cats=0,\n         dogs=0,\n         goldfish=1000):\n    pass\n\nmake(\n    'Example',\n    size=(10, 10),\n    colour=(255, 0, 0),\n    alliance='neutral',\n    awards=100,\n    dogs=20,\n)\n```\n\n\n## Index slicing:\n\nIf you intend to get the last item with a slice, then use -1, regardless of the fixed length.\nThis aids readability by explicitly expressing intent.\n\n```python\n# --------------------------------------------------------------------------\n# Yes\nfull_name = 'Bob.Something.Something'\nsurname = full_name.split('.')[-1]\n\n# --------------------------------------------------------------------------\n# No\nfull_name = 'Bob.Something.Something'\nsurname = full_name.split('.')[1]\n```\n\nOr better yet, use `str.rsplit(..., 1)[-1]` to avoid unnecessary splitting.\n```python\nfull_name = 'Bob.Something.Something'\nsurname = full_name.rsplit('.', 1)[-1]\n```\n\n---\n\n## Separators:\n\nSeparators are used to group sections of code to improve readability at a glance \nand therefore are usually as long as you'd accept a line to be.\n\n\u003e It's useful to have a [macro](https://www.jetbrains.com/help/pycharm/using-macros-in-the-editor.html) to create separators to ensure consistency. \n\n```python\nfrom .constants import CAT_NAMES\n\n\n# --------------------------------------------------------------------------\ndef remove_first_names(name_list):\n    family_names = [\n        name.rsplit('.', 1)[-1]\n        for name in name_list\n    ]\n    return family_names\n\n\ndef remove_family_names(name_list):\n    first_names = [\n        name.split('.', 1)[0]\n        for name in name_list\n    ]\n    return first_names\n\n\n# --------------------------------------------------------------------------\ndef is_a_cat_name(name):\n    return name in CAT_NAMES\n```\n\n---\n\n## Exceptions:\nTry/except blocks should always provide an adequate exception type.\n\n**Lazy Exception handling is generally discouraged.** \n\n\u003e Remember, we want to improve readability not just \"check boxes\".\n\n```python\n# Yes\ntry:\n    1 / 0\nexcept ZeroDivisionError:\n    pass\n\n# --------------------------------------------------------------------------\n# No\ntry:\n    1 / 0\nexcept:\n    pass\n\n# --------------------------------------------------------------------------\n# Better but still No\ntry:\n    1 / 0\nexcept Exception:\n    pass\n```\n\n---\n\n## Try Finally:\nUse `finally` to ensure that regardless of how the scope is exited (return, exception etc) that code will be \nexecuted (like at `__exit__`).  \nAlso, make it easy for others to see the potential issues.\n\n```python\nimport fbx\n\n\ndef doit(fbx_filepath):\n    scene = fbx.open(fbx_filepath)\n    try:\n        scene.super_sketchy_op()\n        return 'Success'\n    except RuntimeError as e:\n        return 'Failed due to :: {}'.format(e)\n    finally:\n        scene.close()\n```\n\n---\n\n## Logging:\nThere is almost no excuse not to use logging. Get into the habit of it as early as possible and keep going. \nClients, other devs and future you will thank you for doing so.\n\n```python\nimport logging\n\n\n# Unless known, it wont hurt to ensure there is a root logger already setup.\nlogging.basicConfig()\n\n# Use the root logger if you dont want to associate your massages \n# to a specific tool/module, although using I've yet to find \n# a valid excuse to use debug on a root logger.\nlogging.debug('debug')  # Nope.\nlogging.info('Like a print, but better.')\n\n# Better to used a named log object\nLOGGER = logging.getLogger('ExampleLogger')\nLOGGER.setLevel(logging.INFO)  # Only INFO and above message are printed.\n\nLOGGER.debug('This is ignored.')\nLOGGER.info('This is good.')\nLOGGER.warning('This is great.')\nLOGGER.error('This is ideal.')\n```\n\nAbsolutely use logging for exceptions. Do not use `traceback` in order to print exception info.\nIf skipping exceptions (via `pass`) then consider still logging the exception at debug level for future debugging.\n\n```python\n# General usage - output the same information you would get \n# from `traceback.print_exc()`, but to a configurable logger object.\ntry:\n    1 / 0\nexcept ZeroDivisionError as e:\n    LOGGER.exception(e)  # prints exception and traceback\n\n    # or - you want to provide specific info...\n    LOGGER.exception('Something went wrong :: {}'.format(e))  # prints traceback and custom error message.\n\n    # or - if you're going to ignore it...\n    LOGGER.debug('Exception \u0026 traceback in debug :: {}'.format(e), exc_info=True)  # prints traceback and custom message (if given) but at debug level.\n```\n\n---\n\n## Docstrings:\n\nUse them and keep them updated. \nGood docstrings save far more time for developers (yourself included) than the time \nspent writing them. It doesn't matter if you use Epytext, reST or Google -  \u003cb\u003ejust use them!\u003c/b\u003e\n\u003e Intellisense will make good use of your docstring typehints!\n```python\n\"\"\"\nExample of a module Docstring. \nNotice the triple quotes.\n\"\"\"\n\n\nclass Foo(object):\n    \"\"\"\n    Example Foo class docstring.\n\n    :param int my_arg: Completely useless argument.\n    \"\"\"\n\n    def __init__(self, my_arg):\n        pass\n\n    def bar(self):\n        \"\"\"Method with no arguments or return.\"\"\"\n        pass\n\n    def bar_two(self, an_arg):\n        \"\"\"\n        Takes given `an_arg` and does nothing with it.\n        Will always return 0.\n        :param int/None an_arg: Completely useless argument.\n        :return int: The value 0.\n        \"\"\"\n        return 0\n```\n\n\n#### Doctests:\n\nPotentially unclear functionality usually benefits from a concise code example or two. \nDoctests also provide a\n\"better-than-nothing\" alternative to automated testing. \nIf used with an IDE like PyCharm, DocTests can be run with little to no setup, and make \nusage clearer for other developers.\n\n\u003e :exclamation: Pay attention to the line breaks and indentation for the code \n\u003e block if you want your doctests to be recognised.\n\n```python\n\"\"\"\nExample of a Docstring.\nNotice the triple quotes.\nI am now going to show a code example which doubles as a DocTest.\n\n    .. code-block:: python\n\n        \u003e\u003e\u003e name = 'mike'\n        \u003e\u003e\u003e # Test a return\n        \u003e\u003e\u003e name.title()\n        'Mike'\n        \u003e\u003e\u003e # Test a variable\n        \u003e\u003e\u003e upper_name = name.upper()\n        \u003e\u003e\u003e upper_name == 'MIKE'\n        True\n\n\"\"\"\n\nclass Foo(object):\n    \"\"\"\n    Example Foo class DocTest.\n\n    .. code-block:: python\n\n        \u003e\u003e\u003e foo = Foo()\n        \u003e\u003e\u003e isinstance(foo, Foo)\n        True\n\n    \"\"\"\n    pass\n```\n\n---\n\n## Testing:\n\nWrite tests. \nStart simple, its just important that you write tests - [tutorial link](https://realpython.com/python-testing/)\n\n#### unittest\n\nOne of the most common testing frameworks is `unittest` - a big plus it being standardlib.\n\n```python\nimport unittest\n\n\n# Some class we want to test\nclass Foo(object):\n    pass\n\n\nclass test_FooClass(unittest.TestCase):\n\n    def test_instance_type_check(self):\n        foo = Foo()\n        self.assertTrue(isinstance(foo, Foo))\n```\nYour testing coverage should reflect the importance of the code you're testing. The more crucial \nthe code, generally the more coverage you should have to reduce the likelihood of an issue.\n\nAutomated testing isn't a magic bullet, but it is a very useful tool. \n\n---\n\n#### Further Reading:\nCheck out Raymond Hettinger and James Powell on YouTube for super informative and clear examples of using Python well (yes, some of these links are _old_, doesn't make them irrelevant or useless).\n- [Transforming Code into Beautiful, Idiomatic Python](https://youtu.be/OSGv2VnC0go?si=2I3EeM-F3w8rkZ71)\n- [So you want to be a Python Expert?](https://youtu.be/cKPlPJyQrt4?si=5cDjICWmBVDt7kPA)","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fldunham1%2Fcoding_guidelines","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fldunham1%2Fcoding_guidelines","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fldunham1%2Fcoding_guidelines/lists"}