python版代码整洁之道

总第 113 篇文章,本文大约 8000 字,阅读大约需要 20 分钟

原文:https://github.com/zedr/clean-code-python

python 版的代码整洁之道。目录如下所示:

  1. 介绍

  2. 变量

  3. 函数


1. 介绍

软件工程的原则,来自 Robert C. Martin's 的书--《Clean Code》,而本文则是适用于 Python 版本的 clean code。这并不是一个风格指导,而是指导如何写出可读、可用以及可重构的 pyhton 代码。

并不是这里介绍的每个原则都必须严格遵守,甚至只有很少部分会得到普遍的赞同。下面介绍的都只是指导而已,但这都是来自有多年编程经验的 《Clean Code》的作者。

这里的 python 版本是 3.7+


2. 变量

2.1 采用有意义和可解释的变量名

糟糕的写法

ymdstr = datetime.date.today().strftime("%y-%m-%d")

好的写法

current_date: str = datetime.date.today().strftime("%y-%m-%d")

2.2 对相同类型的变量使用相同的词汇

糟糕的写法:这里对有相同下划线的实体采用三个不同的名字

get_user_info()
get_client_data()
get_customer_record()

好的写法:如果实体是相同的,对于使用的函数应该保持一致

get_user_info()
get_user_data()
get_user_record()

更好的写法:python 是一个面向对象的编程语言,所以可以将相同实体的函数都放在类中,作为实例属性或者是方法

class User:info : str@propertydef data(self) -> dict:# ...def get_record(self) -> Union[Record, None]:# ...

2.3 采用可以搜索的名字

我们通常都是看的代码多于写过的代码,所以让我们写的代码是可读而且可以搜索的是非常重要的,如果不声明一些有意义的变量,会让我们的程序变得难以理解,例子如下所示。

糟糕的写法

# 86400 表示什么呢?
time.sleep(86400)

好的写法

# 声明了一个全局变量
SECONDS_IN_A_DAY = 60 * 60 * 24time.sleep(SECONDS_IN_A_DAY)

2.4 采用带解释的变量

糟糕的写法

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])

还行的写法

这个更好一点,但还是很依赖于正则表达式

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)

好的写法

通过子模式命名来减少对正则表达式的依赖

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)save_city_zip_code(matches['city'], matches['zip_code'])

2.5 避免让读者进行猜测

不要让读者需要联想才可以知道变量名的意思,显式比隐式更好。

糟糕的写法

seq = ('Austin', 'New York', 'San Francisco')for item in seq:do_stuff()do_some_other_stuff()# ...# item 是表示什么?dispatch(item)

好的写法

locations = ('Austin', 'New York', 'San Francisco')for location in locations:do_stuff()do_some_other_stuff()# ...dispatch(location)

2.6 不需要添加额外的上下文

如果类或者对象名称已经提供一些信息来,不需要在变量中重复。

糟糕的写法

class Car:car_make: strcar_model: strcar_color: str

好的写法

class Car:make: strmodel: strcolor: str

2.7 采用默认参数而不是条件语句

糟糕的写法

def create_micro_brewery(name):name = "Hipster Brew Co." if name is None else nameslug = hashlib.sha1(name.encode()).hexdigest()# etc.

这个写法是可以直接给 name 参数设置一个默认数值,而不需要采用一个条件语句来进行判断的。

好的写法

def create_micro_brewery(name: str = "Hipster Brew Co."):slug = hashlib.sha1(name.encode()).hexdigest()# etc.

3. 函数

3.1 函数参数(2个或者更少)

限制函数的参数个数是很重要的,这有利于测试你编写的函数代码。超过3个以上的函数参数会导致测试组合爆炸的情况,也就是需要考虑很多种不同的测试例子。

没有参数是最理想的情况。一到两个参数也是很好的,三个参数应该尽量避免。如果多于 3 个那么应该需要好好整理函数。通常,如果函数多于2个参数,那代表你的函数可能要实现的东西非常多。此外,很多时候,一个高级对象也是可以用作一个参数使用。

糟糕的写法

def create_menu(title, body, button_text, cancellable):# ...

很好的写法

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}
)

另一种很好的写法

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: strbody: strbutton_text: strcancellable: bool = Falsedef create_menu(config: MenuConfig):title = config.titlebody = 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 = Truecreate_menu(config)

优秀的写法

from typing import NamedTupleclass 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: strbody: strbutton_text: strcancellable: bool = Falsedef 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!")
)

更优秀的写法

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: strbody: strbutton_text: strcancellable: bool = Falsedef 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!")
)

3.2 函数应该只完成一个功能

