Total 113 articles, this article is about 8000 words, about 20 minutes to read
Original text: github.com/zedr/clean-…
The Python version of code cleanliness. The directory is as follows:
- introduce
- variable
- function
1. Introduction
The principles of software engineering come from Robert C. Martin’s book Clean Code, and this article is Clean Code for the Python version. This is not a style guide, but a guide on how to write Readable, usable, and reconfigurable Pyhton code.
Not every principle outlined here must be strictly adhered to, and only a few will be universally agreed upon. What follows is just a guide, but it comes from the author of Clean Code, who has years of programming experience.
The Python version here is 3.7+
2. The variable
2.1 Use meaningful and interpretable variable names
Bad writing
ymdstr = datetime.date.today().strftime("%y-%m-%d")
Copy the code
Good way to write it
current_date: str = datetime.date.today().strftime("%y-%m-%d")
Copy the code
2.2 Use the same vocabulary for variables of the same type
Bad writing: There are three different names for entities with the same underscore
get_user_info()
get_client_data()
get_customer_record()
Copy the code
Good writing: If the entities are the same, the functions used should be consistent
get_user_info()
get_user_data()
get_user_record()
Copy the code
Better: Python is an object-oriented programming language, so you can put functions of the same entity in classes, either as instance properties or as methods
class User:
info : str
@property
def data(self) - >dict:
#...
def get_record(self) - >Union[Record, None] :
#...
Copy the code
2.3 Use searchable names
We often see more code than we write, so it is important that we write code that is readable and searchable. If we do not declare some meaningful variables, our programs will become unintelligible, as shown in the following example.
Bad writing
What does # 86400 mean?
time.sleep(86400)
Copy the code
Good way to write it
A global variable is declared
SECONDS_IN_A_DAY = 60 * 60 * 24
time.sleep(SECONDS_IN_A_DAY)
Copy the code
2.4 Variables with explanations are used
Bad writing
address = 'One Infinite Loop, Cupertino 95014'
city_zip_code_regex = r'^[^,\\]+[,\\\s]+(.+?) \s*(\d{5})? $'
matches = re.match(city_zip_code_regex, address)
save_city_zip_code(matches[1], matches[2])
Copy the code
That’s a good way to write it
This is better, but it still relies heavily on regular expressions
address = 'One Infinite Loop, Cupertino 95014'
city_zip_code_regex = r'^[^,\\]+[,\\\s]+(.+?) \s*(\d{5})? $'
matches = re.match(city_zip_code_regex, address)
city, zip_code = matches.groups()
save_city_zip_code(city, zip_code)
Copy the code
Good way to write it
Subschema naming reduces dependence on regular expressions
address = 'One Infinite Loop, Cupertino 95014'
city_zip_code_regex = r'^[^,\\]+[,\\\s]+(? P
.+?) \s*(? P
\d{5})? $'
matches = re.match(city_zip_code_regex, address)
save_city_zip_code(matches['city'], matches['zip_code'])
Copy the code
2.5 Avoid making readers guess
Do not make the reader need association to know the meaning of variable names; explicit is better than implicit.
Bad writing
seq = ('Austin'.'New York'.'San Francisco')
for item in seq:
do_stuff()
do_some_other_stuff()
#...
What does # item mean?
dispatch(item)
Copy the code
Good way to write it
locations = ('Austin'.'New York'.'San Francisco')
for location in locations:
do_stuff()
do_some_other_stuff()
#...
dispatch(location)
Copy the code
2.6 No additional context is required
If the class or object name already provides some information, it does not need to be repeated in the variable.
Bad writing
class Car:
car_make: str
car_model: str
car_color: str
Copy the code
Good way to write it
class Car:
make: str
model: str
color: str
Copy the code
2.7 Use default parameters instead of conditional statements
Bad writing
def create_micro_brewery(name) :
name = "Hipster Brew Co." if name is None else name
slug = hashlib.sha1(name.encode()).hexdigest()
# etc.
Copy the code
It is possible to set the name parameter to a default value without using a conditional statement.
Good way to write it
def create_micro_brewery(name: str = "Hipster Brew Co.") :
slug = hashlib.sha1(name.encode()).hexdigest()
# etc.
Copy the code
3. The function
3.1 Function Parameters (2 or fewer)
It is important to limit the number of arguments a function can take to test the code you write. More than three function parameters can result in test combinations exploding, meaning that many different test cases need to be considered.
No parameters is the best case. One or two parameters are also good, and three parameters should be avoided. If there are more than three then you need to tidy up the function. In general, if a function has more than two arguments, that means your function probably has a lot to implement. In addition, many times a high-level object can also be used as a parameter.
Bad writing
def create_menu(title, body, button_text, cancellable) :
#...
Copy the code
Nice way to write it
class Menu:
def __init__(self, config: dict) :
title = config["title"]
body = config["body"]
#...
menu = Menu(
{
"title": "My Menu"."body": "Something about my menu"."button_text": "OK"."cancellable": False})Copy the code
Another nice way to write it
class MenuConfig:
"""A configuration for the Menu. Attributes: title: The title of the Menu. body: The body of the Menu. button_text: The text for the button label. cancellable: Can it be cancelled? "" "
title: str
body: str
button_text: str
cancellable: bool = False
def create_menu(config: MenuConfig) :
title = config.title
body = config.body
#...
config = MenuConfig
config.title = "My delicious menu"
config.body = "A description of the various items on the menu"
config.button_text = "Order now!"
# The instance attribute overrides the default class attribute.
config.cancellable = True
create_menu(config)
Copy the code
Excellent writing
from typing import NamedTuple
class MenuConfig(NamedTuple) :
"""A configuration for the Menu. Attributes: title: The title of the Menu. body: The body of the Menu. button_text: The text for the button label. cancellable: Can it be cancelled? "" "
title: str
body: str
button_text: str
cancellable: bool = False
def create_menu(config: MenuConfig) :
title, body, button_text, cancellable = config
#...
create_menu(
MenuConfig(
title="My delicious menu",
body="A description of the various items on the menu",
button_text="Order now!"))Copy the code
A better way to write it
rom dataclasses import astuple, dataclass
@dataclass
class MenuConfig:
"""A configuration for the Menu. Attributes: title: The title of the Menu. body: The body of the Menu. button_text: The text for the button label. cancellable: Can it be cancelled? "" "
title: str
body: str
button_text: str
cancellable: bool = False
def create_menu(config: MenuConfig) :
title, body, button_text, cancellable = astuple(config)
#...
create_menu(
MenuConfig(
title="My delicious menu",
body="A description of the various items on the menu",
button_text="Order now!"))Copy the code
3.2 Functions should perform only one function
This is by far the most important rule in software engineering. It is difficult to decouple and test a function that performs more than one function. A function can be easily refactored if it can be separated into just one action, and the code is easy to read. Even if you just follow this advice, you’ll be better than many developers.
Bad writing
def email_clients(clients: List[Client]) :
"""Filter active clients and send them an email. Screen active clients and email them """
for client in clients:
if client.active:
email(client)
Copy the code
Good way to write it
def get_active_clients(clients: List[Client]) - >List[Client]:
"""Filter active clients. """
return [client for client in clients if client.active]
def email_clients(clients: List[Client, ...]) - >None:
"""Send an email to a given list of clients. """
for client in clients:
email(client)
Copy the code
This is where generators can be used to improve function writing.
A better way to write it
def active_clients(clients: List[Client]) -> Generator[Client]:
"""Only active clients. """
return (client for client in clients if client.active)
def email_client(clients: Iterator[Client]) - >None:
"""Send an email to a given list of clients. """
for client in clients:
email(client)
Copy the code
3.3 The function name should indicate the function function
Bad writing
class Email:
def handle(self) - >None:
# Do something...
message = Email()
# What is this supposed to do again?
What does this function need to do?
message.handle()
Copy the code
Good way to write it
class Email:
def send(self) - >None:
"""Send this message. """
message = Email()
message.send()
Copy the code
3.4 Functions should have only one layer of abstraction
If a function contains more than one layer of abstraction, it is usually a function that implements too much, and should be broken up into multiple functions to make it reusable and easier to test.
Bad writing
def parse_better_js_alternative(code: str) - >None:
regexes = [
#...
]
statements = regexes.split()
tokens = []
for regex in regexes:
for statement in statements:
#...
ast = []
for token in tokens:
# Lex.
for node in ast:
# Parse.
Copy the code
Good way to write it
REGEXES = (
#...
)
def parse_better_js_alternative(code: str) - >None:
tokens = tokenize(code)
syntax_tree = parse(tokens)
for node in syntax_tree:
# Parse.
def tokenize(code: str) - >list:
statements = code.split()
tokens = []
for regex in REGEXES:
for statement in statements:
# Append the statement to tokens.
return tokens
def parse(tokens: list) - >list:
syntax_tree = []
for token in tokens:
# Append the parsed token to the syntax tree.
return syntax_tree
Copy the code
3.5 Do not use flags as function arguments
Flags indicate that a function does more than one thing, but a function should only do one thing, so if you need flags, you’ll write one more function.
Bad writing
from pathlib import Path
def create_file(name: str, temp: bool) - >None:
if temp:
Path('./temp/' + name).touch()
else:
Path(name).touch()
Copy the code
Good way to write it
from pathlib import Path
def create_file(name: str) - >None:
Path(name).touch()
def create_temp_file(name: str) - >None:
Path('./temp/' + name).touch()
Copy the code
3.6 Avoid side effects of functions
A function has side effects when it does more than just input a number and return something else. For example, side effects might be writing data to files, changing global variables, or accidentally writing all your money to a stranger.
Sometimes, though, you have to create side effects in your program — for example, in the example just mentioned, you have to write data to a file. In this case, you should try to concentrate and indicate the functions that produce these side effects, for example, making sure that only one function produces data that is written to a particular file, rather than multiple functions or classes.
The main point of this advice is to avoid common pitfalls, such as analyzing state between objects without any structure, using modifiable data types that can be modified by any data, or using instance objects of a class without centralized side effects, etc. If you can follow this advice, you’ll be happier than many developers.
Bad writing
# This is a module-level name.
# It's good practice to define these as immutable values, such as a string.
# However...
name = 'Ryan McDermott'
def split_into_first_and_last_name() - >None:
# The use of the global keyword here is changing the meaning of the
# the following line. This function is now mutating the module-level
# state and introducing a side-effect!
# change global variable ();
The second call to the function will result in a different result from the first call.
global name
name = name.split()
split_into_first_and_last_name()
print(name) # ['Ryan', 'McDermott']
# OK. It worked the first time, but what will happen if we call the
# function again?
Copy the code
Good way to write it
def split_into_first_and_last_name(name: str) - >list:
return name.split()
name = 'Ryan McDermott'
new_name = split_into_first_and_last_name(name)
print(name) # 'Ryan McDermott'
print(new_name) # ['Ryan', 'McDermott']
Copy the code
Another good way to write it
from dataclasses import dataclass
@dataclass
class Person:
name: str
@property
def name_as_first_and_last(self) - >list:
return self.name.split()
# The reason why we create instances of classes is to manage state!
person = Person('Ryan McDermott')
print(person.name) # 'Ryan McDermott'
print(person.name_as_first_and_last) # ['Ryan', 'McDermott']
Copy the code
conclusion
The original table of contents actually has three sections:
- Objects and data structures
- class
-
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- Don’t repeat
The author hasn’t updated it yet, so if you want to know more about it, you’re advised to read the corresponding section of Clean Code.
Public account recently recommended reading: \
GAN is 6 years old! It’s time to stroke him!
Hundreds of GAN papers have been downloaded! With a recent overview of generative adversarial networks! \
A bit exaggerated, a bit twisted! See how these Gans exaggerate and caricature human faces! \
It rained from heaven, but nothing for me! How about GAN for rain? \
Face to face! GAN can make the side face killer, peppa pig true face no escape? \
Losing face! GAN to predict? \
Weak water three thousand, only take your mark! How about AL (Active Learning) and GAN? \
Anomaly detection, GAN how GAN?
Virtual change of clothes! Quick overview of these latest papers how to do! \
Facial makeup migration! Take a quick look at a few papers using GAN
[1] How about the generation of GAN in medical images?
01-GAN formula concise principle of ironclad small treasure
GAN&CV communication Group, no matter small white or big guy, sincerely invite you to join! \
Discuss and exchange! Long press note to join:
Share more, long click to follow our public account: \