diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a8a82d9..0000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length = 120 -max-complexity=10 -exclude = - */migrations/* - __pycache__ - manage.py - settings.py - env - .env - ./env - env/ - .env/ - .venv/ - inspectdb_models diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b4a66db..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: ci -on: - push: - branches: - - master -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install mkdocs-material mkdocs-jupyter - - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b24d71e..0000000 --- a/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - -# Node artifact files -node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - diff --git a/sample_django_project/library/__init__.py b/.nojekyll similarity index 100% rename from sample_django_project/library/__init__.py rename to .nojekyll diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 9919d1b..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,34 +0,0 @@ -default_language_version: - python: python3.10 -repos: - - repo: https://github.com/psf/black - rev: 22.3.0 # Replace by any tag/version: https://github.com/psf/black/tags - hooks: - - id: black - - repo: https://github.com/pycqa/isort - rev: "5.10.1" # Use the revision sha / tag you want to point at - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: "4.0.1" - hooks: - - id: flake8 - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 # Use the ref you want to point at - hooks: - - id: check-added-large-files - args: ["--maxkb=1024"] # throw an error if we try to commit a 1MB or greater file - - id: check-case-conflict # so we don't rename files that will break case insensitive filesystems - - id: check-merge-conflict # don't accidentally commit files with incomplete merges - - id: end-of-file-fixer # makes all files end in a newline - - id: mixed-line-ending # fixes mixed line endings automatically - - id: no-commit-to-branch - args: ["-b master"] # no commits to master - - repo: local - hooks: - - id: tests - name: run tests - always_run: true # Not required but ensures that the tests always run even if there are no matching. - entry: make test # The command that calls the tests. In this case, it's a command in a Makefile. - language: system # It's a system hook and no specific virtual environment will be provided for the hook's execution; - pass_filenames: false # Don't try to pass the names of the files that were changed to the hook entry; diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 93989d0..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "runserver", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/sample_django_project/manage.py", - "args": [ - "runserver" - ], - "env": { - "SECRET_KEY": "qwert" - }, - "django": true, - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 72a7850..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "sample_django_project" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "python.envFile": "${workspaceFolder}/sample_django_project/.env", -} \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..424b62c --- /dev/null +++ b/404.html @@ -0,0 +1,1006 @@ + + + + + + + + + + + + + + + + + + Python Knowledge Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 89e2659..0000000 --- a/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Python Knowledge Base - -Available at [https://runtimerevolution.github.io/python-knowledge-base/](https://runtimerevolution.github.io/python-knowledge-base/) - -## Setup - -This project uses Poetry [https://python-poetry.org/](https://python-poetry.org/), make sure it's present on your system. - -1. clone project -2. `cd python-knowledge-base` -3. run `poetry install` -4. run `poetry run mkdocs serve` diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.51198bba.min.js b/assets/javascripts/bundle.51198bba.min.js new file mode 100644 index 0000000..31bd041 --- /dev/null +++ b/assets/javascripts/bundle.51198bba.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ri=Object.create;var gr=Object.defineProperty;var ki=Object.getOwnPropertyDescriptor;var Hi=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Pi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,on=Object.prototype.propertyIsEnumerable;var nn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&nn(e,r,t[r]);if(Ht)for(var r of Ht(t))on.call(t,r)&&nn(e,r,t[r]);return e};var an=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&on.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var $i=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Hi(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=ki(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ri(Pi(e)):{},$i(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var cn=Pt((xr,sn)=>{(function(e,t){typeof xr=="object"&&typeof sn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function c(T){var Qe=T.type,De=T.tagName;return!!(De==="INPUT"&&s[Qe]&&!T.readOnly||De==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function f(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(a(r.activeElement)&&f(r.activeElement),n=!0)}function m(T){n=!1}function d(T){a(T.target)&&(n||c(T.target))&&f(T.target)}function h(T){a(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),G())}function G(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function oe(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,oe())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),G(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var fn=Pt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(f){return!1}},r=t(),n=function(f){var u={next:function(){var p=f.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(f){return encodeURIComponent(f).replace(/%20/g,"+")},i=function(f){return decodeURIComponent(String(f).replace(/\+/g," "))},s=function(){var f=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof f){var d=this;p.forEach(function(oe,N){d.append(N,oe)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),f._entries&&(f._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(c,f){typeof c!="string"&&(c=String(c)),f&&typeof f!="string"&&(f=String(f));var u=document,p;if(f&&(e.location===void 0||f!==e.location.href)){f=f.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=f,u.head.appendChild(p);try{if(p.href.indexOf(f)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+f+" due to "+T)}}var m=u.createElement("a");m.href=c,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=c,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!f)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,G=!0,oe=this;["append","delete","set"].forEach(function(T){var Qe=h[T];h[T]=function(){Qe.apply(h,arguments),v&&(G=!1,oe.search=h.toString(),G=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,G&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(c){Object.defineProperty(s,c,{get:function(){return this._anchorElement[c]},set:function(f){this._anchorElement[c]=f},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(c){a(c)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(c){this._anchorElement.search=c,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var c=this;return function(){return c.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(c){this._anchorElement.href=c,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(c){this._anchorElement.pathname=c},enumerable:!0},origin:{get:function(){var c={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],f=this._anchorElement.port!=c&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(f?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(c){},enumerable:!0},username:{get:function(){return""},set:function(c){},enumerable:!0}}),i.createObjectURL=function(c){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(c){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var Kr=Pt((Mt,qr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof qr=="object"?qr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ci}});var s=i(279),a=i.n(s),c=i(370),f=i.n(c),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var E=p()(O);return m("cut"),E},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[O?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var G=function(O,E){var H=v(O);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},oe=function(O){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof O=="string"?H=G(O,E):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?H=G(O.value,E):(H=p()(O),m("copy")),H},N=oe;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(E){return typeof E}:T=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},T(j)}var Qe=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=O.action,H=E===void 0?"copy":E,I=O.container,q=O.target,Me=O.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function wi(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function rn(j,O){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=f()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",kt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),E}(a()),Ci=Ai},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=f.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function c(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function f(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function c(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return f(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function f(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=c},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),f=document.createRange();f.selectNodeContents(i),c.removeAllRanges(),c.addRange(f),s=c.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function f(){c.off(i,f),s.apply(a,arguments)}return f._=s,this.on(i,f,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,f=a.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var ns=/["'&<>]/;Go.exports=os;function os(e){var t=""+e,r=ns.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(f[0]===6||f[0]===2)){r=0;continue}if(f[0]===3&&(!i||f[1]>i[0]&&f[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{c(n[m](d))}catch(h){p(i[0][3],h)}}function c(m){m.value instanceof et?Promise.resolve(m.value.v).then(f,u):p(i[0][2],m)}function f(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function ln(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),o(a,c,s.done,s.value)})}}function o(i,s,a,c){Promise.resolve(c).then(function(f){i({value:f,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),c=a.next();!c.done;c=a.next()){var f=c.value;f.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(C(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{mn(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)mn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function jt(e){return e instanceof Ie||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function mn(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new En(r,n)},t}(F);var En=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,c=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var Tn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var Te=new Tn(Sn);var _=new F(function(e){return e.complete()});function Vt(e){return e&&C(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return C(Cr(e))?e.pop():void 0}function Oe(e){return Vt(Cr(e))?e.pop():void 0}function zt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return C(e==null?void 0:e.then)}function qt(e){return C(e[ft])}function Kt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ni(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=Ni();function Gt(e){return C(e==null?void 0:e[Yt])}function Bt(e){return pn(this,arguments,function(){var r,n,o,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return C(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(qt(e))return qi(e);if(pt(e))return Ki(e);if(Nt(e))return Qi(e);if(Kt(e))return On(e);if(Gt(e))return Yi(e);if(Jt(e))return Gi(e)}throw Qt(e)}function qi(e){return new F(function(t){var r=e[ft]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Ki(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?A(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Vn(function(){return new Zt}))}}function zn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(f){var u,p,m,d=0,h=!1,v=!1,G=function(){p==null||p.unsubscribe(),p=void 0},oe=function(){G(),u=m=void 0,h=v=!1},N=function(){var T=u;oe(),T==null||T.unsubscribe()};return y(function(T,Qe){d++,!v&&!h&&G();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,c))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,G(),p=$r(oe,o,$e),De.error($e)},complete:function(){h=!0,G(),p=$r(oe,s),De.complete()}}),U(T).subscribe(u))})(f)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),B())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Qn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,Te),l(()=>Xe(e)),V(Xe(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Te),l(()=>rr(e)),V(rr(e)))}var Gn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Dr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),ga?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Dr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=va.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Bn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Xn=typeof WeakMap!="undefined"?new WeakMap:new Gn,Zn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ya.getInstance(),n=new Aa(t,r,this);Xn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Zn.prototype[e]=function(){var t;return(t=Xn.get(this))[e].apply(t,arguments)}});var Ca=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:Zn}(),eo=Ca;var to=new x,Ra=$(()=>k(new eo(e=>{for(let t of e)to.next(t)}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var ro=new x,ka=$(()=>k(new IntersectionObserver(e=>{for(let t of e)ro.next(t)},{threshold:0}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),J(1));function sr(e){return ka.pipe(S(t=>t.observe(e)),g(t=>ro.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function no(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),B())}var cr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function oo(e){return cr[e].checked}function Ke(e,t){cr[e].checked!==t&&cr[e].click()}function Ue(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function Ha(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Pa(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function io(){let e=b(window,"keydown").pipe(A(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:oo("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),A(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!Ha(n,r)}return!0}),pe());return Pa().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function ao(){return new x}function so(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)so(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)so(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function co(){return location.hash.substring(1)}function Vr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function $a(e){return L(b(window,"hashchange"),e).pipe(l(co),V(co()),A(t=>t.length>0),J(1))}function fo(e){return $a(e).pipe(l(t=>ce(`[id="${t}"]`)),A(t=>typeof t!="undefined"))}function zr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function uo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function Nr(e,t){return e.pipe(g(r=>r?t():_))}function ur(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Tt(()=>new Error(r.statusText)):k(r)))}function We(e,t){return ur(e,t).pipe(g(r=>r.json()),J(1))}function po(e,t){let r=new DOMParser;return ur(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(g(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function lo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function mo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(lo),V(lo()))}function ho(){return{width:innerWidth,height:innerHeight}}function bo(){return b(window,"resize",{passive:!0}).pipe(l(ho),V(ho()))}function vo(){return Q([mo(),bo()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(Z("size")),o=Q([n,r]).pipe(l(()=>Xe(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:c,y:f}])=>({offset:{x:s.x-c,y:s.y-f+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,c,f)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:c,error:f});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + +

Code Principles

+

Code should be clear and easy to maintain, by maintaining we also mean writing and +revisiting tests if necessary. Below there's a list of principles which used +with The Zen of Python, will help us to write better code as well as to test it.

+

KISS - Keep it simple, stupid 1

+

The snippet below is hard to read and takes time to understand

+
f = lambda x: x if x in {0, 1} else f(x - 1) + f(x - 2)
+
+

Alternatively, the next code snippet is much easier to understand and maintain.

+
def fibonacci(number: int) -> int:
+    if number in {0, 1}:
+        return number
+    return fibonacci(number - 1) + fibonacci(number - 2)
+
+

By adding descriptive names, type hints, and splitting the line into multiple ones it gets +easier to read and maintain.

+
+

The main goal in design should always be to be as easy to understand +as possible.

+
+

DRY - Don't repeat yourself 2

+

This principle is about writing functions and automating sections of code that are repeated. If you perform the same +task multiple times in your code, consider a function or a loop to make your workflow more efficient.

+

Let's consider the next example where we'll be using a math expression to convert temperature values from fahrenheit +to celsius.

+
temp_1 = 32
+res_1 = (temp_1-32) * 5/9
+
+temp_2 = 40
+res_2 = (temp_2-32) * 5/9
+
+

Some points to consider:

+
    +
  • If the calculation changes, we'd need to update both expressions
  • +
  • it's not clear what the math expression is calculating, unless you're familiar with it
  • +
+

Let's create a method for the math expression.

+
def conv_fahr_to_celsius(fahr: float) -> float:
+    """Convert temperature in Fahrenheit to Celsius.
+
+    Parameters:
+    -----------
+    fahr: float
+        The temperature in Fahrenheit.
+
+    Returns:
+    -----------
+    Celsius : int or float
+        The temperature in Celsius.
+    """
+    celsius = (fahr-32) * 5/9
+    return celsius
+
+

And update the previous sample to use this method.

+
fahr_1 = 32
+celsius_1 = conv_fahr_to_celsius(fahr_1)
+
+fahr_2 = 40
+celsius_2 = conv_fahr_to_celsius(fahr_2)
+
+

In summary

+
    +
  • The code is cleaner, because the repeated calculation was replaced with a function
  • +
  • If this function is well-defined with a docstring that describes what it does, it is easier to +both understand and use.
  • +
  • If you need to change the calculation itself, you can do so once in the function
  • +
+

SoC - Separation of concerns 1

+

A known example of this is the model-view-controller (MVC) design. MVC separates a program +into three distinct areas: the data (model), the logic (controller), and what the page displays (view).

+

SOLID 3

+

SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, +and maintainable.

+
    +
  • Single-responsibility principle: "A class should have one, and only one, reason to change."
  • +
  • Open–closed principle: "Entities should be open for extension, but closed for modification."
  • +
  • Liskov substitution principle: "Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it."
  • +
  • Interface segregation principle: "A client should not be forced to implement an interface that it doesn’t use."
  • +
  • Dependency inversion principle: "Depend upon abstractions, not concretions."
  • +
+

Single-responsibility principle (SRP)

+

Every component of your code (in general a class, but also a function) should have one and only one responsibility. +As a consequence of that, there should be only a reason to change it.

+

Too often you see a piece of code that takes care of an entire process all at once. I.e., A function that loads data, +modifies and, plots them, all before returning its result.

+

Let’s take a simpler example, where we have a list of number L = [n1, n2, …, nx] and we compute some mathematical +functions to this list. For example, compute the mean, median, etc.

+

A bad approach would be to have a single function doing all the work:

+
import numpy as np
+
+def math_operations(list_):
+    # Compute Average
+    print(f"the mean is {np.mean(list_)}")
+    # Compute Max
+    print(f"the max is {np.max(list_)}") 
+
+math_operations(list_ = [1,2,3,4,5])
+# the mean is 3.0
+# the max is 5
+
+

The first thing we should do, to make this more SRP compliant, is to split the function math_operations into atomic +functions! Thus, when a function’s responsibility cannot be divided into more sub-parts.

+

The second step is to make a single function (or class), generically named, “main”. This will call all the other +functions one-by-one in a step-to-step process.

+
import numpy as np
+
+def get_mean(list_):
+    """
+        Compute Mean
+    """
+    print(f"the mean is {np.mean(list_)}") 
+
+def get_max(list_):
+    """
+        Compute Max
+    """
+    print(f"the max is {np.max(list_)}") 
+
+def main(list_): 
+    # Compute Average
+    get_mean(list_)
+    # Compute Max
+    get_max(list_)
+
+main([1,2,3,4,5])
+# the mean is 3.0
+# the max is 5
+
+

Now, you would only have one single reason to change each function connected with “main”.

+

The result of this simple action is that now:

+
    +
  1. It is easier to localize errors. Any error in execution will point out to a smaller section of your code, +accelerating your debug phase.
  2. +
  3. Any part of the code is reusable in other section of your code.
  4. +
  5. Moreover and, often overlooked, is that it is easier to create testing for each function of your code. +Side note on testing: You should write tests before you actually write the script. But, this is often ignored in +favour of creating some nice result to be shown to the stakeholders instead.
  6. +
+

This is already a much bigger improvement with respect to the first code example. But, having created a “main” and +calling functions with single responsibility is not the full fulfilment of the SR principle. Indeed, our “main” has +many reasons to be changed. The class is actually fragile and hard to maintain. To solve that, let’s introduce the +next principle.

+

Open/Closed principle (OCP)

+

You should not need to modify the code you have already written to accommodate new functionality, but simply add what +you now need.

+

This does not mean that you cannot change your code when the code premises needs to be modified, but that if you need +to add new functions similar to the one present, you should not require to change other parts of the code. To clarify +this point let’s refer to the example we saw earlier. If we wanted to add new functionality, for example, compute the +median, we should have created a new method function and add its invocation to “main”. That would have added an +extension but also modified the main.

+

We can solve this by turning all the functions we wrote into subclasses of a class. In this case, I have created an +abstract class called “Operations” with an abstract method “get_operation”. (Abstract classes are generally an +advanced topic. If you don’t know what an abstract class is, you can run the following code even without).

+

Now, all the old functions, now classes are called by the subclasses() method. That will find all classes inheriting +from Operations and operate the function “operations” that is present in all subclasses.

+
+

for additional information on abstractmethod decorator please +check https://docs.python.org/3/library/abc.html#abc.abstractmethod

+
+
import numpy as np
+from abc import ABC, abstractmethod
+
+class Operations(ABC):
+    """Operations"""
+    @staticmethod
+    @abstractmethod
+    def operation(list_):
+        pass
+
+class Mean(Operations):
+    """Compute Max"""
+    @staticmethod
+    def operation(list_):
+        print(f"The mean is {np.mean(list_)}")
+
+class Max(Operations):
+    """Compute Max"""
+    @staticmethod
+    def operation(list_):
+        print(f"The max is {np.max(list_)}")
+
+class Main:
+    """Main"""
+    @staticmethod
+    @abstractmethod
+    def get_operations(list_):
+        # __subclasses__ will find all classes inheriting from Operations
+        for operation in Operations.__subclasses__():
+            operation.operation(list_)
+
+
+if __name__ == "__main__":
+    Main.get_operations([1,2,3,4,5])
+# The mean is 3.0
+# The max is 5
+
+

If now we want to add a new operation e.g.: median, we will only need to add a class “Median” inheriting from the class +“Operations”. The newly formed subclass will be immediately picked up by subclasses() and no modification in any +other part of the code needs to happen.

+

The result is a very flexible class, that requires minimum time to be maintained.

+

The Liskov substitution principle (LSP)

+

Functions that use pointers or references to base classes must be able to use objects of derived classes without +knowing it, that alternatively can be expressed as, derived classes must be substitutable for their base classes.

+

In (maybe) simpler words, if a subclass redefines a function also present in the parent class, a client-user should not +be noticing any difference in behaviour, and it is a substitute for the base class. For example, if you are using a +function and your colleague change the base class, you should not notice any difference in the function that you are +using.

+

Among all the SOLID principle, this is the most abstruse to understand and to explain. For this principle, there is no +standard “template-like” solution where it must be applied, and it is hard to offer a “standard example” to showcase.

+

In the most simplistic way, I can put it, this principle can be summarised by saying: +If in a subclass, you redefine a function that is also present in the base class, the two functions ought to have the +same behaviour. This, though, does not mean that they must be mandatory equal, but that the user, should expect that +the same type of result, given the same input. +In the example ocp.py, the “operation” method is present in the subclasses and in the base class, and an end-user should +expect the same behaviour from the two.

+

The result of this principle is that we’d write our code in a consistent manner and, the end-user will need to learn how +our code works, only one.

+
+

A consequence of LSP is that: the new redefined function in the subclass should be valid and +be possibly used wherever the same function in the parent class is used.

+

This is not, typically the case, indeed usually we, human, think in terms of set theory. +Having a class that define a concept and subclasses that expand the first with an +exception or different behaviour.

+

For example, the subclass “Platypus”, of the base class “Mammals”, would have the +exception that these mammals lay eggs. The LSP, tell us that it would create a +function called “give_birth”, this function will have different behaviour +for the subclass Platypus and the subclass Dog. Therefore, we should have had a +more abstract base class than Mammals that accommodate this. +If this sounds very confusing, do not worry, the application of this latter aspect +of the LSP is rarely fully implemented, and it rarely leaves the theoretical textbooks.

+
+

The Interface Segregation Principle (ISP)

+

Many client-specific interfaces are better than one general-purpose interface. In the context of classes, an interface +is considered, all the methods and properties exposed, thus, everything that a user can interact with that belongs to +that class.

+

In this sense, the IS principles tell us that a class should only have the interface needed (SRP) and avoid methods +that won’t work or that have no reason to be part of that class.

+

This problem arises, primarily, when, a subclass inherits methods from a base class that it does not need.

+

Let’s see an example:

+
+

for additional information on abstractmethod decorator please +check https://docs.python.org/3/library/abc.html#abc.abstractmethod

+
+
from abc import ABC, abstractmethod
+
+class Mammals(ABC):
+    @staticmethod
+    @abstractmethod
+    def swim():
+        print("Can Swim")
+
+    @staticmethod
+    @abstractmethod
+    def walk():
+        print("Can Walk")
+
+class Human(Mammals):
+    @staticmethod
+    def swim():
+        return print("Humans can swim")
+
+    @staticmethod
+    def walk():
+        return print("Humans can walk")
+
+class Whale(Mammals):
+    @staticmethod
+    def swim():
+        return print("Whales can swim") 
+
+

For this example, we have got the abstract class “Mammals” that has two abstract methods: “walk” and “swim”. These two +elements will belong to the subclass “Human”, whereas only “swim” will belong to the subclass “Whale”.

+

And indeed, if we run this code we could have:

+
Human.swim()
+Human.walk()
+
+Whale.swim()
+Whale.walk()
+
+# Humans can swim
+# Humans can walk
+# Whales can swim
+# Can Walk
+
+

The subclass whale can still invoke the method “walk” but it shouldn’t, and we must avoid it.

+

The way suggested by ISP is to create more client-specific interfaces rather than one general-purpose interface. +So, our code example becomes:

+
from abc import ABC, abstractmethod
+
+class Walker(ABC):
+    @staticmethod
+    @abstractmethod
+    def walk():
+        return print("Can Walk")
+
+class Swimmer(ABC):
+    @staticmethod
+    @abstractmethod
+    def swim():
+        return print("Can Swim")
+
+class Human(Walker, Swimmer):
+    @staticmethod
+    def walk():
+        return print("Humans can walk")
+    @staticmethod
+    def swim():
+        return print("Humans can swim")
+
+class Whale(Swimmer):
+    @staticmethod
+    def swim():
+        return print("Whales can swim") 
+
+if __name__ == "__main__":
+  Human.walk()
+  Human.swim()
+
+  Whale.swim()
+  Whale.walk()
+
+# Humans can walk
+# Humans can swim
+# Whales can swim
+# AttributeError: type object 'Whale' has no attribute 'walk'
+
+

Now, every subclass inherits only what it needs, avoiding invoking an out-of-context (wrong) sub-method. That might +create an error hard to catch.

+

This principle is closely connected with the other ones and specifically, it tells us to keep the content of a subclass +clean from elements of no use to that subclass. This has the final aim to keep our classes clean and minimise mistakes.

+

Dependency Inversion Principle (DIP)

+

Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend +on low-level modules. Both should depend on abstractions. So, that abstractions (e.g., the interface, as seen above) +should not be dependent on low-level methods but both should depend on a third interface.

+

To better explain this concept, I prefer to think of a sort of information flow.

+

Imagine that you have a program that takes in input a specific set of info (a file, a format, etc) and you wrote a +script to process it. What would happen if that info were subject to change? +You would have to rewrite your script and adjust the new format. Losing the retro compatibility with the older files.

+

However, you could solve this by creating a third abstraction that takes the info as input and passes it to the others. +This is basically what an API is also, used for.

+
flowchart LR
+    ObjectA --> |references| ObjectB
+    subgraph Package B
+    ObjectB
+    end
+    subgraph Package A
+    ObjectA
+    end
+
flowchart TB
+    ObjectB --> |inherits| Interface
+    subgraph Package B
+    ObjectB
+    end
+    subgraph Package A
+    ObjectA--> |references| Interface
+    end
+

The interesting design concept of this principle is that it is the reverse approach to what we would normally do.

+

With the DIP in mind, we would start from the end of the project, in which our code is independent of what takes in +input, and it is not susceptible to changes and out of our direct control.

+

YAGNI - You ain't gonna need it

+

It's a mantra from Extreme Programming that's often used generally in agile software teams. It's a statement that some +capability we presume our software needs in the future should not be built now because "you aren't gonna need it".

+
+

For additional information on this make sure to +check https://www.martinfowler.com/bliki/Yagni.html

+
+

Document your code

+
    +
  1. Don't comment bad code, rewrite it
  2. +
  3. Readable code doesn't need comments
  4. +
  5. Don't add noise comments
  6. +
+
+
+
    +
  1. +

    https://testdriven.io/blog/clean-code-python/ 

    +
  2. +
  3. +

    https://www.earthdatascience.org/courses/intro-to-earth-data-science/write-efficient-python-code/intro-to-clean-code/dry-modular-code/ 

    +
  4. +
  5. +

    https://towardsdatascience.com/solid-coding-in-python-1281392a6a94 

    +
  6. +
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/best_practices/styleguide/index.html b/best_practices/styleguide/index.html new file mode 100644 index 0000000..7343011 --- /dev/null +++ b/best_practices/styleguide/index.html @@ -0,0 +1,5701 @@ + + + + + + + + + + + + + + + + + + + + + + Style Guide - Python Knowledge Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Style Guide

+
+

Inspired by https://google.github.io/styleguide/pyguide.html and +https://phalt.github.io/django-api-domains/styleguide/.

+
+

Introduction

+

This style guide is split into two sections, one for the project structure and another which is a list of +dos and don'ts for Python programs.

+

1. Project structure

+

1.1 Domains

+

A domain is a piece of software that provides a distinct business value for your application. What this styleguide +calls a domain is roughly an extension of what Django would call an app. Therefore a business domain should have at +least one distinct software domain mirroring it.

+

This guide tries to keep the key benefits of Django's app pattern - namely Django's models to represent tables in a +datastore, but with an emphasis on skinny models.

+

1.1.1 Domain rules

+
    +
  1. You should split a domain if it becomes too big to work on.
  2. +
+
+

A domain should allow between 4-6 developers (3 pairs) to comfortably work on it. If you find your developers being +blocked by each other then it is time to consider splitting the domain or checking whether the software has not +diverged too far from the styleguide.

+
+
    +
  1. You should adhere to the styleguide patterns in this document in order to maintain strong bounded contexts between +your domains.
  2. +
+
+

This applies even in situations where you extract one domain into two domains to increase velocity, but they still +have to maintain a dependency between one another. We have found that if you relax the bounded context between domains, +the boundary will erode and you will lose the ability to work on them independent of each other.

+
+

1.2 Structure

+
    +
  • views.py: Public functions and access points
  • +
  • serializers.py: Public functions and access points presentation logic
  • +
  • interfaces.py: Integrations with other domains or external services
  • +
  • models.py: Object models and storage, simple information logic
  • +
  • services.py: coordination and transactional logic
  • +
  • urls.py: route definition
  • +
  • apps.py: Django application configuration
  • +
  • migrations/*: database migrations
  • +
  • management/*: Django custom commands definition
  • +
  • routes/*: Django custom databse routes
  • +
  • tests/*: tests
  • +
+

You can mask one of the required files as a directory for better file organisation. For example, you might want to split views.py file into this structure:

+
views/
+  __init__.py
+  brand.py
+  legacy_brand.py
+
+

1.2.1 Migrations

+
+

You might notice that Django creates migrations even when nothing has changed in the model definition, these migrations should not be added to the codebase.

+
+

Besides the initial migration, all the remaining migrations must include a name that represent the change while including the date and time of it's creation, in the following format <migration_number>_<migration_description>_<date>_<time>.py which as an example can be represented as 0004_added_status_to_sample_table_20210404_12_30.py.

+

1.2.2 Management

+

Following the Django documentation this folder holds the definition for the custom Django commands for a given Django app/domain. For instance, instead of a defining a traditional custom python script, a custom Django command should be added to take advantage of the already existing logic.

+

1.3 Absolute and Relative Imports

+

The ruling for absolute or relative imports is as follows:

+
    +
  • When importing files within a domain, you must use relative imports.
  • +
  • When importing other domains in the same project, you must use absolute imports.
  • +
  • When importing domains in tests, you should use absolute imports.
  • +
  • When importing third-party packages you should use absolute imports.
  • +
+
+

TL;DR - relative imports inside a domain, absolute for everything else!

+
+

With this ruling domains are easy to package and move around. When it comes time to move it into it's own project; tidying up imports will be one less thing you have to do.

+

2. Python Language Rules

+

2.1 Lint

+

Run pylint over your code using this pylintrc.

+

2.1.1 Definition

+

pylint +is a tool for finding bugs and style problems in Python source code. It finds +problems that are typically caught by a compiler for less dynamic languages like +C and C++. Because of the dynamic nature of Python, some +warnings may be incorrect; however, spurious warnings should be fairly +infrequent.

+

2.1.2 Pros

+

Catches easy-to-miss errors like typos, using-vars-before-assignment, etc.

+

2.1.3 Cons

+

pylint +isn't perfect. To take advantage of it, sometimes we'll need to write around it, +suppress its warnings or fix it.

+

2.1.4 Decision

+

Make sure you run +pylint +on your code.

+

Suppress warnings if they are inappropriate so that other issues are not hidden. +To suppress warnings, you can set a line-level comment:

+
dict = 'something awful'  # Bad Idea... pylint: disable=redefined-builtin
+
+

pylint +warnings are each identified by symbolic name (empty-docstring) +Google-specific warnings start with g-.

+

If the reason for the suppression is not clear from the symbolic name, add an +explanation.

+

Suppressing in this way has the advantage that we can easily search for +suppressions and revisit them.

+

You can get a list of +pylint +warnings by doing:

+
pylint --list-msgs
+
+

To get more information on a particular message, use:

+
pylint --help-msg=C6409
+
+

Prefer pylint: disable to the deprecated older form pylint: disable-msg.

+

Unused argument warnings can be suppressed by deleting the variables at the +beginning of the function. Always include a comment explaining why you are +deleting it. "Unused." is sufficient. For example:

+
def viking_cafe_order(spam, beans, eggs=None):
+    del beans, eggs  # Unused by vikings.
+    return spam + spam + spam
+
+

Other common forms of suppressing this warning include using '_' as the +identifier for the unused argument or prefixing the argument name with +'unused_', or assigning them to '_'. These forms are allowed but no longer +encouraged. These break callers that pass arguments by name and do not enforce +that the arguments are actually unused.

+

2.2 Imports

+

Use import statements for packages and modules only, not for individual +classes or functions. Note that there is an explicit exemption for imports from +the typing module.

+

2.2.1 Definition

+

Reusability mechanism for sharing code from one module to another.

+

2.2.2 Pros

+

The namespace management convention is simple. The source of each identifier is +indicated in a consistent way; x.Obj says that object Obj is defined in +module x.

+

2.2.3 Cons

+

Module names can still collide. Some module names are inconveniently long.

+

2.2.4 Decision

+
    +
  • Use import x for importing packages and modules.
  • +
  • Use from x import y where x is the package prefix and y is the module + name with no prefix.
  • +
  • Use from x import y as z if two modules named y are to be imported or if + y is an inconveniently long name.
  • +
  • Use import y as z only when z is a standard abbreviation (e.g., np for + numpy).
  • +
+

For example the module sound.effects.echo may be imported as follows:

+
from sound.effects import echo
+...
+echo.EchoFilter(input, output, delay=0.7, atten=4)
+
+

Do not use relative names in imports. Even if the module is in the same package, +use the full package name. This helps prevent unintentionally importing a +package twice.

+

Imports from the typing module and the +six.moves module +are exempt from this rule.

+

2.3 Packages

+

Import each module using the full pathname location of the module.

+

2.3.1 Pros

+

Avoids conflicts in module names or incorrect imports due to the module search +path not being what the author expected. Makes it easier to find modules.

+

2.3.2 Cons

+

Makes it harder to deploy code because you have to replicate the package +hierarchy. Not really a problem with modern deployment mechanisms.

+

2.3.3 Decision

+

All new code should import each module by its full package name.

+

Imports should be as follows:

+

Yes:

+
# Reference absl.flags in code with the complete name (verbose).
+import absl.flags
+from doctor.who import jodie
+
+FLAGS = absl.flags.FLAGS
+
+
# Reference flags in code with just the module name (common).
+from absl import flags
+from doctor.who import jodie
+
+FLAGS = flags.FLAGS
+
+

No: (assume this file lives in doctor/who/ where jodie.py also exists)

+
# Unclear what module the author wanted and what will be imported.  The actual
+# import behavior depends on external factors controlling sys.path.
+# Which possible jodie module did the author intend to import?
+import jodie
+
+

The directory the main binary is located in should not be assumed to be in +sys.path despite that happening in some environments. This being the case, +code should assume that import jodie refers to a third party or top level +package named jodie, not a local jodie.py.

+

2.4 Exceptions

+

Exceptions are allowed but must be used carefully.

+

2.4.1 Definition

+

Exceptions are a means of breaking out of normal control flow to handle errors +or other exceptional conditions.

+

2.4.2 Pros

+

The control flow of normal operation code is not cluttered by error-handling +code. It also allows the control flow to skip multiple frames when a certain +condition occurs, e.g., returning from N nested functions in one step instead of +having to plumb error codes through.

+

2.4.3 Cons

+

May cause the control flow to be confusing. Easy to miss error cases when making +library calls.

+

2.4.4 Decision

+

Exceptions must follow certain conditions:

+
    +
  • +

    Make use of built-in exception classes when it makes sense. For example, + raise a ValueError to indicate a programming mistake like a violated + precondition (such as if you were passed a negative number but required a + positive one). Do not use assert statements for validating argument values + of a public API. assert is used to ensure internal correctness, not to + enforce correct usage nor to indicate that some unexpected event occurred. + If an exception is desired in the latter cases, use a raise statement. For + example:

    +
    Yes:
    +  def connect_to_next_port(self, minimum):
    +    """Connects to the next available port.
    +
    +    Args:
    +      minimum: A port value greater or equal to 1024.
    +
    +    Returns:
    +      The new minimum port.
    +
    +    Raises:
    +      ConnectionError: If no available port is found.
    +    """
    +    if minimum < 1024:
    +      # Note that this raising of ValueError is not mentioned in the doc
    +      # string's "Raises:" section because it is not appropriate to
    +      # guarantee this specific behavioral reaction to API misuse.
    +      raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
    +    port = self._find_next_open_port(minimum)
    +    if not port:
    +      raise ConnectionError(
    +          f'Could not connect to service on port {minimum} or higher.')
    +    assert port >= minimum, (
    +        f'Unexpected port {port} when minimum was {minimum}.')
    +    return port
    +
    +
    No:
    +  def connect_to_next_port(self, minimum):
    +    """Connects to the next available port.
    +
    +    Args:
    +      minimum: A port value greater or equal to 1024.
    +
    +    Returns:
    +      The new minimum port.
    +    """
    +    assert minimum >= 1024, 'Minimum port must be at least 1024.'
    +    port = self._find_next_open_port(minimum)
    +    assert port is not None
    +    return port
    +
    +
  • +
  • +

    Libraries or packages may define their own exceptions. When doing so they + must inherit from an existing exception class. Exception names should end in + Error and should not introduce stutter (foo.FooError).

    +
  • +
  • +

    Never use catch-all except: statements, or catch Exception or + StandardError, unless you are

    +
  • +
  • +

    re-raising the exception, or

    +
  • +
  • +

    creating an isolation point in the program where exceptions are not + propagated but are recorded and suppressed instead, such as protecting a + thread from crashing by guarding its outermost block.

    +

    Python is very tolerant in this regard and except: will really catch +everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, +unittest failures and all kinds of other exceptions that you simply don't +want to catch.

    +
  • +
  • +

    Minimize the amount of code in a try/except block. The larger the body + of the try, the more likely that an exception will be raised by a line of + code that you didn't expect to raise an exception. In those cases, the + try/except block hides a real error.

    +
  • +
  • +

    Use the finally clause to execute code whether or not an exception is + raised in the try block. This is often useful for cleanup, i.e., closing a + file.

    +
  • +
+

2.5 Global variables

+

Avoid global variables.

+

2.5.1 Definition

+

Variables that are declared at the module level or as class attributes.

+

2.5.2 Pros

+

Occasionally useful.

+

2.5.3 Cons

+

Has the potential to change module behavior during the import, because +assignments to global variables are done when the module is first imported.

+

2.5.4 Decision

+

Avoid global variables.

+

While they are technically variables, module-level constants are permitted and +encouraged. For example: MAX_HOLY_HANDGRENADE_COUNT = 3. Constants must be +named using all caps with underscores. See Naming below.

+

If needed, globals should be declared at the module level and made internal to +the module by prepending an _ to the name. External access must be done +through public module-level functions. See Naming below.

+

2.6 Nested/Local/Inner Classes and Functions

+

Nested local functions or classes are fine when used to close over a local +variable. Inner classes are fine.

+

2.6.1 Definition

+

A class can be defined inside of a method, function, or class. A function can be +defined inside a method or function. Nested functions have read-only access to +variables defined in enclosing scopes.

+

2.6.2 Pros

+

Allows definition of utility classes and functions that are only used inside of +a very limited scope. Very +ADT-y. +Commonly used for implementing decorators.

+

2.6.3 Cons

+

Nested functions and classes cannot be directly tested. Nesting can make the +outer function longer and less readable.

+

2.6.4 Decision

+

They are fine with some caveats. Avoid nested functions or classes except when +closing over a local value. Do not nest a function just to hide it from users of +a module. Instead, prefix its name with an _ at the module level so that it can +still be accessed by tests.

+

2.7 Comprehensions & Generator Expressions

+

Okay to use for simple cases.

+

2.7.1 Definition

+

List, Dict, and Set comprehensions as well as generator expressions provide a +concise and efficient way to create container types and iterators without +resorting to the use of traditional loops, map(), filter(), or lambda.

+

2.7.2 Pros

+

Simple comprehensions can be clearer and simpler than other dict, list, or set +creation techniques. Generator expressions can be very efficient, since they +avoid the creation of a list entirely.

+

2.7.3 Cons

+

Complicated comprehensions or generator expressions can be hard to read.

+

2.7.4 Decision

+

Okay to use for simple cases. Each portion must fit on one line: mapping +expression, for clause, filter expression. Multiple for clauses or filter +expressions are not permitted. Use loops instead when things get more +complicated.

+
Yes:
+  result = [mapping_expr for value in iterable if filter_expr]
+
+  result = [{'key': value} for value in iterable
+            if a_long_filter_expression(value)]
+
+  result = [complicated_transform(x)
+            for x in iterable if predicate(x)]
+
+  descriptive_name = [
+      transform({'key': key, 'value': value}, color='black')
+      for key, value in generate_iterable(some_input)
+      if complicated_condition_is_met(key, value)
+  ]
+
+  result = []
+  for x in range(10):
+      for y in range(5):
+          if x * y > 10:
+              result.append((x, y))
+
+  return {x: complicated_transform(x)
+          for x in long_generator_function(parameter)
+          if x is not None}
+
+  squares_generator = (x**2 for x in range(10))
+
+  unique_names = {user.name for user in users if user is not None}
+
+  eat(jelly_bean for jelly_bean in jelly_beans
+      if jelly_bean.color == 'black')
+
+
No:
+  result = [complicated_transform(
+                x, some_argument=x+1)
+            for x in iterable if predicate(x)]
+
+  result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
+
+  return ((x, y, z)
+          for x in range(5)
+          for y in range(5)
+          if x != y
+          for z in range(5)
+          if y != z)
+
+

2.8 Default Iterators and Operators

+

Use default iterators and operators for types that support them, like lists, +dictionaries, and files.

+

2.8.1 Definition

+

Container types, like dictionaries and lists, define default iterators and +membership test operators ("in" and "not in").

+

2.8.2 Pros

+

The default iterators and operators are simple and efficient. They express the +operation directly, without extra method calls. A function that uses default +operators is generic. It can be used with any type that supports the operation.

+

2.8.3 Cons

+

You can't tell the type of objects by reading the method names (e.g. has_key() +means a dictionary). This is also an advantage.

+

2.8.4 Decision

+

Use default iterators and operators for types that support them, like lists, +dictionaries, and files. The built-in types define iterator methods, too. Prefer +these methods to methods that return lists, except that you should not mutate a +container while iterating over it.

+
Yes:  for key in adict: ...
+      if key not in adict: ...
+      if obj in alist: ...
+      for line in afile: ...
+      for k, v in adict.items(): ...
+      for k, v in six.iteritems(adict): ...
+
+
No:   for key in adict.keys(): ...
+      if not adict.has_key(key): ...
+      for line in afile.readlines(): ...
+      for k, v in dict.iteritems(): ...
+
+

2.9 Generators

+

Use generators as needed.

+

2.9 Definition

+

A generator function returns an iterator that yields a value each time it +executes a yield statement. After it yields a value, the runtime state of the +generator function is suspended until the next value is needed.

+

2.9.2 Pros

+

Simpler code, because the state of local variables and control flow are +preserved for each call. A generator uses less memory than a function that +creates an entire list of values at once.

+

2.9.3 Cons

+

None.

+

2.9.4 Decision

+

Fine. Use "Yields:" rather than "Returns:" in the docstring for generator +functions.

+

2.10 Lambda Functions

+

Okay for one-liners. Prefer generator expressions over map() or filter() +with a lambda.

+

2.10.1 Definition

+

Lambdas define anonymous functions in an expression, as opposed to a statement.

+

2.10.2 Pros

+

Convenient.

+

2.10.3 Cons

+

Harder to read and debug than local functions. The lack of names means stack +traces are more difficult to understand. Expressiveness is limited because the +function may only contain an expression.

+

2.10.4 Decision

+

Okay to use them for one-liners. If the code inside the lambda function is +longer than 60-80 chars, it's probably better to define it as a regular +nested function.

+

For common operations like multiplication, use the functions from the operator +module instead of lambda functions. For example, prefer operator.mul to +lambda x, y: x * y.

+

2.11 Conditional Expressions

+

Okay for simple cases.

+

2.11.1 Definition

+

Conditional expressions (sometimes called a “ternary operator”) are mechanisms +that provide a shorter syntax for if statements. For example: x = 1 if cond +else 2.

+

2.11.2 Pros

+

Shorter and more convenient than an if statement.

+

2.11.3 Cons

+

May be harder to read than an if statement. The condition may be difficult to +locate if the expression is long.

+

2.11.4 Decision

+

Okay to use for simple cases. Each portion must fit on one line: +true-expression, if-expression, else-expression. Use a complete if statement +when things get more complicated.

+
Yes:
+    one_line = 'yes' if predicate(value) else 'no'
+    slightly_split = ('yes' if predicate(value)
+                      else 'no, nein, nyet')
+    the_longest_ternary_style_that_can_be_done = (
+        'yes, true, affirmative, confirmed, correct'
+        if predicate(value)
+        else 'no, false, negative, nay')
+
+
No:
+    bad_line_breaking = ('yes' if predicate(value) else
+                         'no')
+    portion_too_long = ('yes'
+                        if some_long_module.some_long_predicate_function(
+                            really_long_variable_name)
+                        else 'no, false, negative, nay')
+
+

2.12 Default Argument Values

+

Okay in most cases.

+

2.12.1 Definition

+

You can specify values for variables at the end of a function's parameter list, +e.g., def foo(a, b=0):. If foo is called with only one argument, b is set +to 0. If it is called with two arguments, b has the value of the second +argument.

+

2.12.2 Pros

+

Often you have a function that uses lots of default values, but on rare +occasions you want to override the defaults. Default argument values provide an +easy way to do this, without having to define lots of functions for the rare +exceptions. As Python does not support overloaded methods/functions, default +arguments are an easy way of "faking" the overloading behavior.

+

2.12.3 Cons

+

Default arguments are evaluated once at module load time. This may cause +problems if the argument is a mutable object such as a list or a dictionary. If +the function modifies the object (e.g., by appending an item to a list), the +default value is modified.

+

2.12.4 Decision

+

Okay to use with the following caveat:

+

Do not use mutable objects as default values in the function or method +definition.

+
Yes: def foo(a, b=None):
+         if b is None:
+             b = []
+Yes: def foo(a, b: Optional[Sequence] = None):
+         if b is None:
+             b = []
+Yes: def foo(a, b: Sequence = ()):  # Empty tuple OK since tuples are immutable
+         ...
+
+
No:  def foo(a, b=[]):
+         ...
+No:  def foo(a, b=time.time()):  # The time the module was loaded???
+         ...
+No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...
+         ...
+No:  def foo(a, b: Mapping = {}):  # Could still get passed to unchecked code
+         ...
+
+

2.13 Properties

+

Use properties for accessing or setting data where you would normally have used +simple, lightweight accessor or setter methods.

+

2.13.1 Definition

+

A way to wrap method calls for getting and setting an attribute as a standard +attribute access when the computation is lightweight.

+

2.13.2 Pros

+

Readability is increased by eliminating explicit get and set method calls for +simple attribute access. Allows calculations to be lazy. Considered the Pythonic +way to maintain the interface of a class. In terms of performance, allowing +properties bypasses needing trivial accessor methods when a direct variable +access is reasonable. This also allows accessor methods to be added in the +future without breaking the interface.

+

2.13.3 Cons

+

Can hide side-effects much like operator overloading. Can be confusing for +subclasses.

+

2.13.4 Decision

+

Use properties in new code to access or set data where you would normally have +used lightweight accessor or setter methods. Properties should be created with +the @property decorator.

+

Inheritance with properties can be non-obvious if the property itself is not +overridden. Thus one must make sure that accessor methods are called indirectly +to ensure methods overridden in subclasses are called by the property (using the +template method design pattern).

+
Yes: import math
+
+     class Square:
+         """A square with two properties: a writable area and a read-only perimeter.
+
+         To use:
+         >>> sq = Square(3)
+         >>> sq.area
+         9
+         >>> sq.perimeter
+         12
+         >>> sq.area = 16
+         >>> sq.side
+         4
+         >>> sq.perimeter
+         16
+         """
+
+         def __init__(self, side):
+             self.side = side
+
+         @property
+         def area(self):
+             """Area of the square."""
+             return self._get_area()
+
+         @area.setter
+         def area(self, area):
+             return self._set_area(area)
+
+         def _get_area(self):
+             """Indirect accessor to calculate the 'area' property."""
+             return self.side ** 2
+
+         def _set_area(self, area):
+             """Indirect setter to set the 'area' property."""
+             self.side = math.sqrt(area)
+
+         @property
+         def perimeter(self):
+             return self.side * 4
+
+

2.14 True/False Evaluations

+

Use the "implicit" false if at all possible.

+

2.14.1 Definition

+

Python evaluates certain values as False when in a boolean context. A quick +"rule of thumb" is that all "empty" values are considered false, so 0, None, +[], {}, '' all evaluate as false in a boolean context.

+

2.14.2 Pros

+

Conditions using Python booleans are easier to read and less error-prone. In +most cases, they're also faster.

+

2.14.3 Cons

+

May look strange to C/C++ developers.

+

2.14.4 Decision

+

Use the "implicit" false if possible, e.g., if foo: rather than if foo != +[]:. There are a few caveats that you should keep in mind though:

+
    +
  • +

    Always use if foo is None: (or is not None) to check for a None value. + E.g., when testing whether a variable or argument that defaults to None + was set to some other value. The other value might be a value that's false + in a boolean context!

    +
  • +
  • +

    Never compare a boolean variable to False using ==. Use if not x: + instead. If you need to distinguish False from None then chain the + expressions, such as if not x and x is not None:.

    +
  • +
  • +

    For sequences (strings, lists, tuples), use the fact that empty sequences + are false, so if seq: and if not seq: are preferable to if len(seq): + and if not len(seq): respectively.

    +
  • +
  • +

    When handling integers, implicit false may involve more risk than benefit + (i.e., accidentally handling None as 0). You may compare a value which is + known to be an integer (and is not the result of len()) against the + integer 0.

    +
    Yes: if not users:
    +         print('no users')
    +
    +     if foo == 0:
    +         self.handle_zero()
    +
    +     if i % 10 == 0:
    +         self.handle_multiple_of_ten()
    +
    +     def f(x=None):
    +         if x is None:
    +             x = []
    +
    +
    No:  if len(users) == 0:
    +         print('no users')
    +
    +     if foo is not None and not foo:
    +         self.handle_zero()
    +
    +     if not i % 10:
    +         self.handle_multiple_of_ten()
    +
    +     def f(x=None):
    +         x = x or []
    +
    +
  • +
  • +

    Note that '0' (i.e., 0 as string) evaluates to true.

    +
  • +
+

2.16 Lexical Scoping

+

Okay to use.

+

2.16.1 Definition

+

A nested Python function can refer to variables defined in enclosing functions, +but cannot assign to them. Variable bindings are resolved using lexical scoping, +that is, based on the static program text. Any assignment to a name in a block +will cause Python to treat all references to that name as a local variable, even +if the use precedes the assignment. If a global declaration occurs, the name is +treated as a global variable.

+

An example of the use of this feature is:

+
def get_adder(summand1):
+    """Returns a function that adds numbers to a given number."""
+    def adder(summand2):
+        return summand1 + summand2
+
+    return adder
+
+

2.16.2 Pros

+

Often results in clearer, more elegant code. Especially comforting to +experienced Lisp and Scheme (and Haskell and ML and ...) programmers.

+

2.16.3 Cons

+

Can lead to confusing bugs. Such as this example based on +PEP-0227:

+
i = 4
+def foo(x):
+    def bar():
+        print(i, end='')
+    # ...
+    # A bunch of code here
+    # ...
+    for i in x:  # Ah, i *is* local to foo, so this is what bar sees
+        print(i, end='')
+    bar()
+
+

So foo([1, 2, 3]) will print 1 2 3 3, +not 1 2 3 4.

+

2.16.4 Decision

+

Okay to use.

+

2.17 Function and Method Decorators

+

Use decorators judiciously when there is a clear advantage. Avoid staticmethod +and limit use of classmethod.

+

2.17.1 Definition

+

Decorators for Functions and Methods +(a.k.a "the @ notation"). One common decorator is @property, used for +converting ordinary methods into dynamically computed attributes. However, the +decorator syntax allows for user-defined decorators as well. Specifically, for +some function my_decorator, this:

+
class C:
+    @my_decorator
+    def method(self):
+        # method body ...
+
+

is equivalent to:

+
class C:
+    def method(self):
+        # method body ...
+    method = my_decorator(method)
+
+

2.17.2 Pros

+

Elegantly specifies some transformation on a method; the transformation might +eliminate some repetitive code, enforce invariants, etc.

+

2.17.3 Cons

+

Decorators can perform arbitrary operations on a function's arguments or return +values, resulting in surprising implicit behavior. Additionally, decorators +execute at import time. Failures in decorator code are pretty much impossible to +recover from.

+

2.17.4 Decision

+

Use decorators judiciously when there is a clear advantage. Decorators should +follow the same import and naming guidelines as functions. Decorator pydoc +should clearly state that the function is a decorator. Write unit tests for +decorators.

+

Avoid external dependencies in the decorator itself (e.g. don't rely on files, +sockets, database connections, etc.), since they might not be available when the +decorator runs (at import time, perhaps from pydoc or other tools). A +decorator that is called with valid parameters should (as much as possible) be +guaranteed to succeed in all cases.

+

Decorators are a special case of "top level code" - see main for +more discussion.

+

Never use staticmethod unless forced to in order to integrate with an API +defined in an existing library. Write a module level function instead.

+

Use classmethod only when writing a named constructor or a class-specific +routine that modifies necessary global state such as a process-wide cache.

+

2.18 Threading

+

Do not rely on the atomicity of built-in types.

+

While Python's built-in data types such as dictionaries appear to have atomic +operations, there are corner cases where they aren't atomic (e.g. if __hash__ +or __eq__ are implemented as Python methods) and their atomicity should not be +relied upon. Neither should you rely on atomic variable assignment (since this +in turn depends on dictionaries).

+

Use the Queue module's Queue data type as the preferred way to communicate +data between threads. Otherwise, use the threading module and its locking +primitives. Prefer condition variables and threading.Condition instead of +using lower-level locks.

+

2.19 Power Features

+

Avoid these features.

+

2.19.1 Definition

+

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 (e.g. some uses of +getattr()), modification of system internals, etc.

+

2.19.2 Pros

+

These are powerful language features. They can make your code more compact.

+

2.19.3 Cons

+

It's very tempting to use these "cool" features when they're not absolutely +necessary. It's harder to read, understand, and debug code that's using unusual +features underneath. It doesn't seem that way at first (to the original author), +but when revisiting the code, it tends to be more difficult than code that is +longer but is straightforward.

+

2.19.4 Decision

+

Avoid these features in your code.

+

Standard library modules and classes that internally use these features are okay +to use (for example, abc.ABCMeta, dataclasses, and enum).

+

2.20 Modern Python: Python 3 and from __future__ imports

+

Python 3 is here! While not every project is ready to use it yet, +all code should be written to be 3 compatible (and tested under 3 when +possible).

+

2.20.1 Definition

+

Python 3 is a significant change in the Python language. While existing code is +often written with 2.7 in mind, there are some simple things to do to make code +more explicit about its intentions and thus better prepared for use under Python +3 without modification.

+

2.20.2 Pros

+

Code written with Python 3 in mind is more explicit and easier to get running +under Python 3 once all of the dependencies of your project are ready.

+

2.20.3 Cons

+

Some people find the additional boilerplate to be ugly. It's unusual to add +imports to a module that doesn't actually require the features added by the +import.

+

2.20.4 Decision

+
from __future__ imports
+

Use of from __future__ import statements is encouraged. All new code should +contain the following and existing code should be updated to be compatible when +possible:

+
from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+

For more information on these imports, see +absolute imports, +/ division behavior, and +the print function.

+

Please don't omit or remove these imports, even if they're not currently used in +the module, unless the code is Python 3 only. It is better to always have the +future imports in all files so that they are not forgotten during later edits +when someone starts using such a feature.

+

There are other from __future__ import statements. Use them as you see fit. We +do not include unicode_literals in our recommendations as it is not a clear +win due to implicit default codec conversion consequences it introduces in many +places within Python 2.7. Most code is better off with explicit use of b'' and +u'' bytes and unicode string literals as necessary.

+
The six, future, and past libraries
+

When your project needs to actively support use under both Python 2 and 3, use +the six, +future, and +past libraries as you see fit. They exist to +make your code cleaner and life easier.

+

2.21 Type Annotated Code

+

You can annotate Python 3 code with type hints according to +PEP-484, and type-check the code at +build time with a type checking tool like pytype.

+

Type annotations can be in the source or in a +stub pyi file. Whenever +possible, annotations should be in the source. Use pyi files for third-party or +extension modules.

+

2.21.1 Definition

+

Type annotations (or "type hints") are for function or method arguments and +return values:

+
def func(a: int) -> List[int]:
+
+

You can also declare the type of a variable using similar +PEP-526 syntax:

+
a: SomeType = some_func()
+
+

Or by using a type comment in code that must support legacy Python versions.

+
a = some_func()  # type: SomeType
+
+

2.21.2 Pros

+

Type annotations improve the readability and maintainability of your code. The +type checker will convert many runtime errors to build-time errors, and reduce +your ability to use Power Features.

+

2.21.3 Cons

+

You will have to keep the type declarations up to date. +You might see type errors that you think are +valid code. Use of a +type checker +may reduce your ability to use Power Features.

+

2.21.4 Decision

+

You are strongly encouraged to enable Python type analysis when updating code. +When adding or modifying public APIs, include type annotations and enable +checking via pytype in the build system. As static analysis is relatively new to +Python, we acknowledge that undesired side-effects (such as +wrongly +inferred types) may prevent adoption by some projects. In those situations, +authors are encouraged to add a comment with a TODO or link to a bug describing +the issue(s) currently preventing type annotation adoption in the BUILD file or +in the code itself as appropriate.

+

3 Python Style Rules

+

3.1 Semicolons

+

Do not terminate your lines with semicolons, and do not use semicolons to put +two statements on the same line.

+

3.2 Line length

+

Maximum line length is 120 characters.

+

Explicit exceptions to the 120 character limit:

+
    +
  • Long import statements.
  • +
  • URLs, pathnames, or long flags in comments.
  • +
  • Long string module level constants not containing whitespace that would be + inconvenient to split across lines such as URLs or pathnames.
  • +
  • Pylint disable comments. (e.g.: # pylint: disable=invalid-name)
  • +
+

Do not use backslash line continuation except for with statements requiring +three or more context managers.

+

Make use of Python's +implicit line joining inside parentheses, brackets and braces. +If necessary, you can add an extra pair of parentheses around an expression.

+
Yes: foo_bar(self, width, height, color='black', design=None, x='foo',
+             emphasis=None, highlight=0)
+
+     if (width == 0 and height == 0 and
+         color == 'red' and emphasis == 'strong'):
+
+

When a literal string won't fit on a single line, use parentheses for implicit +line joining.

+
x = ('This will build a very long long '
+     'long long long long long long string')
+
+

Within comments, put long URLs on their own line if necessary.

+
Yes:  # See details at
+      # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
+
+
No:  # See details at
+     # http://www.example.com/us/developer/documentation/api/content/\
+     # v2.0/csv_file_name_extension_full_specification.html
+
+

It is permissible to use backslash continuation when defining a with statement +whose expressions span three or more lines. For two lines of expressions, use a +nested with statement:

+
Yes:  with very_long_first_expression_function() as spam, \
+           very_long_second_expression_function() as beans, \
+           third_thing() as eggs:
+          place_order(eggs, beans, spam, beans)
+
+
No:  with VeryLongFirstExpressionFunction() as spam, \
+          VeryLongSecondExpressionFunction() as beans:
+       PlaceOrder(eggs, beans, spam, beans)
+
+
Yes:  with very_long_first_expression_function() as spam:
+          with very_long_second_expression_function() as beans:
+              place_order(beans, spam)
+
+

Make note of the indentation of the elements in the line continuation examples +above; see the indentation section for explanation.

+

In all other cases where a line exceeds 80 characters, and the +yapf +auto-formatter does not help bring the line below the limit, the line is allowed +to exceed this maximum.

+

3.3 Parentheses

+

Use parentheses sparingly.

+

It is fine, though not required, to use parentheses around tuples. Do not use +them in return statements or conditional statements unless using parentheses for +implied line continuation or to indicate a tuple.

+
Yes: if foo:
+         bar()
+     while x:
+         x = bar()
+     if x and y:
+         bar()
+     if not x:
+         bar()
+     # For a 1 item tuple the ()s are more visually obvious than the comma.
+     onesie = (foo,)
+     return foo
+     return spam, beans
+     return (spam, beans)
+     for (x, y) in dict.items(): ...
+
+
No:  if (x):
+         bar()
+     if not(x):
+         bar()
+     return (foo)
+
+

3.4 Indentation

+

Indent your code blocks with 4 spaces.

+

Never use tabs or mix tabs and spaces. In cases of implied line continuation, +you should align wrapped elements either vertically, as per the examples in the +line length section; or using a hanging indent of 4 spaces, +in which case there should be nothing after the open parenthesis or bracket on +the first line.

+
Yes:   # Aligned with opening delimiter
+       foo = long_function_name(var_one, var_two,
+                                var_three, var_four)
+       meal = (spam,
+               beans)
+
+       # Aligned with opening delimiter in a dictionary
+       foo = {
+           long_dictionary_key: value1 +
+                                value2,
+           ...
+       }
+
+       # 4-space hanging indent; nothing on first line
+       foo = long_function_name(
+           var_one, var_two, var_three,
+           var_four)
+       meal = (
+           spam,
+           beans)
+
+       # 4-space hanging indent in a dictionary
+       foo = {
+           long_dictionary_key:
+               long_dictionary_value,
+           ...
+       }
+
+
No:    # Stuff on first line forbidden
+       foo = long_function_name(var_one, var_two,
+           var_three, var_four)
+       meal = (spam,
+           beans)
+
+       # 2-space hanging indent forbidden
+       foo = long_function_name(
+         var_one, var_two, var_three,
+         var_four)
+
+       # No hanging indent in a dictionary
+       foo = {
+           long_dictionary_key:
+           long_dictionary_value,
+           ...
+       }
+
+

3.4.1 Trailing commas in sequences of items?

+

Trailing commas in sequences of items are recommended only when the closing +container token ], ), or } does not appear on the same line as the final +element. The presence of a trailing comma is also used as a hint to our Python +code auto-formatter YAPF to direct it to auto-format the container +of items to one item per line when the , after the final element is present.

+
Yes:   golomb3 = [0, 1, 3]
+Yes:   golomb4 = [
+           0,
+           1,
+           4,
+           6,
+       ]
+
+
No:    golomb4 = [
+           0,
+           1,
+           4,
+           6
+       ]
+
+

3.5 Blank Lines

+

Two blank lines between top-level definitions, be they function or class +definitions. One blank line between method definitions and between the class +line and the first method. No blank line following a def line. Use single +blank lines as you judge appropriate within functions or methods.

+

3.6 Whitespace

+

Follow standard typographic rules for the use of spaces around punctuation.

+

No whitespace inside parentheses, brackets or braces.

+
Yes: spam(ham[1], {eggs: 2}, [])
+
+
No:  spam( ham[ 1 ], { eggs: 2 }, [ ] )
+
+

No whitespace before a comma, semicolon, or colon. Do use whitespace after a +comma, semicolon, or colon, except at the end of the line.

+
Yes: if x == 4:
+         print(x, y)
+     x, y = y, x
+
+
No:  if x == 4 :
+         print(x , y)
+     x , y = y , x
+
+

No whitespace before the open paren/bracket that starts an argument list, +indexing or slicing.

+
Yes: spam(1)
+
+
No:  spam (1)
+
+
Yes: dict['key'] = list[index]
+
+
No:  dict ['key'] = list [index]
+
+

No trailing whitespace.

+

Surround binary operators with a single space on either side for assignment +(=), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), and +Booleans (and, or, not). Use your better judgment for the insertion of spaces +around arithmetic operators (+, -, *, /, //, %, **, @).

+
Yes: x == 1
+
+
No:  x<1
+
+

Never use spaces around = when passing keyword arguments or defining a default +parameter value, with one exception: +when a type annotation is present, do use spaces +around the = for the default parameter value.

+
Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag)
+Yes: def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
+
+
No:  def complex(real, imag = 0.0): return Magic(r = real, i = imag)
+No:  def complex(real, imag: float=0.0): return Magic(r = real, i = imag)
+
+

Don't use spaces to vertically align tokens on consecutive lines, since it +becomes a maintenance burden (applies to :, #, =, etc.):

+
Yes:
+  foo = 1000  # comment
+  long_name = 2  # comment that should not be aligned
+
+  dictionary = {
+      'foo': 1,
+      'long_name': 2,
+  }
+
+
No:
+  foo       = 1000  # comment
+  long_name = 2     # comment that should not be aligned
+
+  dictionary = {
+      'foo'      : 1,
+      'long_name': 2,
+  }
+
+

3.7 Shebang Line

+

Most .py files do not need to start with a #! line. Start the main file of a +program with +#!/usr/bin/env python3 (to support virtualenvs) or #!/usr/bin/python3 per +PEP-394.

+

This line is used by the kernel to find the Python interpreter, but is ignored by Python when importing modules. It is only necessary on a file intended to be executed directly.

+

3.8 Comments and Docstrings

+

Be sure to use the right style for module, function, method docstrings and +inline comments.

+

3.8.1 Docstrings

+

Python uses docstrings to document code. A docstring is a string that is the +first statement in a package, module, class or function. These strings can be +extracted automatically through the __doc__ member of the object and are used +by pydoc. +(Try running pydoc on your module to see how it looks.) Always use the three +double-quote """ format for docstrings (per +PEP 257). +A docstring should be organized as a summary line (one physical line not +exceeding 80 characters) terminated by a period, question mark, or exclamation +point. When writing more (encouraged), this must be followed by a blank line, +followed by the rest of the docstring starting at the same cursor position as +the first quote of the first line. There are more formatting guidelines for +docstrings below.

+

3.8.2 Modules

+

Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL)

+

Files should start with a docstring describing the contents and usage of the +module.

+
"""A one line summary of the module or program, terminated by a period.
+
+Leave one blank line.  The rest of this docstring should contain an
+overall description of the module or program.  Optionally, it may also
+contain a brief description of exported classes and functions and/or usage
+examples.
+
+  Typical usage example:
+
+  foo = ClassFoo()
+  bar = foo.FunctionBar()
+"""
+
+

3.8.3 Functions and Methods

+

In this section, "function" means a method, function, or generator.

+

A function must have a docstring, unless it meets all of the following criteria:

+
    +
  • not externally visible
  • +
  • very short
  • +
  • obvious
  • +
+

A docstring should give enough information to write a call to the function +without reading the function's code. The docstring should be descriptive-style +("""Fetches rows from a Bigtable.""") rather than imperative-style ("""Fetch +rows from a Bigtable."""), except for @property data descriptors, which +should use the same style as attributes. A docstring +should describe the function's calling syntax and its semantics, not its +implementation. For tricky code, comments alongside the code are more +appropriate than using docstrings.

+

A method that overrides a method from a base class may have a simple docstring +sending the reader to its overridden method's docstring, such as """See base +class.""". The rationale is that there is no need to repeat in many places +documentation that is already present in the base method's docstring. However, +if the overriding method's behavior is substantially different from the +overridden method, or details need to be provided (e.g., documenting additional +side effects), a docstring with at least those differences is required on the +overriding method.

+

Certain aspects of a function should be documented in special sections, listed +below. Each section begins with a heading line, which ends with a colon. All +sections other than the heading should maintain a hanging indent of two or four +spaces (be consistent within a file). These sections can be omitted in cases +where the function's name and signature are informative enough that it can be +aptly described using a one-line docstring.

+

Args: +: List each parameter by name. A description should follow the name, and be + separated by a colon followed by either a space or newline. If the + description is too long to fit on a single 80-character line, use a hanging + indent of 2 or 4 spaces more than the parameter name (be consistent with the + rest of the docstrings in the file). The description should include required + type(s) if the code does not contain a corresponding type annotation. If a + function accepts *foo (variable length argument lists) and/or **bar + (arbitrary keyword arguments), they should be listed as *foo and **bar.

+

Returns: (or Yields: for generators) +: Describe the type and semantics of the return value. If the function only + returns None, this section is not required. It may also be omitted if the + docstring starts with Returns or Yields (e.g. """Returns row from Bigtable + as a tuple of strings.""") and the opening sentence is sufficient to + describe return value.

+

Raises: +: List all exceptions that are relevant to the interface followed by a + description. Use a similar exception name + colon + space or newline and + hanging indent style as described in Args:. You should not document + exceptions that get raised if the API specified in the docstring is violated + (because this would paradoxically make behavior under violation of the API + part of the API).

+
def fetch_smalltable_rows(table_handle: smalltable.Table,
+                          keys: Sequence[Union[bytes, str]],
+                          require_all_keys: bool = False,
+) -> Mapping[bytes, Tuple[str]]:
+    """Fetches rows from a Smalltable.
+
+    Retrieves rows pertaining to the given keys from the Table instance
+    represented by table_handle.  String keys will be UTF-8 encoded.
+
+    Args:
+        table_handle: An open smalltable.Table instance.
+        keys: A sequence of strings representing the key of each table
+          row to fetch.  String keys will be UTF-8 encoded.
+        require_all_keys: Optional; If require_all_keys is True only
+          rows with values set for all keys will be returned.
+
+    Returns:
+        A dict mapping keys to the corresponding table row data
+        fetched. Each row is represented as a tuple of strings. For
+        example:
+
+        {b'Serak': ('Rigel VII', 'Preparer'),
+         b'Zim': ('Irk', 'Invader'),
+         b'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+        Returned keys are always bytes.  If a key from the keys argument is
+        missing from the dictionary, then that row was not found in the
+        table (and require_all_keys must have been False).
+
+    Raises:
+        IOError: An error occurred accessing the smalltable.
+    """
+
+

Similarly, this variation on Args: with a line break is also allowed:

+
def fetch_smalltable_rows(table_handle: smalltable.Table,
+                          keys: Sequence[Union[bytes, str]],
+                          require_all_keys: bool = False,
+) -> Mapping[bytes, Tuple[str]]:
+    """Fetches rows from a Smalltable.
+
+    Retrieves rows pertaining to the given keys from the Table instance
+    represented by table_handle.  String keys will be UTF-8 encoded.
+
+    Args:
+      table_handle:
+        An open smalltable.Table instance.
+      keys:
+        A sequence of strings representing the key of each table row to
+        fetch.  String keys will be UTF-8 encoded.
+      require_all_keys:
+        Optional; If require_all_keys is True only rows with values set
+        for all keys will be returned.
+
+    Returns:
+      A dict mapping keys to the corresponding table row data
+      fetched. Each row is represented as a tuple of strings. For
+      example:
+
+      {b'Serak': ('Rigel VII', 'Preparer'),
+       b'Zim': ('Irk', 'Invader'),
+       b'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+      Returned keys are always bytes.  If a key from the keys argument is
+      missing from the dictionary, then that row was not found in the
+      table (and require_all_keys must have been False).
+
+    Raises:
+      IOError: An error occurred accessing the smalltable.
+    """
+
+

3.8.4 Classes

+

Classes should have a docstring below the class definition describing the class. +If your class has public attributes, they should be documented here in an +Attributes section and follow the same formatting as a +function's Args section.

+
class SampleClass:
+    """Summary of class here.
+
+    Longer class information....
+    Longer class information....
+
+    Attributes:
+        likes_spam: A boolean indicating if we like SPAM or not.
+        eggs: An integer count of the eggs we have laid.
+    """
+
+    def __init__(self, likes_spam=False):
+        """Inits SampleClass with blah."""
+        self.likes_spam = likes_spam
+        self.eggs = 0
+
+    def public_method(self):
+        """Performs operation blah."""
+
+

3.8.5 Block and Inline Comments

+

The final place to have comments is in tricky parts of the code. If you're going +to have to explain it at the next code review, +you should comment it now. Complicated operations get a few lines of comments +before the operations commence. Non-obvious ones get comments at the end of the +line.

+
# We use a weighted dictionary search to find out where i is in
+# the array.  We extrapolate position based on the largest num
+# in the array and the array size and then do binary search to
+# get the exact number.
+
+if i & (i-1) == 0:  # True if i is 0 or a power of 2.
+
+

To improve legibility, these comments should start at least 2 spaces away from +the code with the comment character #, followed by at least one space before +the text of the comment itself.

+

On the other hand, never describe the code. Assume the person reading the code +knows Python (though not what you're trying to do) better than you do.

+
# BAD COMMENT: Now go through the b array and make sure whenever i occurs
+# the next element is i+1
+
+ + +

3.8.6 Punctuation, Spelling, and Grammar

+

Pay attention to punctuation, spelling, and grammar; it is easier to read +well-written comments than badly written ones.

+

Comments should be as readable as narrative text, with proper capitalization and +punctuation. In many cases, complete sentences are more readable than sentence +fragments. Shorter comments, such as comments at the end of a line of code, can +sometimes be less formal, but you should be consistent with your style.

+

Although it can be frustrating to have a code reviewer point out that you are +using a comma when you should be using a semicolon, it is very important that +source code maintain a high level of clarity and readability. Proper +punctuation, spelling, and grammar help with that goal.

+

3.10 Strings

+

Use an +f-string, +the % operator, or the format method for formatting strings, even when the +parameters are all strings. Use your best judgment to decide between + and % +(or format) though. Do not use % or the format method for pure +concatenation.

+
Yes: x = a + b
+     x = '%s, %s!' % (imperative, expletive)
+     x = '{}, {}'.format(first, second)
+     x = 'name: %s; score: %d' % (name, n)
+     x = 'name: {}; score: {}'.format(name, n)
+     x = f'name: {name}; score: {n}'
+
+
No: x = '%s%s' % (a, b)  # use + in this case
+    x = '{}{}'.format(a, b)  # use + in this case
+    x = first + ', ' + second
+    x = 'name: ' + name + '; score: ' + str(n)
+
+

Avoid using the + and += operators to accumulate a string within a loop. In +some conditions, accumulating a string with addition can lead to quadratic +rather than linear running time. Although common accumulations of this sort may +be optimized on CPython, that is an implementation detail. The conditions under +which an optimization applies are not easy to predict and may change. Instead, +add each substring to a list and ''.join the list after the loop terminates, +or write each substring to an io.StringIO buffer. These techniques +consistently have amortized-linear run time complexity.

+
Yes: items = ['<table>']
+     for last_name, first_name in employee_list:
+         items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
+     items.append('</table>')
+     employee_table = ''.join(items)
+
+
No: employee_table = '<table>'
+    for last_name, first_name in employee_list:
+        employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
+    employee_table += '</table>'
+
+

Be consistent with your choice of string quote character within a file. Pick ' +or " and stick with it. It is okay to use the other quote character on a +string to avoid the need to \\ escape within the string.

+
Yes:
+  Python('Why are you hiding your eyes?')
+  Gollum("I'm scared of lint errors.")
+  Narrator('"Good!" thought a happy Python reviewer.')
+
+
No:
+  Python("Why are you hiding your eyes?")
+  Gollum('The lint. It burns. It burns us.')
+  Gollum("Always the great lint. Watching. Watching.")
+
+

Prefer """ for multi-line strings rather than '''. Projects may choose to +use ''' for all non-docstring multi-line strings if and only if they also use +' for regular strings. Docstrings must use """ regardless.

+

Multi-line strings do not flow with the indentation of the rest of the program. +If you need to avoid embedding extra space in the string, use either +concatenated single-line strings or a multi-line string with +textwrap.dedent() +to remove the initial space on each line:

+
  No:
+  long_string = """This is pretty ugly.
+Don't do this.
+"""
+
+
  Yes:
+  long_string = """This is fine if your use case can accept
+      extraneous leading spaces."""
+
+
  Yes:
+  long_string = ("And this is fine if you cannot accept\n" +
+                 "extraneous leading spaces.")
+
+
  Yes:
+  long_string = ("And this too is fine if you cannot accept\n"
+                 "extraneous leading spaces.")
+
+
  Yes:
+  import textwrap
+
+  long_string = textwrap.dedent("""\
+      This is also fine, because textwrap.dedent()
+      will collapse common leading spaces in each line.""")
+
+

3.10.1 Logging

+

For logging functions that expect a pattern-string (with %-placeholders) as +their first argument: Always call them with a string literal (not an f-string!) +as their first argument with pattern-parameters as subsequent arguments. Some +logging implementations collect the unexpanded pattern-string as a queryable +field. It also prevents spending time rendering a message that no logger is +configured to output.

+
  Yes:
+  import tensorflow as tf
+  logger = tf.get_logger()
+  logger.info('TensorFlow Version is: %s', tf.__version__)
+
+
  Yes:
+  import os
+  from absl import logging
+
+  logging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))
+
+  homedir = os.getenv('HOME')
+  if homedir is None or not os.access(homedir, os.W_OK):
+    logging.error('Cannot write to home directory, $HOME=%r', homedir)
+
+
  No:
+  import os
+  from absl import logging
+
+  logging.info('Current $PAGER is:')
+  logging.info(os.getenv('PAGER', default=''))
+
+  homedir = os.getenv('HOME')
+  if homedir is None or not os.access(homedir, os.W_OK):
+    logging.error(f'Cannot write to home directory, $HOME={homedir!r}')
+
+

3.10.2 Error Messages

+

Error messages (such as: message strings on exceptions like ValueError, or +messages shown to the user) should follow three guidelines:

+
    +
  1. +

    The message needs to precisely match the actual error condition.

    +
  2. +
  3. +

    Interpolated pieces need to always be clearly identifiable as such.

    +
  4. +
  5. +

    They should allow simple automated processing (e.g. grepping).

    +
  6. +
+
  Yes:
+  if not 0 <= p <= 1:
+    raise ValueError(f'Not a probability: {p!r}')
+
+  try:
+    os.rmdir(workdir)
+  except OSError as error:
+    logging.warning('Could not remove directory (reason: %r): %r',
+                    error, workdir)
+
+
  No:
+  if p < 0 or p > 1:  # PROBLEM: also false for float('nan')!
+    raise ValueError(f'Not a probability: {p!r}')
+
+  try:
+    os.rmdir(workdir)
+  except OSError:
+    # PROBLEM: Message makes an assumption that might not be true:
+    # Deletion might have failed for some other reason, misleading
+    # whoever has to debug this.
+    logging.warning('Directory already was deleted: %s', workdir)
+
+  try:
+    os.rmdir(workdir)
+  except OSError:
+    # PROBLEM: The message is harder to grep for than necessary, and
+    # not universally non-confusing for all possible values of `workdir`.
+    # Imagine someone calling a library function with such code
+    # using a name such as workdir = 'deleted'. The warning would read:
+    # "The deleted directory could not be deleted."
+    logging.warning('The %s directory could not be deleted.', workdir)
+
+

3.11 Files and Sockets

+

Explicitly close files and sockets when done with them.

+

Leaving files, sockets or other file-like objects open unnecessarily has many +downsides:

+
    +
  • They may consume limited system resources, such as file descriptors. Code + that deals with many such objects may exhaust those resources unnecessarily + if they're not returned to the system promptly after use.
  • +
  • Holding files open may prevent other actions such as moving or deleting + them.
  • +
  • Files and sockets that are shared throughout a program may inadvertently be + read from or written to after logically being closed. If they are actually + closed, attempts to read or write from them will throw exceptions, making + the problem known sooner.
  • +
+

Furthermore, while files and sockets are automatically closed when the file +object is destructed, tying the lifetime of the file object to the state of the +file is poor practice:

+
    +
  • There are no guarantees as to when the runtime will actually run the file's + destructor. Different Python implementations use different memory management + techniques, such as delayed garbage collection, which may increase the + object's lifetime arbitrarily and indefinitely.
  • +
  • Unexpected references to the file, e.g. in globals or exception tracebacks, + may keep it around longer than intended.
  • +
+

The preferred way to manage files is using the +with statement:

+
with open("hello.txt") as hello_file:
+    for line in hello_file:
+        print(line)
+
+

For file-like objects that do not support the with statement, use +contextlib.closing():

+
import contextlib
+
+with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
+    for line in front_page:
+        print(line)
+
+

3.12 TODO Comments

+

TODO comments should not be merged to master, you can use them while developing your branch.

+
+

If the TODO is out of the scope of the Ticket a new one should be created.
+If it is in the scope of the ticket it should be implemented.

+
+

3.13 Imports formatting

+

Imports should be on separate lines; there are +exceptions for typing imports.

+

E.g.:

+
Yes: import os
+     import sys
+     from typing import Mapping, Sequence
+
+
No:  import os, sys
+
+

Imports are always put at the top of the file, just after any module comments +and docstrings and before module globals and constants. Imports should be +grouped from most generic to least generic:

+
    +
  1. +

    Python future import statements. For example:

    +
    from __future__ import absolute_import
    +from __future__ import division
    +from __future__ import print_function
    +
    +

    See above for more information about those.

    +
  2. +
  3. +

    Python standard library imports. For example:

    +
    import sys
    +
    +
  4. +
  5. +

    third-party module + or package imports. For example:

    +
    import tensorflow as tf
    +
    +
  6. +
  7. +

    Code repository + sub-package imports. For example:

    +
    from otherproject.ai import mind
    +
    +
  8. +
  9. +

    Deprecated: application-specific imports that are part of the same + top level + sub-package as this file. For example:

    +
    from myproject.backend.hgwells import time_machine
    +
    +

    You may find older Google Python Style code doing this, but it is no longer +required. New code is encouraged not to bother with this. Simply treat +application-specific sub-package imports the same as other sub-package +imports.

    +
  10. +
+

Within each grouping, imports should be sorted lexicographically, ignoring case, +according to each module's full package path (the path in from path import +...). Code may optionally place a blank line between import sections.

+
import collections
+import queue
+import sys
+
+from absl import app
+from absl import flags
+import bs4
+import cryptography
+import tensorflow as tf
+
+from book.genres import scifi
+from myproject.backend import huxley
+from myproject.backend.hgwells import time_machine
+from myproject.backend.state_machine import main_loop
+from otherproject.ai import body
+from otherproject.ai import mind
+from otherproject.ai import soul
+
+# Older style code may have these imports down here instead:
+#from myproject.backend.hgwells import time_machine
+#from myproject.backend.state_machine import main_loop
+
+

3.14 Statements

+

Generally only one statement per line.

+

However, you may put the result of a test on the same line as the test only if +the entire statement fits on one line. In particular, you can never do so with +try/except since the try and except can't both fit on the same line, and +you can only do so with an if if there is no else.

+
Yes:
+
+  if foo: bar(foo)
+
+
No:
+
+  if foo: bar(foo)
+  else:   baz(foo)
+
+  try:               bar(foo)
+  except ValueError: baz(foo)
+
+  try:
+      bar(foo)
+  except ValueError: baz(foo)
+
+

3.15 Accessors

+

If an accessor function would be trivial, you should use public variables +instead of accessor functions to avoid the extra cost of function calls in +Python. When more functionality is added you can use property to keep the +syntax consistent.

+

On the other hand, if access is more complex, or the cost of accessing the +variable is significant, you should use function calls (following the +Naming guidelines) such as get_foo() and set_foo(). If the +past behavior allowed access through a property, do not bind the new accessor +functions to the property. Any code still attempting to access the variable by +the old method should break visibly so they are made aware of the change in +complexity.

+

3.16 Naming

+

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.

+

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.

+

Always use a .py filename extension. Never use dashes.

+

3.16.1 Names to Avoid

+
    +
  • +

    single character names, except for specifically allowed cases:

    +
  • +
  • +

    counters or iterators (e.g. i, j, k, v, et al.)

    +
  • +
  • e as an exception identifier in try/except statements.
  • +
  • +

    f as a file handle in with statements

    +

    Please be mindful not to abuse single-character naming. Generally speaking, +descriptiveness should be proportional to the name's scope of visibility. +For example, i might be a fine name for 5-line code block but within +multiple nested scopes, it is likely too vague.

    +
  • +
  • +

    dashes (-) in any package/module name

    +
  • +
  • +

    __double_leading_and_trailing_underscore__ names (reserved by Python)

    +
  • +
  • +

    offensive terms

    +
  • +
  • +

    names that needlessly include the type of the variable (for example: + id_to_name_dict)

    +
  • +
+

3.16.2 Naming Conventions

+
    +
  • +

    "Internal" means internal to a module, or protected or private within a + class.

    +
  • +
  • +

    Prepending a single underscore (_) has some support for protecting module + variables and functions (linters will flag protected member access). While + prepending a double underscore (__ aka "dunder") to an instance variable + or method effectively makes the variable or method private to its class + (using name mangling); we discourage its use as it impacts readability and + testability, and isn't really private.

    +
  • +
  • +

    Place related classes and top-level functions together in a + module. + Unlike Java, there is no need to limit yourself to one class per module.

    +
  • +
  • +

    Use CapWords for class names, but lower_with_under.py for module names. + Although there are some old modules named CapWords.py, this is now + discouraged because it's confusing when the module happens to be named after + a class. ("wait -- did I write import StringIO or from StringIO import + StringIO?")

    +
  • +
  • +

    Underscores may appear in unittest method names starting with test to + separate logical components of the name, even if those components use + CapWords. One possible pattern is test<MethodUnderTest>_<state>; for + example testPop_EmptyStack is okay. There is no One Correct Way to name + test methods.

    +
  • +
+

3.16.3 File Naming

+

Python filenames must have a .py extension and must not contain dashes (-). +This allows them to be imported and unittested. If you want an executable to be +accessible without the extension, use a symbolic link or a simple bash wrapper +containing exec "$0.py" "$@".

+

3.16.4 Guidelines derived from Guido's Recommendations

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypePublicInternal
Packageslower_with_under
Moduleslower_with_under_lower_with_under
ClassesCapWords_CapWords
ExceptionsCapWords
Functionslower_with_under()_lower_with_under()
Global/Class ConstantsCAPS_WITH_UNDER_CAPS_WITH_UNDER
Global/Class Variableslower_with_under_lower_with_under
Instance Variableslower_with_under_lower_with_under(protected)
Method Nameslower_with_under()_lower_with_under()(protected)
Function/Method Parameterslower_with_under
Local Variableslower_with_under
+

3.17 Main

+

In Python, pydoc as well as unit tests require modules to be importable. If a +file is meant to be used as an executable, its main functionality should be in a +main() function, and your code should always check if __name__ == '__main__' +before executing your main program, so that it is not executed when the module +is imported.

+

When using absl, use app.run:

+
from absl import app
+...
+
+def main(argv):
+    # process non-flag arguments
+    ...
+
+if __name__ == '__main__':
+    app.run(main)
+
+

Otherwise, use:

+
def main():
+    ...
+
+if __name__ == '__main__':
+    main()
+
+

All code at the top level will be executed when the module is imported. Be +careful not to call functions, create objects, or perform other operations that +should not be executed when the file is being pydoced.

+

3.18 Function length

+

Prefer small and focused functions.

+

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.

+

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.

+

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.

+

3.19 Type Annotations

+

3.19.1 General Rules

+
    +
  • Familiarize yourself with + PEP-484.
  • +
  • In methods, only annotate self, or cls if it is necessary for proper + type information. e.g., @classmethod def create(cls: Type[T]) -> T: return + cls()
  • +
  • If any other variable or a returned type should not be expressed, use Any.
  • +
  • You are not required to annotate all the functions in a module.
  • +
  • At least annotate your public APIs.
  • +
  • Use judgment to get to a good balance between safety and clarity on the + one hand, and flexibility on the other.
  • +
  • Annotate code that is prone to type-related errors (previous bugs or + complexity).
  • +
  • Annotate code that is hard to understand.
  • +
  • Annotate code as it becomes stable from a types perspective. In many + cases, you can annotate all the functions in mature code without losing + too much flexibility.
  • +
+

3.19.2 Line Breaking

+

Try to follow the existing indentation rules.

+

After annotating, many function signatures will become "one parameter per line".

+
def my_method(self,
+              first_var: int,
+              second_var: Foo,
+              third_var: Optional[Bar]) -> int:
+  ...
+
+

Always prefer breaking between variables, and not, for example, between variable +names and type annotations. However, if everything fits on the same line, go for +it.

+
def my_method(self, first_var: int) -> int:
+  ...
+
+

If the combination of the function name, the last parameter, and the return type +is too long, indent by 4 in a new line.

+
def my_method(
+    self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
+  ...
+
+

When the return type does not fit on the same line as the last parameter, the +preferred way is to indent the parameters by 4 on a new line and align the +closing parenthesis with the def.

+
Yes:
+def my_method(
+    self, other_arg: Optional[MyLongType]
+) -> Dict[OtherLongType, MyLongType]:
+  ...
+
+

pylint +allows you to move the closing parenthesis to a new line and align with the +opening one, but this is less readable.

+
No:
+def my_method(self,
+              other_arg: Optional[MyLongType]
+             ) -> Dict[OtherLongType, MyLongType]:
+  ...
+
+

As in the examples above, prefer not to break types. However, sometimes they are +too long to be on a single line (try to keep sub-types unbroken).

+
def my_method(
+    self,
+    first_var: Tuple[List[MyLongType1],
+                     List[MyLongType2]],
+    second_var: List[Dict[
+        MyLongType3, MyLongType4]]) -> None:
+  ...
+
+

If a single name and type is too long, consider using an +alias for the type. The last resort is to break after the +colon and indent by 4.

+
Yes:
+def my_function(
+    long_variable_name:
+        long_module_name.LongTypeName,
+) -> None:
+  ...
+
+
No:
+def my_function(
+    long_variable_name: long_module_name.
+        LongTypeName,
+) -> None:
+  ...
+
+

3.19.3 Forward Declarations

+

If you need to use a class name from the same module that is not yet defined -- +for example, if you need the class inside the class declaration, or if you use a +class that is defined below -- use a string for the class name.

+
class MyClass:
+
+  def __init__(self,
+               stack: List["MyClass"]) -> None:
+
+

3.19.4 Default Values

+

As per +PEP-008, use +spaces around the = only for arguments that have both a type annotation and +a default value.

+
Yes:
+def func(a: int = 0) -> int:
+  ...
+
+
No:
+def func(a:int=0) -> int:
+  ...
+
+

3.19.5 NoneType

+

In the Python type system, NoneType is a "first class" type, and for typing +purposes, None is an alias for NoneType. If an argument can be None, it +has to be declared! You can use Union, but if there is only one other type, +use Optional.

+

Use explicit Optional instead of implicit Optional. Earlier versions of PEP +484 allowed a: Text = None to be interpreted as a: Optional[Text] = None, +but that is no longer the preferred behavior.

+
Yes:
+def func(a: Optional[Text], b: Optional[Text] = None) -> Text:
+  ...
+def multiple_nullable_union(a: Union[None, Text, int]) -> Text
+  ...
+
+
No:
+def nullable_union(a: Union[None, Text]) -> Text:
+  ...
+def implicit_optional(a: Text = None) -> Text:
+  ...
+
+

3.19.6 Type Aliases

+

You can declare aliases of complex types. The name of an alias should be +CapWorded. If the alias is used only in this module, it should be _Private.

+

For example, if the name of the module together with the name of the type is too +long:

+
_ShortName = module_with_long_name.TypeWithLongName
+ComplexMap = Mapping[Text, List[Tuple[int, int]]]
+
+

Other examples are complex nested types and multiple return variables from a +function (as a tuple).

+

3.19.7 Ignoring Types

+

You can disable type checking on a line with the special comment # type: +ignore.

+

pytype has a disable option for specific errors (similar to lint):

+
# pytype: disable=attribute-error
+
+

3.19.8 Typing Variables

+

If an internal variable has a type that is hard or impossible to infer, you can +specify its type in a couple ways.

+

Type Comments: +: Use a # type: comment on the end of the line

+
a = SomeUndecoratedFunction()  # type: Foo
+
+

Annotated Assignments +: Use a colon and type between the variable name and value, as with function + arguments.

+
a: Foo = SomeUndecoratedFunction()
+
+

3.19.9 Tuples vs Lists

+

Typed lists can only contain objects of a single type. Typed tuples can either +have a single repeated type or a set number of elements with different types. +The latter is commonly used as the return type from a function.

+
a = [1, 2, 3]  # type: List[int]
+b = (1, 2, 3)  # type: Tuple[int, ...]
+c = (1, "2", 3.5)  # type: Tuple[int, Text, float]
+
+

3.19.10 TypeVars

+

The Python type system has +generics. The factory +function TypeVar is a common way to use them.

+

Example:

+
from typing import List, TypeVar
+T = TypeVar("T")
+...
+def next(l: List[T]) -> T:
+  return l.pop()
+
+

A TypeVar can be constrained:

+
AddableType = TypeVar("AddableType", int, float, Text)
+def add(a: AddableType, b: AddableType) -> AddableType:
+  return a + b
+
+

A common predefined type variable in the typing module is AnyStr. Use it for +multiple annotations that can be bytes or unicode and must all be the same +type.

+
from typing import AnyStr
+def check_length(x: AnyStr) -> AnyStr:
+  if len(x) <= 42:
+    return x
+  raise ValueError()
+
+

3.19.11 String types

+

The proper type for annotating strings depends on what versions of Python the +code is intended for.

+

For Python 3 only code, prefer to use str. Text is also acceptable. Be +consistent in using one or the other.

+

For Python 2 compatible code, use Text. In some rare cases, str may make +sense; typically to aid compatibility when the return types aren't the same +between the two Python versions. Avoid using unicode: it doesn't exist in +Python 3.

+

The reason this discrepancy exists is because str means different things +depending on the Python version.

+
No:
+def py2_code(x: str) -> unicode:
+  ...
+
+

For code that deals with binary data, use bytes.

+
def deals_with_binary_data(x: bytes) -> bytes:
+  ...
+
+

For Python 2 compatible code that processes text data (str or unicode in +Python 2, str in Python 3), use Text. For Python 3 only code that process +text data, prefer str.

+
from typing import Text
+...
+def py2_compatible(x: Text) -> Text:
+  ...
+def py3_only(x: str) -> str:
+  ...
+
+

If the type can be either bytes or text, use Union, with the appropriate text +type.

+
from typing import Text, Union
+...
+def py2_compatible(x: Union[bytes, Text]) -> Union[bytes, Text]:
+  ...
+def py3_only(x: Union[bytes, str]) -> Union[bytes, str]:
+  ...
+
+

If all the string types of a function are always the same, for example if the +return type is the same as the argument type in the code above, use +AnyStr.

+

Writing it like this will simplify the process of porting the code to Python 3.

+

3.19.12 Imports For Typing

+

For classes from the typing module, always import the class itself. You are +explicitly allowed to import multiple specific classes on one line from the +typing module. Ex:

+
from typing import Any, Dict, Optional
+
+

Given that this way of importing from typing adds items to the local +namespace, any names in typing should be treated similarly to keywords, and +not be defined in your Python code, typed or not. If there is a collision +between a type and an existing name in a module, import it using import x as +y.

+
from typing import Any as AnyType
+
+

3.19.13 Conditional Imports

+

Use conditional imports only in exceptional cases where the additional imports +needed for type checking must be avoided at runtime. This pattern is +discouraged; alternatives such as refactoring the code to allow top level +imports should be preferred.

+

Imports that are needed only for type annotations can be placed within an if +TYPE_CHECKING: block.

+
    +
  • Conditionally imported types need to be referenced as strings, to be forward + compatible with Python 3.6 where the annotation expressions are actually + evaluated.
  • +
  • Only entities that are used solely for typing should be defined here; this + includes aliases. Otherwise it will be a runtime error, as the module will + not be imported at runtime.
  • +
  • The block should be right after all the normal imports.
  • +
  • There should be no empty lines in the typing imports list.
  • +
  • Sort this list as if it were a regular imports list.
  • +
+
import typing
+if typing.TYPE_CHECKING:
+  import sketch
+def f(x: "sketch.Sketch"): ...
+
+

3.19.14 Circular Dependencies

+

Circular dependencies that are caused by typing are code smells. Such code is a +good candidate for refactoring. Although technically it is possible to keep +circular dependencies, various build systems will not let you do so +because each module has to depend on the other.

+

Replace modules that create circular dependency imports with Any. Set an +alias with a meaningful name, and use the real type name from +this module (any attribute of Any is Any). Alias definitions should be separated +from the last import by one line.

+
from typing import Any
+
+some_mod = Any  # some_mod.py imports this module.
+...
+
+def my_method(self, var: "some_mod.SomeType") -> None:
+  ...
+
+

3.19.15 Generics

+

When annotating, prefer to specify type parameters for generic types; otherwise, +the generics' parameters will be assumed to be Any.

+
def get_names(employee_ids: List[int]) -> Dict[int, Any]:
+  ...
+
+
# These are both interpreted as get_names(employee_ids: List[Any]) -> Dict[Any, Any]
+def get_names(employee_ids: list) -> Dict:
+  ...
+
+def get_names(employee_ids: List) -> Dict:
+  ...
+
+

If the best type parameter for a generic is Any, make it explicit, but +remember that in many cases TypeVar might be more +appropriate:

+
def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
+  """Returns a mapping from employee ID to employee name for given IDs."""
+
+
T = TypeVar('T')
+def get_names(employee_ids: List[T]) -> Dict[T, Text]:
+  """Returns a mapping from employee ID to employee name for given IDs."""
+
+

4 Parting Words

+

BE CONSISTENT.

+

If you're editing code, take a few minutes to look at the code around you and +determine its style. If they use spaces around all their arithmetic operators, +you should too. If their comments have little boxes of hash marks around them, +make your comments have little boxes of hash marks around them too.

+

The point of having style guidelines is to have a common vocabulary of coding so +people can concentrate on what you're saying rather than on how you're saying +it. We present global style rules here so people know the vocabulary, but local +style is also important. If code you add to a file looks drastically different +from the existing code around it, it throws readers out of their rhythm when +they go to read it. Avoid this.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/best_practices/zen_of_python/index.html b/best_practices/zen_of_python/index.html new file mode 100644 index 0000000..786af04 --- /dev/null +++ b/best_practices/zen_of_python/index.html @@ -0,0 +1,1060 @@ + + + + + + + + + + + + + + + + + + + + + + Zen of Python - Python Knowledge Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Zen of Python

+

Available in the shell

+

import this

+
    +
  1. Beautiful is better than ugly.
  2. +
  3. Explicit is better than implicit.
  4. +
  5. Simple is better than complex.
  6. +
  7. Complex is better than complicated.
  8. +
  9. Flat is better than nested.
  10. +
  11. Sparse is better than dense.
  12. +
  13. Readability counts.
  14. +
  15. Special cases aren't special enough to break the rules.
  16. +
  17. Although practicality beats purity.
  18. +
  19. Errors should never pass silently.
  20. +
  21. Unless explicitly silenced.
  22. +
  23. In the face of ambiguity, refuse the temptation to guess.
  24. +
  25. There should be one-- and preferably only one --obvious way to do it.
  26. +
  27. Although that way may not be obvious at first unless you're Dutch.
  28. +
  29. Now is better than never.
  30. +
  31. Although never is often better than right now.
  32. +
  33. If the implementation is hard to explain, it's a bad idea.
  34. +
  35. If the implementation is easy to explain, it may be a good idea.
  36. +
  37. Namespaces are one honking great idea -- let's do more of those!
  38. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/django/image_field/index.html b/django/image_field/index.html new file mode 100644 index 0000000..7e6c9f7 --- /dev/null +++ b/django/image_field/index.html @@ -0,0 +1,1268 @@ + + + + + + + + + + + + + + + + + + + + + + Django Image field - Python Knowledge Base + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Django Image field

+

For the ImageField to work it's necessary to have the Pillow library installed.

+

Django Models

+

The setup of the model containing the image field can be done in the following way.

+

class BookInformation(models.Model):
+    #...
+    cover_image = models.ImageField(upload_to="covers/", null=True)
+    #...
+
+Note that the upload_to option corresponds to the path in which the images are stored/saved in the media folder. +In this case the path will be .../library_project/media/covers/.

+

Factory Boy mock data

+

The easiest way to generate mock images data with factory boy is with the factory.django.ImageField() method just like the example below.

+
class BookInformationFactory(factory.django.DjangoModelFactory):
+    #...
+    cover_image = factory.django.ImageField(color=factory.Faker("color"))
+    #...
+
+

Note that the color option can be Hardcoded with something like color="blue" and that will result in the +generation of images with the color blue. In this case, factory.Faker("color") will generate random color names +which will result in the generation of images with diferent colors for each cover_image.

+

Django Model Serializer

+

Considering the previous factory, here is the correspondent serializer for POST and PUT requests.

+
class BookInformationCreateUpdateSerializer(serializers.ModelSerializer):
+    cover_image = serializers.ImageField(required=False, allow_empty_file=True)
+
+    class Meta:
+        model = BookInformation
+        fields = [     
+        #... , 
+        "cover_image", 
+        #... ,
+        ]
+
+

API View

+
class BookInformationViewset(viewsets.ModelViewSet):
+    queryset = BookInformation.objects.all()
+    serializer_class = BookInformationCreateUpdateSerializer
+
+

ImageField Views Tests

+

To test the views that involve the cover_image attribute you can simply generate an image based on the +implemented factory for the cover image just like the example below.

+
from PIL import Image
+import shutil
+from ..fixtures import REMOVE_MEDIA_FILES_PATH
+
+class BookCreateViewTestCase(APITestCase):
+    def setUp(self):
+        #...
+        self.data = {
+            "cover_image": #...
+            #...
+        }
+
+    def tearDown(self):
+        shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
+
+    def test_create_with_image(self):
+        cover = BookInformationFactory.create().cover_image
+
+        self.data["cover_image"] = cover
+        res = self.client.post(self.url, self.data, format="multipart")
+
+        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(
+            BookInformation.objects.first().cover_image.read(), BookInformation.objects.last().cover_image.read()
+        )
+
+    def test_create_with_image_failure(self):
+        self.data["cover_image"] = Image.new(mode="RGB", size=(20, 20))
+
+        res = self.client.post(self.url, self.data, format="multipart")
+
+        self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
+
+

It is crucial that the format option in set to "multipart" for the test to work. +It is also important to have the tearDown method implemented in order to delete the images +saved on the folder refered previously (.../library_project/media/covers/).

+

ImageField Views Tests using temp files

+

Other way of implementing a view test that includes an ImageField is with the SimpleUploadedFile() method.

+

The code below will generate an Image with the PIL Library, save it in a temporary file, +process it with SimpleUploadedFile() method and then make the POST request.

+

In this case the tearDown method still needs to be implemented, since the POST +request will save the image in the media folder.

+
from django.core.files.uploadedfile import SimpleUploadedFile
+from io import BytesIO
+from PIL import Image
+import shutil
+
+class BookCreateViewTestCase(APITestCase):
+    def setUp(self):
+        self.data = {
+            "cover_image": #...
+            #...
+        }
+
+    def tearDown(self):
+        shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
+
+    def test_create_with_image(self):
+        base_image = Image.new(mode="RGB", size=(20, 20))
+
+        tempFile = BytesIO()
+        base_image.save(tempFile, format="JPEG")
+        tempFile.seek(0)
+
+        with tempFile as temp:
+            read_image = SimpleUploadedFile("sample.jpg", temp.read(), content_type="image/jpeg")
+            self.data["cover_image"] = read_image
+
+            res = self.client.post(self.url, self.data, format="multipart")
+            read_image.seek(0)
+
+        self.assertEqual(res.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(BookInformation.objects.first().cover_image.read(), read_image.read())
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/best_practices/code_principles.md b/docs/best_practices/code_principles.md deleted file mode 100644 index ed6393f..0000000 --- a/docs/best_practices/code_principles.md +++ /dev/null @@ -1,458 +0,0 @@ -# Code Principles - -Code should be clear and easy to maintain, by maintaining we also mean writing and -revisiting tests if necessary. Below there's a list of principles which used -with [The Zen of Python](zen_of_python.md), will help us to write better code as well as to test it. - -## KISS - Keep it simple, stupid [^1] - -The snippet below is hard to read and takes time to understand - -```python -f = lambda x: x if x in {0, 1} else f(x - 1) + f(x - 2) -``` - -Alternatively, the next code snippet is much easier to understand and maintain. - -```python -def fibonacci(number: int) -> int: - if number in {0, 1}: - return number - return fibonacci(number - 1) + fibonacci(number - 2) -``` - -By adding descriptive names, type hints, and splitting the line into multiple ones it gets -easier to read and maintain. - -> The main goal in design should always be to be as easy to understand -as possible. - -## DRY - Don't repeat yourself [^2] - -This principle is about writing functions and automating sections of code that are repeated. If you perform the same -task multiple times in your code, consider a function or a loop to make your workflow more efficient. - -Let's consider the next example where we'll be using a math expression to convert temperature values from fahrenheit -to celsius. - -```python -temp_1 = 32 -res_1 = (temp_1-32) * 5/9 - -temp_2 = 40 -res_2 = (temp_2-32) * 5/9 -``` - -Some points to consider: - -- If the calculation changes, we'd need to update both expressions -- it's not clear what the math expression is calculating, unless you're familiar with it - -Let's create a method for the math expression. - -```python -def conv_fahr_to_celsius(fahr: float) -> float: - """Convert temperature in Fahrenheit to Celsius. - - Parameters: - ----------- - fahr: float - The temperature in Fahrenheit. - - Returns: - ----------- - Celsius : int or float - The temperature in Celsius. - """ - celsius = (fahr-32) * 5/9 - return celsius -``` - -And update the previous sample to use this method. - -```python -fahr_1 = 32 -celsius_1 = conv_fahr_to_celsius(fahr_1) - -fahr_2 = 40 -celsius_2 = conv_fahr_to_celsius(fahr_2) -``` - -In summary - -- The code is cleaner, because the repeated calculation was replaced with a function -- If this function is well-defined with a docstring that describes what it does, it is easier to -both understand and use. -- If you need to change the calculation itself, you can do so once in the function - -## SoC - Separation of concerns [^1] - -A known example of this is the model-view-controller (MVC) design. MVC separates a program -into three distinct areas: the data (model), the logic (controller), and what the page displays (view). - -## SOLID [^3] - -SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, -and maintainable. - -- **S**ingle-responsibility principle: _"A class should have one, and only one, reason to change."_ -- **O**pen–closed principle: _"Entities should be open for extension, but closed for modification."_ -- **L**iskov substitution principle: _"Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it."_ -- **I**nterface segregation principle: _"A client should not be forced to implement an interface that it doesn’t use."_ -- **D**ependency inversion principle: _"Depend upon abstractions, not concretions."_ - - -### Single-responsibility principle (SRP) - -Every component of your code (in general a class, but also a function) should have one and only one responsibility. -As a consequence of that, there should be only a reason to change it. - -Too often you see a piece of code that takes care of an entire process all at once. I.e., A function that loads data, -modifies and, plots them, all before returning its result. - -Let’s take a simpler example, where we have a list of number L = [n1, n2, …, nx] and we compute some mathematical -functions to this list. For example, compute the mean, median, etc. - -A **bad approach** would be to have a single function doing all the work: - -```python -import numpy as np - -def math_operations(list_): - # Compute Average - print(f"the mean is {np.mean(list_)}") - # Compute Max - print(f"the max is {np.max(list_)}") - -math_operations(list_ = [1,2,3,4,5]) -# the mean is 3.0 -# the max is 5 -``` - -The first thing we should do, to make this more SRP compliant, is to split the function math_operations into atomic -functions! Thus, when a function’s responsibility cannot be divided into more sub-parts. - -The second step is to make a single function (or class), generically named, “main”. This will call all the other -functions one-by-one in a step-to-step process. - -```python -import numpy as np - -def get_mean(list_): - """ - Compute Mean - """ - print(f"the mean is {np.mean(list_)}") - -def get_max(list_): - """ - Compute Max - """ - print(f"the max is {np.max(list_)}") - -def main(list_): - # Compute Average - get_mean(list_) - # Compute Max - get_max(list_) - -main([1,2,3,4,5]) -# the mean is 3.0 -# the max is 5 -``` - -Now, you would only have one single reason to change each function connected with “main”. - -The result of this simple action is that now: - -1. It is easier to localize errors. Any error in execution will point out to a smaller section of your code, -accelerating your debug phase. -2. Any part of the code is reusable in other section of your code. -3. Moreover and, often overlooked, is that it is easier to create testing for each function of your code. -Side note on testing: You should write tests before you actually write the script. But, this is often ignored in -favour of creating some nice result to be shown to the stakeholders instead. - -This is already a much bigger improvement with respect to the first code example. But, having created a “main” and -calling functions with single responsibility is not the full fulfilment of the SR principle. Indeed, our “main” has -many reasons to be changed. The class is actually fragile and hard to maintain. To solve that, let’s introduce the -next principle. - -### Open/Closed principle (OCP) - -You should not need to modify the code you have already written to accommodate new functionality, but simply add what -you now need. - -This does not mean that you cannot change your code when the code premises needs to be modified, but that if you need -to add new functions similar to the one present, you should not require to change other parts of the code. To clarify -this point let’s refer to the example we saw earlier. If we wanted to add new functionality, for example, compute the -median, we should have created a new method function and add its invocation to “main”. That would have added an -extension but also modified the main. - -We can solve this by turning all the functions we wrote into subclasses of a class. In this case, I have created an -abstract class called “Operations” with an abstract method “get_operation”. (Abstract classes are generally an -advanced topic. If you don’t know what an abstract class is, you can run the following code even without). - -Now, all the old functions, now classes are called by the __subclasses__() method. That will find all classes inheriting -from Operations and operate the function “operations” that is present in all subclasses. - -> for additional information on abstractmethod decorator please -> check https://docs.python.org/3/library/abc.html#abc.abstractmethod - -```python -import numpy as np -from abc import ABC, abstractmethod - -class Operations(ABC): - """Operations""" - @staticmethod - @abstractmethod - def operation(list_): - pass - -class Mean(Operations): - """Compute Max""" - @staticmethod - def operation(list_): - print(f"The mean is {np.mean(list_)}") - -class Max(Operations): - """Compute Max""" - @staticmethod - def operation(list_): - print(f"The max is {np.max(list_)}") - -class Main: - """Main""" - @staticmethod - @abstractmethod - def get_operations(list_): - # __subclasses__ will find all classes inheriting from Operations - for operation in Operations.__subclasses__(): - operation.operation(list_) - - -if __name__ == "__main__": - Main.get_operations([1,2,3,4,5]) -# The mean is 3.0 -# The max is 5 -``` - -If now we want to add a new operation e.g.: median, we will only need to add a class “Median” inheriting from the class -“Operations”. The newly formed subclass will be immediately picked up by __subclasses__() and no modification in any -other part of the code needs to happen. - -The result is a very flexible class, that requires minimum time to be maintained. - -### The Liskov substitution principle (LSP) - -Functions that use pointers or references to base classes must be able to use objects of derived classes without -knowing it, that alternatively can be expressed as, derived classes must be substitutable for their base classes. - -In (maybe) simpler words, if a subclass redefines a function also present in the parent class, a client-user should not -be noticing any difference in behaviour, and it is a substitute for the base class. For example, if you are using a -function and your colleague change the base class, you should not notice any difference in the function that you are -using. - -Among all the SOLID principle, this is the most abstruse to understand and to explain. For this principle, there is no -standard “template-like” solution where it must be applied, and it is hard to offer a “standard example” to showcase. - -In the most simplistic way, I can put it, this principle can be summarised by saying: -If in a subclass, you redefine a function that is also present in the base class, the two functions ought to have the -same behaviour. This, though, does not mean that they must be mandatory equal, but that the user, should expect that -the same type of result, given the same input. -In the example ocp.py, the “operation” method is present in the subclasses and in the base class, and an end-user should -expect the same behaviour from the two. - -The result of this principle is that we’d write our code in a consistent manner and, the end-user will need to learn how -our code works, only one. - - ->A consequence of LSP is that: the new redefined function in the subclass should be valid and ->be possibly used wherever the same function in the parent class is used. -> ->This is not, typically the case, indeed usually we, human, think in terms of set theory. ->Having a class that define a concept and subclasses that expand the first with an ->exception or different behaviour. -> ->For example, the subclass “Platypus”, of the base class “Mammals”, would have the ->exception that these mammals lay eggs. The LSP, tell us that it would create a ->function called “give_birth”, this function will have different behaviour ->for the subclass Platypus and the subclass Dog. Therefore, we should have had a ->more abstract base class than Mammals that accommodate this. ->If this sounds very confusing, do not worry, the application of this latter aspect ->of the LSP is rarely fully implemented, and it rarely leaves the theoretical textbooks. - -### The Interface Segregation Principle (ISP) - -Many client-specific interfaces are better than one general-purpose interface. In the context of classes, an interface -is considered, all the methods and properties exposed, thus, everything that a user can interact with that belongs to -that class. - -In this sense, the IS principles tell us that a class should only have the interface needed (SRP) and avoid methods -that won’t work or that have no reason to be part of that class. - -This problem arises, primarily, when, a subclass inherits methods from a base class that it does not need. - -Let’s see an example: - -> for additional information on abstractmethod decorator please -> check https://docs.python.org/3/library/abc.html#abc.abstractmethod - -```python -from abc import ABC, abstractmethod - -class Mammals(ABC): - @staticmethod - @abstractmethod - def swim(): - print("Can Swim") - - @staticmethod - @abstractmethod - def walk(): - print("Can Walk") - -class Human(Mammals): - @staticmethod - def swim(): - return print("Humans can swim") - - @staticmethod - def walk(): - return print("Humans can walk") - -class Whale(Mammals): - @staticmethod - def swim(): - return print("Whales can swim") -``` - -For this example, we have got the abstract class “Mammals” that has two abstract methods: “walk” and “swim”. These two -elements will belong to the subclass “Human”, whereas only “swim” will belong to the subclass “Whale”. - -And indeed, if we run this code we could have: - -```python -Human.swim() -Human.walk() - -Whale.swim() -Whale.walk() - -# Humans can swim -# Humans can walk -# Whales can swim -# Can Walk -``` - -The subclass whale can still invoke the method “walk” but it shouldn’t, and we must avoid it. - -The way suggested by ISP is to create more client-specific interfaces rather than one general-purpose interface. -So, our code example becomes: - -```python -from abc import ABC, abstractmethod - -class Walker(ABC): - @staticmethod - @abstractmethod - def walk(): - return print("Can Walk") - -class Swimmer(ABC): - @staticmethod - @abstractmethod - def swim(): - return print("Can Swim") - -class Human(Walker, Swimmer): - @staticmethod - def walk(): - return print("Humans can walk") - @staticmethod - def swim(): - return print("Humans can swim") - -class Whale(Swimmer): - @staticmethod - def swim(): - return print("Whales can swim") - -if __name__ == "__main__": - Human.walk() - Human.swim() - - Whale.swim() - Whale.walk() - -# Humans can walk -# Humans can swim -# Whales can swim -# AttributeError: type object 'Whale' has no attribute 'walk' -``` - -Now, every subclass inherits only what it needs, avoiding invoking an out-of-context (wrong) sub-method. That might -create an error hard to catch. - -This principle is closely connected with the other ones and specifically, it tells us to keep the content of a subclass -clean from elements of no use to that subclass. This has the final aim to keep our classes clean and minimise mistakes. - -### Dependency Inversion Principle (DIP) - -Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend -on low-level modules. Both should depend on abstractions. So, that abstractions (e.g., the interface, as seen above) -should not be dependent on low-level methods but both should depend on a third interface. - -To better explain this concept, I prefer to think of a sort of information flow. - -Imagine that you have a program that takes in input a specific set of info (a file, a format, etc) and you wrote a -script to process it. What would happen if that info were subject to change? -You would have to rewrite your script and adjust the new format. Losing the retro compatibility with the older files. - -However, you could solve this by creating a third abstraction that takes the info as input and passes it to the others. -This is basically what an API is also, used for. - -``` mermaid -flowchart LR - ObjectA --> |references| ObjectB - subgraph Package B - ObjectB - end - subgraph Package A - ObjectA - end -``` - -``` mermaid -flowchart TB - ObjectB --> |inherits| Interface - subgraph Package B - ObjectB - end - subgraph Package A - ObjectA--> |references| Interface - end -``` - -The interesting design concept of this principle is that it is the reverse approach to what we would normally do. - -With the DIP in mind, we would start from the end of the project, in which our code is independent of what takes in -input, and it is not susceptible to changes and out of our direct control. - -## YAGNI - You ain't gonna need it - -It's a mantra from Extreme Programming that's often used generally in agile software teams. It's a statement that some -capability we presume our software needs in the future should not be built now because "you aren't gonna need it". - -> For additional information on this make sure to -> check [https://www.martinfowler.com/bliki/Yagni.html](https://www.martinfowler.com/bliki/Yagni.html) - -## Document your code - -1. Don't comment bad code, rewrite it -2. Readable code doesn't need comments -3. Don't add noise comments - -[^1]: https://testdriven.io/blog/clean-code-python/ -[^2]: https://www.earthdatascience.org/courses/intro-to-earth-data-science/write-efficient-python-code/intro-to-clean-code/dry-modular-code/ -[^3]: https://towardsdatascience.com/solid-coding-in-python-1281392a6a94 diff --git a/docs/best_practices/styleguide.md b/docs/best_practices/styleguide.md deleted file mode 100644 index 76a7eb5..0000000 --- a/docs/best_practices/styleguide.md +++ /dev/null @@ -1,2688 +0,0 @@ -# Style Guide - -> Inspired by [https://google.github.io/styleguide/pyguide.html](https://google.github.io/styleguide/pyguide.html) and -[https://phalt.github.io/django-api-domains/styleguide/](https://phalt.github.io/django-api-domains/styleguide/). - -## Introduction - -This style guide is split into two sections, one for the project structure and another which is a list of -*dos and don'ts* for Python programs. - -## 1. Project structure - -### 1.1 Domains - -A **domain** is a piece of software that provides a distinct business value for your application. What this styleguide -calls a domain is roughly an extension of what Django would call an **app**. Therefore a business domain should have at -least one distinct software domain mirroring it. - -This guide tries to keep the key benefits of Django's app pattern - namely Django's models to represent tables in a -datastore, but with an emphasis on **skinny models**. - -#### 1.1.1 Domain rules - -1. You should split a domain if it becomes too big to work on. - -> A domain should allow between 4-6 developers (3 pairs) to comfortably work on it. If you find your developers being -blocked by each other then it is time to consider splitting the domain or checking whether the software has not -diverged too far from the styleguide. - -2. You should adhere to the styleguide patterns in this document in order to maintain strong bounded contexts between -your domains. - -> This applies even in situations where you extract one domain into two domains to increase velocity, but they still -have to maintain a dependency between one another. We have found that if you relax the bounded context between domains, -the boundary will erode and you will lose the ability to work on them independent of each other. - -### 1.2 Structure - -- **views.py**: Public functions and access points -- **serializers.py**: Public functions and access points presentation logic -- **interfaces.py**: Integrations with other domains or external services -- **models.py**: Object models and storage, simple information logic -- **services.py**: coordination and transactional logic -- **urls.py**: route definition -- **apps.py**: Django application configuration -- **migrations/\***: database migrations -- **management/\***: Django custom commands definition -- **routes/\***: Django custom databse routes -- **tests/\***: tests - -You can mask one of the required files as a directory for better file organisation. For example, you might want to split views.py file into this structure: - -```bash -views/ - __init__.py - brand.py - legacy_brand.py -``` - -#### 1.2.1 Migrations - -> You might notice that Django creates migrations even when nothing has changed in the model definition, these migrations should not be added to the codebase. - -Besides the initial migration, all the remaining migrations must include a name that represent the change while including the date and time of it's creation, in the following format `___