这是目前为止软件工程里最重要的一个规则。函数如果完成多个功能,就很难对这个函数解耦、测试。如果可以对一个函数分离为仅仅一个动作,那么该函数可以很容易进行重构,并且代码也方便阅读。即便你仅仅遵守这一点建议,你也会比很多开发者更加优秀。

糟糕的写法

def email_clients(clients: List[Client]):"""Filter active clients and send them an email.筛选活跃的客户并发邮件给他们"""for client in clients:if client.active:email(client)

好的写法

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)

这里其实是可以使用生成器来改进函数的写法。

更好的写法

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)

3.3 函数的命名应该表明函数的功能

糟糕的写法

class Email:def handle(self) -> None:# Do something...message = Email()
# What is this supposed to do again?
# 这个函数是需要做什么呢?
message.handle()

好的写法

class Email:def send(self) -> None:"""Send this message."""message = Email()
message.send()

3.4 函数应该只有一层抽象

如果函数包含多于一层的抽象,那通常就是函数实现的功能太多了,应该把函数分解成多个函数来保证可重复使用以及更容易进行测试。

糟糕的写法

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.

好的写法

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 tokensdef parse(tokens: list) -> list:syntax_tree = []for token in tokens:# Append the parsed token to the syntax tree.return syntax_tree

3.5 不要将标志作为函数参数

标志表示函数实现的功能不只是一个,但函数应该仅做一件事情,所以如果需要标志,就将多写一个函数吧。

糟糕的写法

from pathlib import Pathdef create_file(name: str, temp: bool) -> None:if temp:Path('./temp/' + name).touch()else:Path(name).touch()

好的写法

from pathlib import Pathdef create_file(name: str) -> None:Path(name).touch()def create_temp_file(name: str) -> None:Path('./temp/' + name).touch()

3.6 避免函数的副作用

函数产生副作用的情况是在它做的事情不只是输入一个数值,返回其他数值这样一件事情。比如说,副作用可能是将数据写入文件,修改全局变量,或者意外的将你所有的钱都写给一个陌生人。

不过,有时候必须在程序中产生副作用--比如,刚刚提到的例子,必须写入数据到文件中。这种情况下,你应该尽量集中和指示产生这些副作用的函数,比如说,保证只有一个函数会产生将数据写到某个特定文件中,而不是多个函数或者类都可以做到。

这条建议的主要意思是避免常见的陷阱,比如分析对象之间的状态的时候没有任何结构,使用可以被任何数据修改的可修改数据类型,或者使用类的实例对象,不集中副作用影响等等。如果你可以做到这条建议,你会比很多开发者都开心。

糟糕的写法

# 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!# 这里采用了全局变量,并且函数的作用就是修改全局变量,其副作用就是修改了全局变量,# 第二次调用函数的结果就会和第一次调用不一样了。global namename = 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?

好的写法

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']

另一个好的写法

from dataclasses import dataclass@dataclass
class Person:name: str@propertydef 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']

总结

原文的目录实际还有三个部分:

  • 对象和数据结构

    • 单一职责原则(Single Responsibility Principle, SRP)

    • 开放封闭原则(Open/Closed principle,OCP)

    • 里氏替换原则(Liskov Substitution Principle ,LSP)

    • 接口隔离原则(Interface Segregation Principle ,ISP)

    • 依赖倒置原则(Dependency Inversion Principle ,DIP)

  • 不要重复

不过作者目前都还没有更新,所以想了解这部分内容的,建议可以直接阅读《代码整洁之道》对应的这部分内容了。

精选文章

  • 几个有趣的python技巧

  • 10个高效的pandas技巧

  • Mac 下安装配置 Python 开发环境

  • 只需几行代码,即可实现多线程和多进程操作

  • [Python技巧]如何加快循环操作和Numpy数组运算速度

欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

如果觉得不错,在看、转发就是对小编的一个支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408437.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MVC 3.0错误 HTTP 404您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。...

MVC3.0框架开发项目&#xff1a; 有时在程序运行的时候会出现“HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除&#xff0c;或其名称已更改&#xff0c;或暂时不可用。请检查以下 URL 并确保其拼写正确。”的错误提示。 在这里我们以运行时打开登录页面&#xff…

2020年1月总结

总第 114 篇文章&#xff0c;本文大约 1300 字&#xff0c;阅读大约需要 4 分钟这是 2020 年的第一篇月总结&#xff0c;总结的内容和周记差不多&#xff0c;也还是从这几个方面进行总结&#xff1a;工作学习阅读&写作2月计划工作这个月的工作时间大概是2周多一点&#xff…

python技巧(1)--如何转换itertools.chain对象为数组

总第 115 篇文章&#xff0c;本文大约 900 字&#xff0c;阅读大约需要 3 分钟之前做1月总结的时候说过希望每天或者每2天开始的更新一些学习笔记&#xff0c;这是开始的第一篇。这篇介绍的是如何把一个 itertools.chain 对象转换为一个数组。参考 stackoverflow 上的一个回答&…

