{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Coding in Python: Dos and Don’ts"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Content\n",
"\n",
"0. [To Code or Not to Code?](#Chapter0)\n",
"1. [Clean Code](#Chapter1)
\n",
" 1.1 [PEP 8 Quickie: Code Layout](#Chapter1.1)
\n",
" 1.2 [PEP 8 Quickie: Basic Naming Conventions](#Chapter1.2)
\n",
" 1.3 [PEP 8 Quickie: Programming Recommendations](#Chapter1.3)
\n",
" 1.4 [Static Code Analysis and PyLint](#Chapter1.4)
\n",
" 1.5 [A few more best practices](#Chapter1.5)
\n",
" 1.6 [Pythonic Code](#Chapter1.6)
\n",
"2. [Commenting & Documenting](#Chapter2)
\n",
" 2.1 [What is what](#Chapter2.1)
\n",
" 2.2 [Numpy-style docstrings](#Chapter2.2)\n",
"3. [Exception Handling and Logging](#Chapter3)
\n",
" 3.1 [Exception Handling](#Chapter3.1)
\n",
" 3.2 [Logging](#Chapter3.2)
\n",
"4. [Importing](#Chapter4)\n",
"5. [How to structure a method or function](#Chapter5)\n",
"6. [Debugging](#Chapter6)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## 0. To Code or Not to Code?\n",
"\n",
"Before you start implementing functions which then go into the climada code base, you have to ask yourself a few questions:
\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"**Has something similar already been implemented?**
\n",
"This is far from trivial to answer!
\n",
"First, search for functions in the same module where you'd be implementing the new piece of code.\n",
"Then, search in the `util` folders, there's a lot of functions in some of the scripts!\n",
"You could also search the index (a list of all functions and global constants) in the [climada documentation](https://climada-python.readthedocs.io/en/stable/genindex.html) for key-words that may be indicative of the functionality you're looking for.\n",
"\n",
"_Don't expect this process to be fast!_\n",
"\n",
"Even if you want to implement _just_ a small helper function, which might take 10mins to write, it may take you 30mins to check the existing code base! That's part of the game!
\n",
"Even if you found something, most likely, it's not the _exact_ same thing which you had in mind. Then, ask yourself how you can re-use what's there, or whether you can easily add another option to the existing method to also fit your case, and only if it's nearly impossible or highly unreadable to do so, write your own implementation."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"\n",
"\n",
"**Can my code serve others?**
\n",
"You probably have a very specific problem in mind. Yet, think about other use-cases, where people may have a similar problem, and try to either directly account for those, or at least make it easy to configure to other cases. Providing keyword options and hard-coding as few things as possible is usually a good thing. For example, if you want to write a daily aggregation function for some time-series, consider that other people might find it useful to have a general function that can also aggregate by week, month or year."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"\n",
"**Can I get started?**
\n",
"Before you finally start coding, be sure about placing them in a sensible location. Functions in non-util modules are actually specific for that module (e.g. a file-reader function is probably not river-flood specific, so put it into the `util` section, not the `RiverFlood` module, even if that's what you're currently working on)!\n",
"If unsure, talk with other people about where your code should go. \n",
"\n",
"If you're implementing more than just a function or two, or even an entirely new module, the planning process should be talked over with someone doing climada-administration. "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## 1. Clean Code \n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"**A few basic principles:**\n",
"\n",
"* Follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) Style Guide. It contains, among others, recommendations on:\n",
" * code layout \n",
" * basic naming conventions \n",
" * programming recommendations\n",
" * commenting (in detail described in Chapter 4)\n",
" * varia\n",
"* Perform a static code analysis - or: PyLint is your friend\n",
"* Follow the best practices of _Correctness - Tightness - Readability_\n",
"* Adhere to principles of pythonic coding (idiomatic coding, the \"python way\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### 1.1 PEP 8 Quickie: Code Layout \n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"\n",
"* _Indentation_: 4 spaces per level. For continuation lines, decide between vertical alignment & hanging indentation as shown here:\n",
"
\n",
" # Vertically aligned with opening delimiter.\n",
" foo = long_function_name(var_one, var_two,\n",
" var_three, var_four)\n",
"\n",
" # Hanging indentation (4 additonal spaces)\n",
" def very_very_long_function_name(\n",
" var_one, var_two, var_three,\n",
" var_four):\n",
" print(var_one)\n",
"
\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* _Line limit_: maximum of 79 characters (docstrings & comments 72).\n",
"* _Blank lines_: \n",
" * **Two**: Surround top-level function and class definitions; \n",
" * **One:** Surround Method definitions inside a class \n",
" * **Several:** may be used (sparingly) to separate groups of related functions\n",
" * **None:** Blank lines may be omitted between a bunch of related one-liners (e.g. a set of dummy implementations)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* _Whitespaces_: \n",
" * **None** immediately inside parentheses, brackets or braces; after trailing commas; for keyword assignments in functions. \n",
" * **Do** for assignments (`i = i + 1`), around comparisons (`>=`, `==`, etc.), around booleans (`and`, `or`, `not`) \n",
" \n",
" # the following 3 examples are correct:\n",
" spam(ham[1], {eggs: 2})\n",
" if x == 4: print x, y; x, y = y, x\n",
" def complex(real, imag=0.0):\n",
"
\n",
"* There's more in the PEP 8 guide!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### 1.2 PEP 8 Quickie: Basic Naming Conventions \n",
"**A short typology:** \n",
"# Correct\n",
"def foo(x):\n",
" if x >= 0:\n",
" return math.sqrt(x)\n",
" else:\n",
" return None\n",
"# Wrong\n",
"def foo(x):\n",
" if x >= 0:\n",
" return math.sqrt(x)\n",
"
\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* Object type comparisons should always use isinstance() instead of comparing types directly: \n",
" \n",
"# Correct: \n",
"if isinstance(obj, int):\n",
"# wrong:\n",
"if type(obj) is type(1)\n",
"
\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* Remember: sequences (strings, lists, tuples) are false if empty; this can be used: \n",
" \n",
"# Correct:\n",
"if not seq:\n",
"if seq:\n",
"# Wrong:\n",
"if len(seq):\n",
"if not len(seq)\n",
"
"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* Don't compare boolean values to True or False using `==`:\n",
" \n",
"# Correct:\n",
"if greeting: \n",
"# Wrong:\n",
"if greeting == True: \n",
"
\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"* Use ''.startswith() and ''.endswith() instead of string slicing to check for prefixes or suffixes.\n",
" \n",
"# Correct:\n",
"if foo.startswith('bar'):\n",
"# Wrong:\n",
"if foo[:3] == 'bar':\n",
"
\n",
"\n",
"* Context managers exist and can be useful (mainly for opening and closing files"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### 1.4 Static Code Analysis and PyLint \n",
"\n",
"Static code analysis detects style issues, bad practices, potential bugs, and other quality problems in your code, all without having to actually execute it. In Spyder, this is powered by the best in class Pylint back-end, which can intelligently detect an enormous and customizable range of problem signatures. It follows the style recommended by PEP 8 and also includes the following features:\n",
"Checking the length of each line, checking that variable names are well-formed according to the project's coding standard, checking that declared interfaces are truly implemented.\n",
"\n",
"@uppercase_decorator\n",
"def say_hi():\n",
" return 'hello there'\n",
"
\n",
" \n",
"* type checking (Python is a dynamically typed language; also: cf. \"Duck typing\". Yet, as a best practice, variables should not change type once assigned)\n",
"* Do not use mutable default arguments in your functions (e.g. lists). For example, if you define a function as such:\n",
"\n",
" ```\n",
" def function(x, list=[]): \n",
" default_list.append(x)\n",
" ```\n",
" Your list will be mutated for future calls of the functions too.\n",
" The correct implementation would bethe following:\n",
" ```\n",
" def func(x, list=None):\n",
" list = [] if list is None\n",
" ```\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"\n",
"* lambda functions (little, anonymous functions,