Table of Contents
Introduction
Software engineering principles, from Robert C. Martin's book Clean Code, adapted for Python. This is not a style guide. It's a guide to producing readable, reusable, and refactorable software in Python.
Not every principle herein has to be strictly followed, and even fewer will be universally agreed upon. These are guidelines and nothing more, but they are ones codified over many years of collective experience by the authors of Clean Code.
Inspired from clean-code-javascript
Targets Python3.7+
Variables
Use meaningful and pronounceable variable names
Bad:
import datetime ymdstr = datetime.date.today().strftime("%y-%m-%d")
Good:
import datetime current_date: str = datetime.date.today().strftime("%y-%m-%d")
Use the same vocabulary for the same type of variable
Bad: Here we use three different names for the same underlying entity:
def get_user_info(): pass def get_client_data(): pass def get_customer_record(): pass
Good: If the entity is the same, you should be consistent in referring to it in your functions:
def get_user_info(): pass def get_user_data(): pass def get_user_record(): pass
Even better Python is (also) an object oriented programming language. If it makes sense, package the functions together with the concrete implementation of the entity in your code, as instance attributes, property methods, or methods:
from typing import Union, Dict, Text class Record: pass class User: info : str @property def data(self) -> Dict[Text, Text]: return {} def get_record(self) -> Union[Record, None]: return Record()
Use searchable names
We will read more code than we will ever write. It's important that the code we do write is readable and searchable. By not naming variables that end up being meaningful for understanding our program, we hurt our readers. Make your names searchable.
Bad:
import time # What is the number 86400 for again? time.sleep(86400)
Good:
import time # Declare them in the global namespace for the module. SECONDS_IN_A_DAY = 60 * 60 * 24 time.sleep(SECONDS_IN_A_DAY)
Use explanatory variables
Bad:
import re address = "One Infinite Loop, Cupertino 95014" city_zip_code_regex = r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$" matches = re.match(city_zip_code_regex, address) if matches: print(f"{matches[1]}: {matches[2]}")
Not bad:
It's better, but we are still heavily dependent on regex.
import re address = "One Infinite Loop, Cupertino 95014" city_zip_code_regex = r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$" matches = re.match(city_zip_code_regex, address) if matches: city, zip_code = matches.groups() print(f"{city}: {zip_code}")
Good:
Decrease dependence on regex by naming subpatterns.
import re address = "One Infinite Loop, Cupertino 95014" city_zip_code_regex = r"^[^,\\]+[,\\\s]+(?P<city>.+?)\s*(?P<zip_code>\d{5})?$" matches = re.match(city_zip_code_regex, address) if matches: print(f"{matches['city']}, {matches['zip_code']}")
Avoid Mental Mapping
Don’t force the reader of your code to translate what the variable means. Explicit is better than implicit.
Bad:
seq = ("Austin", "New York", "San Francisco") for item in seq: #do_stuff() #do_some_other_stuff() # Wait, what's `item` again? print(item)
Good:
locations = ("Austin", "New York", "San Francisco") for location in locations: #do_stuff() #do_some_other_stuff() # ... print(location)
Don't add unneeded context
If your class/object name tells you something, don't repeat that in your variable name.
Bad:
class Car: car_make: str car_model: str car_color: str
Good:
class Car: make: str model: str color: str
Use default arguments instead of short circuiting or conditionals
Tricky
Why write:
import hashlib def create_micro_brewery(name): name = "Hipster Brew Co." if name is None else name slug = hashlib.sha1(name.encode()).hexdigest() # etc.
... when you can specify a default argument instead? This also makes it clear that you are expecting a string as the argument.
Good:
from typing import Text import hashlib def create_micro_brewery(name: Text = "Hipster Brew Co."): slug = hashlib.sha1(name.encode()).hexdigest() # etc.
Functions
Function arguments (2 or fewer ideally)
Limiting the amount of function parameters is incredibly important because it makes testing your function easier. Having more than three leads to a combinatorial explosion where you have to test tons of different cases with each separate argument.
Zero arguments is the ideal case. One or two arguments is ok, and three should be avoided. Anything more than that should be consolidated. Usually, if you have more th