python技巧(2)--碾平列表和列表去重

总第 116 篇文章&#xff0c;本文大约 1000 字&#xff0c;阅读大约需要 3 分钟今天介绍和列表相关的两个小技巧&#xff1a;碾平列表&#xff08;flatten list&#xff09;&#xff0c;也就是列表里的元素也带有列表的情况&#xff1b;列表去重&#xff0c;保留原始顺序和不保…

原来电脑并不需要重装系统才能恢复出厂设置,这个操作学起来!

前言 小伙伴们应该都知道手机上有恢复出厂设置的功能&#xff0c;如果想要把手机送给朋友或者卖给别人&#xff0c;就会先恢复出厂设置。 但换到Windows电脑上之后&#xff0c;如果出现同样的情况&#xff0c;就会第一时间想到重装系统。就好像Windows电脑上不存在恢复出厂设…

2020年周记(2/50)

总第 117 篇文章&#xff0c;本文大约 1400 字&#xff0c;阅读大约需要 5 分钟因为春节假期的延长&#xff0c;中间还是休息过长&#xff0c;少了两周的周记了&#xff0c;这是2020年的第二篇周记&#xff0c;内容还是这几个方面&#xff1a;工作学习阅读&写作其他下周计划…

一日一学:返回排序好的列表的索引顺序

总第 118 篇文章&#xff0c;本文大约 700 字&#xff0c;阅读大约需要 2 分钟今天介绍的是对列表排序后&#xff0c;返回排序好的索引顺序。问题描述&#xff1a;给定一个列表 [2, 3, 1, 4, 5] &#xff0c;怎么返回排序后的索引顺序&#xff0c;即 [2,0,1,3,4] ?解决方法&a…

2020年周记(3/50)

总第 119 篇文章&#xff0c;本文大约 700 字&#xff0c;阅读大约需要 2 分钟第三篇周记&#xff0c;依然在家办公的一周。工作学习阅读&写作其他下周计划ps. 昨天发文章后才发现有些地方需要修改&#xff0c;所以删除了发出的文章&#xff0c;修改后再发出来&#xff1…

一日一学--如何对数值型特征进行分桶

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 120 篇文章&#xff0c;本文大约 1200 字&#xff0c;阅读大约需要 3 分钟今天这篇文章主要是介绍在特征工程中&#xff0c;对数值型特征进行分桶操作的方法。简介分桶是离散化的常用…

常见的数据增强项目和论文介绍

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 121 篇文章&#xff0c;本文大约 1100 字&#xff0c;阅读大约需要 3 分钟在机器学习项目中&#xff0c;数据集对算法模型的性能是有很大的影响的&#xff0c;特别是现在深度学习&a…

PS网页设计教程XXIX——如何在PS中设计一个画廊布局

作为编码者&#xff0c;美工基础是偏弱的。我们可以参考一些成熟的网页PS教程&#xff0c;提高自身的设计能力。套用一句话&#xff0c;“熟读唐诗三百首&#xff0c;不会作诗也会吟”。 本系列的教程来源于网上的PS教程&#xff0c;都是国外的&#xff0c;全英文的。本人尝试翻…

RS(2)--从文本数据到用户画像

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 122 篇文章&#xff0c;本文大约 5100 字&#xff0c;阅读大约需要 15 分钟上一篇文章简单介绍了推荐系统的定义和应用&#xff0c;推荐系统第二篇&#xff0c;简单介绍用户画像的知…

深度学习算法简要综述(上)

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 123 篇文章&#xff0c;本文大约 2300 字&#xff0c;阅读大约需要 7 分钟原文&#xff1a;https://theaisummer.com/Deep-Learning-Algorithms/作者&#xff1a;Sergios Karagianna…

深度学习算法简要综述(下)

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 124 篇文章&#xff0c;本文大约 3731 字&#xff0c;阅读大约需要 10 分钟原文&#xff1a;https://theaisummer.com/Deep-Learning-Algorithms/作者&#xff1a;Sergios Karagianna…

关于AI你可能不知道的5件事情

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 125 篇文章&#xff0c;本文大约 1400 字&#xff0c;阅读大约需要 7 分钟原文&#xff1a;https://www.blog.google/technology/ai/five-things-a-to-z-ai/作者&#xff1a;Gina Nef…

10个实用的机器学习建议

点击上方“算法猿的成长“&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶”总第 126 篇文章&#xff0c;本文大约 2500 字&#xff0c;阅读大约需要 10 分钟原文&#xff1a;https://medium.com/modern-nlp/10-great-ml-practices-for-python-developers-b089eefc1…