好的变量和注释并非为计算机而写,而是为每个阅读代码的人而写。变量与注释是表达作者思想的基础,他们对代码质量的贡献母庸质疑。
把一个可迭代对象的所有成员,一次性的赋值给多个变量的过程就是变量解包。
# 变量解包
username = ['zhangsan', '18']
name, age = username
print('name:{}, age:{} '.format(name, age))
# 嵌套类型变量解包
username = [1, ['zhangsan', 18]]
number, (name, age) = username
print('number:{}, name:{}, age:{}'.format(number, name, age))
# 匹配模式解包
data = ['zhangsan', 'banana', 'apple', 'orange', 18]
name, *fruits, score = data
print('name:{}, fruits:{}, score:{}'.format(name, fruits, score))
# 切片解包
data = ['zhangsan', 'banana', 'apple', 'orange', 18]
name, fruits, score = data[0], data[1:-1], data[-1]
print('name:{}, fruits:{}, score:{}'.format(name, fruits, score))
# 变量解包用到for循环
for name, age in [('zhangsan', 15), ('lisi', 18)]:
print('name:{}, age:{}'.format(name, age))
# 单下划线变量名
username = ['zhangsan', 19]
name, _ = username
print(name, _)
python给变量注明类型,与java变量类型不同,python的变量类型只是一种提示功能,不提供任何校验功能。
因此传入的变量类型与校验类型不一致也不会报错。
变量注明类型语法非常简单,在变量名称后面用冒号分隔表名类型即可。
# list表示参数为列表类型,int表示里面的成员是整形
def remove_invalid(item: list[int]):
print(item)
# 传入符合变量类型参数
remove_invalid([1, 2, 3])
# 传入不符合变量类型参数,不影响函数执行结果。
remove_invalid(1)
# 类型注解使用demo
class Duck:
def __init__(self, color:str):
self.color = color
# 为quack方法注明返回值类型为None
def quack(self) -> None:
print(f"Hi, I'm a {self.color} duck")
# -> List[Duck]:用typing模块的List对象为函数返回值标注具体类型
def create_random_ducks(number:int) -> List[Duck]:
# 为变量加上类型声明
ducks: List[Duck] = []
for _ in number:
color = random.choice(['yellow', 'white', 'gray'])
ducks.append(Duck(color=color))
return ducks
ducks = create_random_ducks((1,2,3))
for duck in ducks:
duck.quack()
给变量起名主要有两种流派:一是通过大小写界定单词的驼峰命名,例如Java语言。二是通过下划线连接的蛇形命名,例如python语言。
PEP8原名《Python Enhacement Proposal #8》译为《8 号 Python 增强规范》为代码编写风格提供了指南,变量命名部分规范如下。
写作过程中一项重要的工作就是为句子斟酌恰当的词语,不同的词语描述性的强弱是不同的。比如”冬天傲骨的梅花“ 就比 ”花“ 描述性要强。为变量命名和词语一样,同样有描述性强弱之分。
下面是描述性强弱不同的变量,对比可以感受到描述性强的变量名称使代码更易读。
# 描述性弱的变量名称:看不出它在描述什么
vlaue = process(s.strip())
# 描述性强的变量名称:从用户输入参数中解析出用户名称,并剔除参数中的空格。
username = extract_username(input_string.strip())
假如一个特别长的重复出现,读者不会觉得它精确,反而是啰嗦难读。在保证描述性清晰前提下,尽量让名字简短易读,通常控制在4个单词内。
虽然python的变量不需要声明类型,但是为了提升可读性,我们可以为变量注明类型。
除了为变量注明类型外,还有约定俗称的变量名称与类型建立匹配关系,下面是一些变量名称和类型匹配的例子。
变量名 | 含义 | 说明 |
---|---|---|
is_superuser | 是否是超级用户 | is 表示是或不是 |
has_errors | 有没有错 | hans 表示有或没有 |
allow_empty | 是否允许空值 | allow表示是否允许 |
在变量命名中有一类名称比较特别,只有一两个字母,通常他们分为两类,一类是大家约定俗称的短名字,另一类是起别名。
约定俗称常用名称
长名称起别名
is_not_normal as l
注释不会影响代码的行为,它会影响代码的可读性。
python的注释分为两种,一种是代码内注释,一种是函数、类的注释也称为接口注释。
行内注释
# 使用strip()去掉空格的好处:
# 1.数据库保存数据时占用空间更小
# 2.不必因为用户多打了空格而要求用户重新输入。
username = extract_username(input_string.strip())
接口注释
class Person:
# 使用三个单引号或三个双引号就是接口注释。
'''人
:param name: 姓名
: param age: 年龄
: param favrite_color: 最喜欢的颜色
'''
def __init__(self, name, age, favrite_color):
self.name = name
self.age = age
self.favrite_color = color
在编程中会用注释屏蔽代码,如果这些代码不需要了可以直接删掉,如果需要用到这些代码可以从Git仓库中找到。临时注释掉的大段代码,对于阅读代码的人来说是一种干扰,没有任何意义。
# 调用strip()去掉空格
input_string = input_string.strip()
上面这样的注释完全是冗余的,因为读者从代码本身就能读到注释里的信息。好的注释应该是这样
# 如果将带有空格的参数传递到后端处理,可能会造成服务奔溃
# 因此使用strip()去掉收尾空格
input_string = input_string.strip()
注释作为代码之外的说明性文字,应该尽量提供那些读者无法从代码里读出的信息,描述代码为什么要这么做,而不是简单复述代码本身。
除了为什么的解释性注释外,还有一种注释也很常见:指引性注释
这种注释不复述代码,而是简明扼要概括代码功能,起到”代码导读“作用。
指引性注释并不提供代码里读不到东西——假如没有注释,耐心读完所有代码也能知道代码做了什么。指引性注释就是降低代码认知的成本,让我们更容易理解代码的意图。
指引性注释示例
# 初始化访问服务的client对象
token = token_service.get_token()
service_client = ServiceClient(token = token)
service_client.ready()
在编写接口注释时,经常会看到下面的情况。
def resize_image(image, size):
'''将图片缩放到指定尺寸,并返回新的图片。
该函数将使用pilot模块读取文件对象,然后调用.resize()方法将其缩放到指定尺寸。
但由于pilot模块自身限制,这个函数不能很好的处理过大文件,当文件超过5MB时,resize()方法的性能
就会急剧下降,因此超过5MB文件,使用resize_big_image()替代,后者基于Pillow模块开发,
很好的解决了内存分配问题,确保更好的性能。
:param image: 图片文件对象
:param size: 包含宽高的元组(width, height)
:return: 新图片对象
'''
这段接口注释最主要的问题在于过多的阐述了函数实现的细节,提供了太多其他人并不关系的内容。
接口注释主要是给函数或类的使用者看的,它存在的最主要价值,是让人们不用逐行阅读函数代码,就能从注释中知道该如何使用这个函数,以及使用时需要注意事项。
在编写接口注释时我们应该站在函数设计者的角度,着重描述函数的功能,参数等。而函数自身实现细节无需放在接口注释中,放到函数内部代码注释即可。
下面是改进后的接口注释
def resize_image(image, size):
'''将图片缩放到指定尺寸,并返回新的图片。
注意:当文件超过5MB时,请使用resize_big_image()
:param image: 图片文件对象
:param size: 包含宽高的元组(width, height)
:return: 新图片对象
'''
在使用变量时,需要保证他在两个方面的一致性:名字一致性与类型一致性。
def():
#user 是一个Dict类型
user = {'data':['zhangsan','wangwu']}
# user 这个名字好用,重复使用它,把它变成List
user = []
定义变量应该把它们放到每段 “各司其职” 的代码头部,缩短变量从初始化到使用的 “距离”。当读者阅读代码时,可以更容易理解代码逻辑,而不是来回翻阅代码查看变量是在什么时候定义的,在什么时候使用的。
当代码中遇到复杂的逻辑时,判断条件就会变得复杂,这时候可以采用临时变量简化判断条件的复杂度,提升代码的可阅读性。
举例:定义临时变量提升代码阅读性
# 为所有女性或者级别大于3的活跃用户发放1000个金币
if user.is_active and (user.sex == 'female' or user.level > 3):
user.add_coins(1000)
# 上面业务逻辑使判断条件变得复杂,阅读起来有些费劲,这时候我们可以把后面复杂的表达式赋值给一个临时变量,代码阅读性就会有所提升,下面是例子。
user_is_eligible = user.is_active and (user.sex == 'female' or user.level > 3)
if user_is_eligible:
user.add_coins(1000)
'''
在新的代码中,“计算用户合规的表达式” 和 “判断合规发送金币的条件分支” 这两段代码不再揉捏在一起,
而是添加了一个可读性强的变量作为缓冲,不论是代码的可读性还是维护性都因为这个变量增强了。
'''
函数越长,用到的变量就越多。但是人脑记忆是有限的,太多的变量会使代码变得难读。如果函数内变量太多,通常是函数承担了太多的职责,把复杂的函数拆分为多个小涵数,代码的整体复杂度会降低。
在编写代码时,我们会下意识的定义很多变量,好为未来调整代码做准备,但是你所想的未来也许永远不会来。
下面是一个定义临时变量过多的例子,我们对它做了优化。
# 定义过多的临时变量会让阅读代码的读者觉得啰嗦,阅读性不好。
def get_best_trip_by_user_id(user_id):
# 下面的值可能会修改,二次使用,先把它定义成变量吧
user = get_user(user_id)
trip = get_best_trip(user_id)
result = {
'user': user,
'trip': trip
}
return result
# 对上面的代码进行优化,去掉多余的临时变量。
'''
这样的代码就像删掉啰嗦的句子,变得更精炼,更易读。
'''
def get_best_trip_by_user_id(user_id):
return {
'user': get_user(user_id),
'trip': get_best_trip(user_id)
}
在写代码的时候,我们可以适当地在代码中插入空行,把代码按不同逻辑块分隔开,这样做能有效提升代码的可读性。
在写代码时有一个好习惯值得推荐:先写注释,后写代码。
比如在开发代码过程中写的最多的就是函数,封装业务逻辑也是函数,下面就来聊聊函数开发的好习惯。
每个函数的名称与接口注释,其实是一种比函数内部更为抽象的东西。你需要在函数名和短短几行注释里,把函数内代码所做的事情,高度浓缩的表达清楚。
正因为如此,接口注释其实可以当成一种协助你设计函数的前置工具。这个工具用法很简单:假如你没法通过几行注释把函数的职责描述清楚,那么整个函数的合理性就该打个问号。
举例:
你在写一个函数process_user(),编写函数注释时,你发现在写了好几行文字后,仍然没法把函数的职责描述清楚,因为它可以同时完成好多件不同的事情。这时你就该意识到,process_user()函数承担了太多的职责,解决办法就是直接删掉它,设计更多单一职责的子函数来替代。
先写注释的另一个好处是:不会漏掉任何应该写的注释。
为什么大家会漏掉注释?我的一个猜测是:程序员在编写函数时,总是跳过接口注释直接开始写代码。而当写完代码,
实现函数的所有功能后,他就对这个函数失去了兴趣。这是他最不愿意做的事,就是回过头去补写函数的注释,
级别写了也只是草草对付了事。
如果遵守 “先写注释,后写代码” 的习惯,我们就可以完全避免上面的问题。养成这个好习惯 ,其实很简单:在写出一句有说服力的接口注释前,先别写任何函数代码。