qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

coding_conventions_python.md (12463B)


      1 # Coding Conventions (Python)
      2 
      3 Most of our style follows PEP8 with some local modifications to make things less nit-picky. 
      4 
      5 * We target Python 3.9 for compatibility with all supported platforms.
      6 * We indent using four (4) spaces (soft tabs)
      7 * We encourage liberal use of comments
      8   * Think of them as a story describing the feature
      9   * Use them liberally to explain why particular decisions were made.
     10   * Do not write obvious comments
     11   * If you're not sure if a comment is obvious, go ahead and include it.
     12 * We require useful docstrings for all functions.
     13 * In general we don't wrap lines, they can be as long as needed. If you do choose to wrap lines please do not wrap any wider than 76 columns.
     14 * Some of our practices conflict with the wider python community to make our codebase more approachable to non-pythonistas.
     15 
     16 # YAPF
     17 
     18 You can use [yapf](https://github.com/google/yapf) to style your code. We provide a config in [setup.cfg](https://github.com/qmk/qmk_firmware/blob/master/setup.cfg).
     19 
     20 # Imports
     21 
     22 We don't have a hard and fast rule for when to use `import ...` vs `from ... import ...`. Understandability and maintainability is our ultimate goal.
     23 
     24 Generally we prefer to import specific function and class names from a module to keep code shorter and easier to understand. Sometimes this results in a name that is ambiguous, and in such cases we prefer to import the module instead. You should avoid using the "as" keyword when importing, unless you are importing a compatibility module.
     25 
     26 Imports should be one line per module. We group import statements together using the standard python rules- system, 3rd party, local.
     27 
     28 Do not use `from foo import *`. Supply a list of objects you want to import instead, or import the whole module.
     29 
     30 ## Import Examples
     31 
     32 Good:
     33 
     34 ```
     35 from qmk import effects
     36 
     37 effects.echo()
     38 ```
     39 
     40 Bad:
     41 
     42 ```
     43 from qmk.effects import echo
     44 
     45 echo()  # It's unclear where echo comes from
     46 ```
     47 
     48 Good:
     49 
     50 ```
     51 from qmk.keymap import compile_firmware
     52 
     53 compile_firmware()
     54 ```
     55 
     56 OK, but the above is better:
     57 
     58 ```
     59 import qmk.keymap
     60 
     61 qmk.keymap.compile_firmware()
     62 ```
     63 
     64 # Statements
     65 
     66 One statement per line.
     67 
     68 Even when allowed (EG `if foo: bar`) we do not combine 2 statements onto a single line.
     69 
     70 # Naming
     71 
     72 `module_name`, `package_name`, `ClassName`, `method_name`, `ExceptionName`, `function_name`, `GLOBAL_CONSTANT_NAME`, `global_var_name`, `instance_var_name`, `function_parameter_name`, `local_var_name`.
     73 
     74 Function names, variable names, and filenames should be descriptive; eschew abbreviation. In particular, do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word.
     75 
     76 Always use a .py filename extension. Never use dashes.
     77 
     78 ## Names to Avoid
     79 
     80 * single character names except for counters or iterators. You may use `e` as an exception identifier in try/except statements.
     81 * dashes (`-`) in any package/module name
     82 * `__double_leading_and_trailing_underscore__` names (reserved by Python)
     83 
     84 # Docstrings
     85 
     86 To maintain consistency with our docstrings we've set out the following guidelines.
     87 
     88 * Use markdown formatting
     89 * Always use triple-dquote docstrings with at least one linebreak: `"""\n"""`
     90 * First line is a short (< 70 char) description of what the function does
     91 * If you need more in your docstring leave a blank line between the description and the rest.
     92 * Start indented lines at the same indent level as the opening triple-dquote
     93 * Document all function arguments using the format described below
     94 * If present, Args:, Returns:, and Raises: should be the last three things in the docstring, separated by a blank line each.
     95 
     96 ## Simple docstring example
     97 
     98 ```
     99 def my_awesome_function():
    100     """Return the number of seconds since 1970 Jan 1 00:00 UTC.
    101     """
    102     return int(time.time())
    103 ```
    104 
    105 ## Complex docstring example
    106 
    107 ```
    108 def my_awesome_function():
    109     """Return the number of seconds since 1970 Jan 1 00:00 UTC.
    110 
    111     This function always returns an integer number of seconds.
    112     """
    113     return int(time.time())
    114 ```
    115 
    116 ## Function arguments docstring example
    117 
    118 ```
    119 def my_awesome_function(start=None, offset=0):
    120     """Return the number of seconds since 1970 Jan 1 00:00 UTC.
    121 
    122     This function always returns an integer number of seconds.
    123 
    124 
    125     Args:
    126         start
    127             The time to start at instead of 1970 Jan 1 00:00 UTC
    128 
    129         offset
    130             Return an answer that has this number of seconds subtracted first
    131 
    132     Returns:
    133         An integer describing a number of seconds.
    134 
    135     Raises:
    136         ValueError
    137             When `start` or `offset` are not positive numbers
    138     """
    139     if start < 0 or offset < 0:
    140         raise ValueError('start and offset must be positive numbers.')
    141 
    142     if not start:
    143         start = time.time()
    144 
    145     return int(start - offset)
    146 ```
    147 
    148 # Exceptions
    149 
    150 Exceptions are used to handle exceptional situations. They should not be used for flow control. This is a break from the python norm of "ask for forgiveness." If you are catching an exception it should be to handle a situation that is unusual.
    151 
    152 If you use a catch-all exception for any reason you must log the exception and stacktrace using cli.log.
    153 
    154 Make your try/except blocks as short as possible. If you need a lot of try statements you may need to restructure your code.
    155 
    156 # Tuples
    157 
    158 When defining one-item tuples always include a trailing comma so that it is obvious you are using a tuple. Do not rely on implicit one-item tuple unpacking. Better still use a list which is unambiguous.
    159 
    160 This is particularly important when using the printf-style format strings that are commonly used.
    161 
    162 # Lists and Dictionaries
    163 
    164 We have configured YAPF to differentiate between sequence styles with a trailing comma. When a trailing comma is omitted YAPF will format the sequence as a single line. When a trailing comma is included YAPF will format the sequence with one item per line.
    165 
    166 You should generally prefer to keep short definition on a single line. Break out to multiple lines sooner rather than later to aid readability and maintainability.
    167 
    168 # Parentheses
    169 
    170 Avoid excessive parentheses, but do use parentheses to make code easier to understand. Do not use them in return statements unless you are explicitly returning a tuple, or it is part of a math expression.
    171 
    172 # Format Strings
    173 
    174 We generally prefer printf-style format strings. Example:
    175 
    176 ```
    177 name = 'World'
    178 print('Hello, %s!' % (name,))
    179 ```
    180 
    181 This style is used by the logging module, which we make use of extensively, and we have adopted it in other places for consistency. It is also more familiar to C programmers, who are a big part of our casual audience.
    182 
    183 Our included CLI module has support for using these without using the percent (%) operator. Look at `cli.echo()` and the various `cli.log` functions (EG, `cli.log.info()`) for more details.
    184 
    185 # Comprehensions & Generator Expressions
    186 
    187 We encourage the liberal use of comprehensions and generators, but do not let them get too complex. If you need complexity fall back to a for loop that is easier to understand.
    188 
    189 # Lambdas
    190 
    191 OK to use but probably should be avoided. With comprehensions and generators the need for lambdas is not as strong as it once was.
    192 
    193 # Conditional Expressions
    194 
    195 OK in variable assignment, but otherwise should be avoided.
    196 
    197 Conditional expressions are if statements that are in line with code. For example:
    198 
    199 ```
    200 x = 1 if cond else 2
    201 ```
    202 
    203 It's generally not a good idea to use these as function arguments, sequence items, etc. It's too easy to overlook.
    204 
    205 # Default Argument Values
    206 
    207 Encouraged, but values must be immutable objects.
    208 
    209 When specifying default values in argument lists always be careful to specify objects that can't be modified in place. If you use a mutable object the changes you make will persist between calls, which is usually not what you want. Even if that is what you intend to do it is confusing for others and will hinder understanding.
    210 
    211 Bad:
    212 
    213 ```
    214 def my_func(foo={}):
    215     pass
    216 ```
    217 
    218 Good:
    219 
    220 ```
    221 def my_func(foo=None):
    222     if not foo:
    223         foo = {}
    224 ```
    225 
    226 # Properties
    227 
    228 Always use properties instead of getter and setter functions.
    229 
    230 ```
    231 class Foo(object):
    232     def __init__(self):
    233         self._bar = None
    234 
    235     @property
    236     def bar(self):
    237         return self._bar
    238 
    239     @bar.setter
    240     def bar(self, bar):
    241         self._bar = bar
    242 ```
    243 
    244 # True/False Evaluations
    245 
    246 You should generally prefer the implicit True/False evaluation in if statements, rather than checking equivalency.
    247 
    248 Bad:
    249 
    250 ```
    251 if foo == True:
    252     pass
    253 
    254 if bar == False:
    255     pass
    256 ```
    257 
    258 Good:
    259 
    260 ```
    261 if foo:
    262     pass
    263 
    264 if not bar:
    265     pass
    266 ```
    267 
    268 # Decorators
    269 
    270 Use when appropriate. Try to avoid too much magic unless it helps with understanding.
    271 
    272 # Threading and Multiprocessing
    273 
    274 Should be avoided. If you need this you will have to make a strong case before we merge your code.
    275 
    276 # Power Features
    277 
    278 Python is an extremely flexible language and gives you many fancy features such as custom metaclasses, access to bytecode, on-the-fly compilation, dynamic inheritance, object reparenting, import hacks, reflection, modification of system internals, etc.
    279 
    280 Don't use these.
    281 
    282 Performance is not a critical concern for us, and code understandability is. We want our codebase to be approachable by someone who only has a day or two to play with it. These features generally come with a cost to easy understanding, and we would prefer to have code that can be readily understood over faster or more compact code.
    283 
    284 Note that some standard library modules use these techniques and it is ok to make use of those modules. But please keep readability and understandability in mind when using them.
    285 
    286 # Type Annotated Code
    287 
    288 For now we are not using any type annotation system, and would prefer that code remain unannotated. We may revisit this in the future.
    289 
    290 # Function length
    291 
    292 Prefer small and focused functions.
    293 
    294 We recognize that long functions are sometimes appropriate, so no hard limit is placed on function length. If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program.
    295 
    296 Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code.
    297 
    298 You could find long and complicated functions when working with some code. Do not be intimidated by modifying existing code: if working with such a function proves to be difficult, you find that errors are hard to debug, or you want to use a piece of it in several different contexts, consider breaking up the function into smaller and more manageable pieces.
    299 
    300 # FIXMEs
    301 
    302 It is OK to leave FIXMEs in code. Why? Encouraging people to at least document parts of code that need to be thought out more (or that are confusing) is better than leaving this code undocumented.
    303 
    304 All FIXMEs should be formatted like:
    305 
    306 ```
    307 FIXME(username): Revisit this code when the frob feature is done.
    308 ```
    309 
    310 ...where username is your GitHub username.
    311 
    312 # Testing
    313 
    314 We use a combination of Integration and Unit testing to ensure that the our code is as bug-free as possible. All the tests can be found in `lib/python/qmk/tests/`. You can run all the tests with `qmk pytest`.
    315 
    316 At the time of this writing our tests are not very comprehensive. Looking at the current tests and writing new test cases for untested situations is a great way to both familiarize yourself with the codebase and contribute to QMK.
    317 
    318 ## Integration Tests
    319 
    320 Integration tests can be found in `lib/python/qmk/tests/test_cli_commands.py`. This is where CLI commands are actually run and their overall behavior is verified. We use [`subprocess`](https://docs.python.org/3.9/library/subprocess.html#module-subprocess) to launch each CLI command and a combination of checking output and returncode to determine if the right thing happened.
    321 
    322 ## Unit Tests
    323 
    324 The other `test_*.py` files in `lib/python/qmk/tests/` contain unit tests. You can write tests for individual functions inside `lib/python/qmk/` here. Generally these files are named after the module, with dots replaced by underscores.
    325 
    326 At the time of this writing we do not do any mocking for our tests. If you would like to help us change this please [open an issue](https://github.com/qmk/qmk_firmware/issues/new?assignees=&labels=cli%2C+python&template=other_issues.md&title=) or [join #cli on Discord](https://discord.gg/heQPAgy) and start a conversation there.