# Standars and best practices [TOC] ## git ### General recommendations * **Good branch names** * **wip**: Works in progress; stuff I know won't be finished soon * **feat**: Feature I'm adding or expanding * **bug**: Bug fix or experiment * **junk**: Throwaway branch created to experiment * **new**/frabnotz * **new**/foo * **new**/bar * **test**/foo * **test**/frabnotz ```sh $ git branch --list "test/*" test/foo test/frabnotz $ git branch --list "*/foo" new/foo test/foo ``` ![](https://i.imgur.com/OJcexEq.png) * **Good commits messages** * A good commit should contain a set of homogeneus and relevant changes. * **Subject** * Simple but explanatory * Capitalized, without period at the end of line. * Max of 50 characters!! * Imperative mode * Clean your room * Close the door * Take out the trash * **Body** * Just if it is necessary * Max of 72 characters!! * Explain what and why vs. how > Fix typo in introduction to user guide > Accelerate to 88 miles per hour > Open the pod bay doors > Derezz the master control program > MCP turned out to be evil and had become intent on world domination. This commit throws Tron's disc into MCP (causing its deresolution) and turns it back into a chess game. [Más info: Pro git](https://git-scm.com/book/en/v2) ## Repo structure ``` name-of-repo ├── .env.example ├── .gitignore ├── docs ├── dev │   └── Dockerfile ├── prod │   └── Dockerfile ├── docker-compose.yml ├── name-of-repo │   └── __init__.py │   └── file_1.py │   └── file_2.py │   └── utils.py │   └── module_3 │    └── __init__.py ├── README.md ├── requirements.txt └── tests ├── __init__.py └── test_name-of-repo.py └── config.py └── main_file.py ``` ### README file Template ``` markdown # Name Description # Table of contents [](#) [TOC] ## Features * feature 1 * feature 2 ## Installation (step by step) \`\`\` \`\`\` ## Python version ## Usage \`\`\` \`\`\` ## Examples ## Test ## Deployment scenario ## TODO ## License [MIT](https://choosealicense.com/licenses/mit/) ``` ### Plus: budges! [A collection of budges](https://gist.github.com/tterb/982ae14a9307b80117dbf49f624ce0e8) [![NPM Version](https://img.shields.io/npm/v/npm.svg?style=flat)]() `[![NPM Version](https://img.shields.io/npm/v/npm.svg?style=flat)]()` [![PyPi Python Versions](https://img.shields.io/pypi/pyversions/yt2mp3.svg)](https://pypi.python.org/pypi/yt2mp3/) `[![PyPi Python Versions](https://img.shields.io/pypi/pyversions/yt2mp3.svg)](https://pypi.python.org/pypi/yt2mp3/)` ### .env **IT MUST NOT** be added! Add and example. Environmental variables text file with key, value pairs: ``` key_1 = value_1 key_2 = value_2 ``` They could be acceded by: ```python import os value_1 = os.getenv('key_1') ``` ### .gitignore Default python .gitignore ```gitignore # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # vscode .idea/ .vscode/ # C extensions *.so # Distribution / packaging .Python .env env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ ``` #### Generate a repo from scratch *with poetry* ```python pip install poetry poetry new <name> ``` ## Python code ### Great names, code doc and comments * Function: lowercase words separated by underscores, ex: `my_function`. * Choose a self explanatory name with infinitive verbs, ex: ```python def multiply_by_two(x): return x * 2 ``` * Variable: lowercase, single words separated by underscore, ex: `my_variable` * Choose good names! Avoid n, x, y, a. * Class: capitalize words, ex: `MyClass`. * Method: lowercase words separated by underscores, ex: `class_method`. * Constant: Uppercase single words separated by underscores, ex: `MY_CONSTANT`. * Module: Short, lowercase words separated by underscores: `my_module.py` * Package: Short, lowercase words separated: `mypackage` * Comments: Capitalize, double space after a line. #### Docstring reST style ##### functions ```python def my_function(param_1, param_2): """ Return / Description :param param_1: (type) description :param param_2: (type) description :return: (type) description """ ``` ###### Example ```python def def multiply_by_two(x): """ Return the result of multiplying by two the x param :param x: (float) number to multiply :return: (float) param x multiplied by two """ return x * 2 ``` ##### classes ```python class Keeper(Storer): """ Keep data fresher longer. Extend `Storer`. Class attribute `instances` keeps track of the number of `Keeper` objects instantiated. """ instances = 0 """How many `Keeper` objects are there?""" def __init__(self): """ Extend `Storer.__init__()` to keep track of instances. Keep count in `self.instances` and data in `self.data`. """ Storer.__init__(self) self.instances += 1 self.data = [] """Store data in a list, most recent last.""" def storedata(self, data): """ Extend `Storer.storedata()`; append new `data` to a list (in `self.data`). """ self.data = data ``` ###### example ```python class MyClass(BaseClass): """ Short class description. Extend `BaseClass`. Important notes about attributes/methods/instances. """ def __init__(self): """ Extend `BaseClass.__init__()`. """ BaseClass.__init__(self) self.attr_1 += 1 self.attr_2 = [] def method_1(self, param): """ Extend `BaseClass.method_1()`; short method description """ self.attr_2 = param ``` #### Inline comments They must add valuable information * In the same line of code that it is refering to * Capitalize * 2 spaces and # before the comment ```python x = 5 # This is an inline comment ``` ### Whitespaces * Operators ```python x = 5 y = x * 2 if z >= x: # ... if z is not None: # ... ``` * Exceptions ```python y = x**2 + 5 z = (x+y) * (x-y) if x>5 and x%2==0: # ... list[x+1 : x+2] def function(default_parameter=5): # ... ``` When DO NOT add whitespaces: ```python print(x, y) double(3) list[3] tuple = (1,) ``` ### Blank lines * **2** before a class/function definition. ```python class MyFirstClass: pass class MySecondClass: pass def top_level_function(): return None ``` * **1** between class method definitions ```python class MyClass: def first_method(self): return None def second_method(self): return None ``` * **1** between group of streps inside functions ```python def calculate_variance(number_list): sum_list = 0 for number in number_list: sum_list = sum_list + number mean = sum_list / len(number_list) sum_squares = 0 for number in number_list: sum_squares = sum_squares + number**2 mean_squares = sum_squares / len(number_list) return mean_squares - mean**2 ``` ### Lenght of line: 79!!! Ways to guarantee max length of lines: ```python def function(arg_one, arg_two, arg_three, arg_four): return arg_one from mypkg import example1, \ example2, example3 total = (first_variable + second_variable - third_variable) ``` ### Identations ```python def function(arg_one, arg_two, arg_three, arg_four): return arg_one x = 5 if (x > 3 and x < 10): print(x) list_of_numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ``` ### imports 1. standard library imports 2. related third party imports 3. local application/library specific imports * linter: isort ### Recommendations * Don’t compare Boolean values to True or False using the equivalence operator. ```python # if my_bool == True: if my_bool: return '6 is bigger than 5' ``` * Empty list should be interpreted as a False statement: ```python my_list = [] # if not len(my_list): if not my_list: print('List is empty!') ``` * Use is not instead of not None ```python # if not x is None: if x is not None: return 'x exists!' ``` ### List of PEP * [PEP8](https://www.python.org/dev/peps/pep-0008/) * [PEP257](https://www.python.org/dev/peps/pep-0257/) ### Linters * isort ```bash $ pip install isort $ isort code.py ``` * black ```bash $ pip install black $ black code.py ``` * flake8 ```bash $ pip install flake8 $ flake8 code.py ``` * pycodestyle ```bash $ pip install pycodestyle $ pycodestyle code.py ``` #### Extra python code recommendations ##### Check edge cases ##### Arg parser ``` python import argparse parser = argparse.ArgumentParser(description='Description.') parser.add_argument('--foo', type=int, default=42, help='FOO!') args = parser.parse_args() ``` ##### unittest ```python import unittest class WidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget') def test_default_widget_size(self): self.assertEqual(self.widget.size(), (50,50), 'incorrect default size') def test_widget_resize(self): self.widget.resize(100,150) self.assertEqual(self.widget.size(), (100,150), 'wrong size after resize') ``` `python -m unittest tests/test_something.py` ##### typehints ```python def greeting(name: str) -> str: return 'Hello ' + name Vector = list[float] def scale(scalar: float, vector: Vector) -> Vector: ConnectionOptions = dict[str, str] Address = tuple[str, int] Server = tuple[Address, ConnectionOptions] def broadcast_message(message: str, servers: Sequence[Server]) ``` ##### dataclasses ```python from dataclasses import dataclass @dataclass class InventoryItem: """Class for keeping track of an item in inventory.""" name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand # Will add, among other things, a __init__() that looks like: def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0): self.name = name self.unit_price = unit_price self.quantity_on_hand = quantity_on_hand ``` ## Documentation