Python 工匠:案例、技巧与工程实践
朱雷
蓝鲸工具 PaaS 平台负责人
502 人已学习
立即订阅
登录后,你可以任选4讲全文学习
推荐试读
换一换
时长 21:38
免费
课程目录
已完结/共 20 讲
时长 21:38
时长 33:20
时长 24:04
时长 29:57
时长 00:56
时长 00:39
时长 00:36
Python 工匠:案例、技巧与工程实践
15
15
1.0x
00:00/44:23
登录|注册

第 1 章 变量与注释

讲述:Alloy大小:10.16M时长:44:23
编程是一个通过代码来表达思想的过程。听上去挺神秘,但其实我们早就做过类似的事情——当年在小学课堂上写出第一篇 500 字的作文,同样也是在表达思想,只是二者方式不同,作文用的是词语和句子,而编程用的是代码。
但代码与作文之间也有相通之处,代码里也有许多“词语”和“句子”。大部分的变量名是词语,而大多数注释本身就是句子。当我们看到一段代码时,最先注意到的,不是代码有几层循环,用了什么模式,而是变量与注释,因为它们是代码里最接近自然语言的东西,最容易被大脑消化、理解。
正因如此,如果作者在编写变量和注释时含糊不清、语焉不详,其他人将很难搞清楚代码的真实意图。就拿下面这行代码来说:
# 去掉 s 两边的空格,再处理
value = process(s.strip())
你能告诉我这段代码在做什么吗?当我看到它时,是这么想的:
s 上调用 strip(),所以 s 可能是一个字符串?不过为什么要去掉两边的空格呢?
process(...),顾名思义,“处理”了一下 s,但具体是什么处理呢?
处理结果赋值给了 valuevalue 代表“值”,但“值”又是什么?
开头的注释就更别提了,它说的就是代码本身,对理解代码没有丝毫帮助。
最后的结论是:“将一个可能是字符串的东西两端的空格去掉,然后处理一下,最后赋值给某个不明物体。”我只能理解到这种程度了。
但同样是这段代码,如果我稍微调整一下变量的名字,加上一点点注释,就会变得截然不同:
# 用户输入可能会有空格,使用 strip() 去掉空格
username = extract_username(input_string.strip())
新代码读上去是什么感觉?是不是代码意图变得容易理解多了?这就是变量与注释的魔力。
从计算机的角度来看,变量(variable)是用来从内存找到某个东西的标记。它叫“阿猫”“阿狗”还是“张三”“李四”,都无所谓。注释同样如此,计算机一点儿都不关心你的注释写得是否通顺,用词是否准确,因为它在执行代码时会忽略所有的注释。
但正是这些对计算机来说无关痛痒的东西,直接决定了人们对代码的“第一印象”。好的变量和注释并非为计算机而写,而是为每个阅读代码的人而写(当然也包括你自己)。变量与注释是作者表达思想的基础,是读者理解代码的第一道门,它们对代码质量的贡献毋庸置疑。
本章将对 Python 里的变量和注释做简单介绍,我会分享一些常用的变量命名原则,介绍编写代码注释的几种方式。在编程建议部分,我会列举一些与变量和注释有关的好习惯。
我们从变量和注释开始,学习如何写出给人留下美好“第一印象”的好代码吧!

1.1 基础知识

本节将介绍一些与变量和注释相关的基础知识。

1.1.1 变量常见用法

在 Python 里,定义一个变量特别简单:
>>> author = 'piglei'
>>> print('Hello, {}!'.format(author))
Hello, piglei!
因为 Python 是一门动态类型的语言,所以我们无须预先声明变量类型,直接对变量赋值即可。
你也可以在一行语句里同时操作多个变量,比如调换两个变量所指向的值:
>>> author, reader = 'piglei', 'raymond'
>>> author, reader = reader, author ➊
>>> author
'raymond'
❶ 交换两个变量
变量解包
变量解包(unpacking)是 Python 里的一种特殊赋值操作,允许我们把一个可迭代对象(比如列表)的所有成员,一次性赋值给多个变量:
>>> usernames = ['piglei', 'raymond']
# 注意:左侧变量的个数必须和待展开的列表长度相等,否则会报错
>>> author, reader = usernames
>>> author
'piglei'
假如在赋值语句左侧添加小括号 (...),甚至可以一次展开多层嵌套数据:
>>> attrs = [1, ['piglei', 100]]
>>> user_id, (username, score) = attrs
>>> user_id
1
>>> username
'piglei'
除了上面的普通解包外,Python 还支持更灵活的动态解包语法。只要用星号表达式(*variables)作为变量名,它便会贪婪 地捕获多个值对象,并将捕获到的内容作为列表赋值给 variables
比如,下面 data 列表里的数据就分为三段:头为用户,尾为分数,中间的都是水果名称。通过把 *fruits 设置为中间的解包变量,我们就能一次性解包所有变量——fruits 会捕获 data 去头去尾后的所有成员:
>>> data = ['piglei', 'apple', 'orange', 'banana', 100]
>>> username, *fruits, score = data
>>> username
'piglei'
>>> fruits
['apple', 'orange', 'banana']
>>> score
100
和常规的切片赋值语句比起来,动态解包语法要直观许多:
# 1. 动态解包
>>> username, *fruits, score = data
# 2. 切片赋值
>>> username, fruits, score = data[0], data[1:-1], data[-1]
# 两种变量赋值方式完全等价
上面的变量解包操作也可以在任何循环语句里使用:
>>> for username, score in [('piglei', 100), ('raymond', 60)]:
... print(username)
...
piglei
raymond
单下划线变量名 _
在常用的诸多变量名中,单下划线 _ 是比较特殊的一个。它常作为一个无意义的占位符出现在赋值语句中。_ 这个名字本身没什么特别之处,这算是大家约定俗成的一种用法。
举个例子,假如你想在解包赋值时忽略某些变量,就可以使用 _ 作为变量名:
# 忽略展开时的第二个变量
>>> author, _ = usernames

# 忽略第一个和最后一个变量之间的所有变量
>>> username, *_, score = data
而在 Python 交互式命令行(直接执行 python 命令进入的交互环境)里,_ 变量还有一层特殊含义——默认保存我们输入的上个表达式的返回值:
>>> 'foo'.upper()
'FOO'
>>> print(_) ➊
FOO
❶ 此时的 _ 变量保存着上一个 .upper() 表达式的结果

1.1.2 给变量注明类型

前面说过,Python 是动态类型语言,使用变量时不需要做任何类型声明。在我看来,这是 Python 相比其他语言的一个重要优势:它减少了我们的心智负担,让写代码变得更容易。尤其对于许多编程新手来说,“不用声明类型”无疑会让学 Python 这件事变得简单很多。
但任何事物都有其两面性。动态类型所带来的缺点是代码的可读性会因此大打折扣。
试着读读下面这段代码:
def remove_invalid(items):
"""剔除 items 里面无效的元素"""
... ...
你能告诉我,函数接收的 items 参数是什么类型吗?是一个装满数字的列表,还是一个装满字符串的集合?只看上面这点儿代码,我们根本无从得知。
为了解决动态类型带来的可读性问题,最常见的办法就是在函数文档(docstring)里做文章。我们可以把每个函数参数的类型与说明全都写在函数文档里。
下面是增加了 Python 官方推荐的 Sphinx 格式文档后的效果:
def remove_invalid(items):
"""剔除 items 里面无效的元素
:param items: 待剔除对象
:type items: 包含整数的列表,[int, ...]
"""
在上面的函数文档里,我用 :type items: 注明了 items 是个整型列表。任何人只要读到这份文档,马上就能知道参数类型,不用再猜来猜去了。
当然,标注类型的办法肯定不止上面这一种。在 Python 3.5 版本 以后,你可以用类型注解功能来直接注明变量类型。相比编写 Sphinx 格式文档,我其实更推荐使用类型注解,因为它是 Python 的内置功能,而且正在变得越来越流行。
要使用类型注解,只需在变量后添加类型,并用冒号隔开即可,比如 func(value: str) 表示函数的 value 参数为字符串类型。
下面是给 remove_invalid() 函数添加类型注解后的样子:
from typing import List
def remove_invalid(items: List[int]): ➊
"""剔除 items 里面无效的元素"""
... ...
List 表示参数为列表类型,[int] 表示里面的成员是整型
 “类型注解”只是一种有关类型的注释,不提供任何校验功能。要校验类型正确性,需要使用其他静态类型检查工具(如 mypy 等)。
平心而论,不管是编写 Sphinx 格式文档,还是添加类型注解,都会增加编写代码的工作量。同样一段代码,标注变量类型比不标注一定要花费更多时间。
但从我的经验来看,这些额外的时间投入,会带来非常丰厚的回报:
代码更易读,读代码时可以直接看到变量类型;
大部分的现代化 IDE 会读取类型注解信息,提供更智能的输入提示;
类型注解配合 mypy 等静态类型检查工具,能提升代码正确性(13.1.5 节)。
因此,我强烈建议在多人参与的中大型 Python 项目里,至少使用一种类型注解方案——Sphinx 格式文档或官方类型注解都行。能直接看到变量类型的代码,总是会让人更安心。
 在 10.1.1 节中,你会看到更详细的“类型注解”功能说明,以及更多启用了类型注解的代码。

1.1.3 变量命名原则

如果要从变量着手来破坏代码质量,办法多到数也数不清,比如定义了变量但是不用,或者定义 100 个全局变量,等等。但如果要在这些办法中选出破坏力最强的那个,非“给变量起个坏名字”莫属。
下面这段代码就是一个充斥着坏名字的“集大成”者。试着读读,看看你会有什么感受:
data1 = process(data)
if data1 > data2:
data2 = process_new(data1)
data3 = data2
return process_v2(data3)
怎么样,是不是挠破头都看不懂它在做什么?坏名字对代码质量的破坏力可见一斑。
那么问题来了,既然大家都知道上面这样的代码不好,为何在程序世界里,每天都有类似的代码被写出来呢?我猜这是因为给变量起个好名字真的很难。在计算机科学领域,有一句广为流传的格言(俏皮话):
计算机科学领域只有两件难事:缓存失效和命名。
——Phil Karlton
这句话里虽然一半严肃一半玩笑,但“命名”有时真的会难到让人抓狂。我常常呆坐在显示器前,抓耳挠腮好几分钟,就是没法给变量想出一个合适的名字。
要给变量起个好名字,主要靠的是经验,有时还需加上一丁点儿灵感,但更重要的是遵守一些基本原则。下面就是我总结的几条变量命名的基本原则。
遵循 PEP 8 原则
给变量起名主要有两种流派:一是通过大小写界定单词的驼峰命名派 CamelCase,二是通过下划线连接的蛇形命名派 snake_case。这两种流派没有明显的优劣之分,似乎与个人喜好有关。
为了让不同开发者写出的代码风格尽量保持统一,Python 制定了官方的编码风格指南:PEP 8。这份风格指南里有许多详细的风格建议,比如应该用 4 个空格缩进,每行不超过 79 个字符,等等。其中,当然也包含变量的命名规范:
对于普通变量,使用蛇形命名法,比如 max_value
对于常量,采用全大写字母,使用下划线连接,比如 MAX_VALUE
如果变量标记为“仅内部使用”,为其增加下划线前缀,比如 _local_var
当名字与 Python 关键字冲突时,在变量末尾追加下划线,比如 class_
除变量名以外,PEP 8 中还有许多其他命名规范,比如类名应该使用驼峰风格(FooClass)、函数应该使用蛇形风格(bar_function),等等。给变量起名的第一条原则,就是一定要在格式上遵循以上规范。
 PEP 8 是 Python 编码风格的事实标准。“代码符合 PEP 8 规范”应该作为对 Python 程序员的基本要求之一。假如一份代码的风格与 PEP 8 大相径庭,就基本不必继续讨论它优雅与否了。
描述性要强
写作过程中的一项重要工作,就是为句子斟酌恰当的词语。不同词语的描述性强弱不同,比如“冬天的梅花”就比“花”的描述性更强。而变量名和普通词语一样,同样有描述性强弱之分,假如代码大量使用描述性弱的变量名,读者就很难理解代码的含义。
本章开头的那两段代码可以很好地解释这个问题:
# 描述性弱的名字:看不懂在做什么
value = process(s.strip())

# 描述性强的名字:尝试从用户输入里解析出一个用户名
username = extract_username(input_string.strip())
所以,在可接受的长度范围内,变量名所指向的内容描述得越精确越好。表 1-1 是一些具体的例子。
表 1-1 描述性弱和描述性强的变量名示例
描述性弱的名字描述性强的名字说明
datafile_chunksdata 泛指所有的“数据”,但如果数据是来自文件的碎块,我们可以直接叫它 file_chunks
temppending_idtemp 泛指所有“临时”的东西,但其实它存放的是一个等待处理的数据 ID,因此直接叫它 pending_id 更好
result(s)active_member(s)result(s) 经常用来表示函数执行的“结果”,但如果结果就是指“活跃会员”,那还是直接叫它 active_member(s)
看到表 1-1 中的示例,你可能会想:“也就是说左边的名字都不好,永远别用它们?”
当然不是这样。判断一个名字是否合适,一定要结合它所在的场景,脱离场景谈名字是片面的,是没有意义的。因此,在“说明”这一列中,我们强调了这个判断所适用的场景。
而在其他一些场景下,这里“描述性弱”的名字也可能是好名字,比如把一个数学公式的计算结果叫作 value,就非常恰当。
要尽量短
刚刚说到,变量名的描述性要尽量强,但描述性越强,通常名字也就越长(不信再看看表 1-1,第二列的名字就比第一列长)。假如不加思考地实践“描述性原则”,那你的代码里可能会充斥着 how_many_points_needed_for_user_level3 这种名字,简直像条蛇一样长:
def upgrade_to_level3(user):
"""如果积分满足要求,将用户升级到级别 3"""
how_many_points_needed_for_user_level3 = get_level_points(3)
if user.points >= how_many_points_needed_for_user_level3:
upgrade(user)
else:
raise Error('积分不够,必须要 {} 分'.format(how_many_points_needed_for_user_level3))
假如一个特别长的名字重复出现,读者不会认为它足够精确,反而会觉得啰唆难读。既然如此,怎么才能在保证描述性的前提下,让名字尽量简短易读呢?
我认为个中诀窍在于:为变量命名要结合代码情境和上下文。比如在上面的代码里,upgrade_to_level3(user) 函数已经通过自己的名称、文档表明了其目的,那在函数内部,我们完全可以把 how_many_points_needed_for_user_level3 直接删减成 level3_points
即使没用特别长的名字,相信读代码的人也肯定能明白,这里的 level3_points 指的就是“升到级别 3 所需要的积分”,而不是其他含义。
要匹配类型
虽然变量无须声明类型,但为了提升可读性,我们可以用类型注解语法给其加上类型。不过现实很残酷,到目前为止,大部分 Python 项目没有类型注解 ,因此当你看到一个变量时,除了通过上下文猜测,没法轻易知道它是什么类型。
但是,对于变量名和类型的关系,通常会有一些“直觉上”的约定。如果在起名时遵守这些约定,就可以建立变量名和类型间的匹配关系,让代码更容易理解。
匹配布尔值类型的变量名
布尔值(bool)是一种很简单的类型,它只有两个可能的值:“是”(True)或“不是”(False)。因此,给布尔值变量起名有一个原则:一定要让读到变量的人觉得它只有“肯定”和“否定”两种可能。举例来说,ishas 这些非黑即白的词就很适合用来修饰这类名字。
表 1-2 中给出了一些更详细的例子。
表 1-2 布尔值变量名示例
变量名含义说明
is_superuser是否是超级用户是 / 不是
has_errors有没有错误有 / 没有
allow_empty是否允许空值允许 / 不允许
匹配 int/float 类型的变量名
当人们看到和数字有关的名字时,自然就会认定它们是 intfloat 类型。这些名字可简单分为以下几种常见类型:
释义为数字的所有单词,比如 port(端口号)、age(年龄)、radius(半径)等;
使用以 _id 结尾的单词,比如 user_idhost_id
使用以 length/count 开头或者结尾的单词,比如 length_of_usernamemax_lengthusers_count
 最好别拿一个名词的复数形式来作为 int 类型的变量名,比如 applestrips 等,因为这类名字容易与那些装着 AppleTrip 的普通容器对象(List[Apple]List[Trip])混淆,建议用 number_of_applestrips_count 这类复合词来作为 int 类型的名字。
匹配其他类型的变量名
至于剩下的字符串str)、列表list)、字典dict)等其他值类型,我们很难归纳出一个“由名字猜测类型”的统一公式。拿 headers 这个名字来说,它既可能是一个装满头信息的列表(List[Header]),也可能是一个包含头信息的字典(Dict[str, Header])。
对于这些值类型,强烈建议使用我们在 1.1.2 节中提到的方案,在代码中明确标注它们的类型详情。
超短命名
在众多变量名里,有一类非常特别,那就是只有一两个字母的短名字。这些短名字一般可分为两类,一类是那些大家约定俗成的短名字,比如:
数组索引三剑客 ijk
某个整数 n
某个字符串 s
某个异常 e
文件对象 fp
我并不反对使用这类短名字,我自己也经常用,因为它们写起来的确很方便。但如果条件允许,建议尽量用更精确的名字替代。比如,在表示用户输入的字符串时,用 input_str 替代 s 会更明确一些。
另一类短名字,则是对一些其他常用名的缩写。比如,在使用 Django 框架做国际化内容翻译时,常常会用到 gettext 方法。为了方便,我们常把 gettext 缩写成 _
from django.utils.translation import gettext as _

print(_('待翻译文字'))
如果你的项目中有一些长名字反复出现,可以效仿上面的方式,为它们设置一些短名字作为别名。这样可以让代码变得更紧凑、更易读。但同一个项目内的超短缩写不宜太多,否则会适得其反。
其他技巧
除了上面这些规则外,下面再分享几个给变量命名的小技巧:
在同一段代码内,不要出现多个相似的变量名,比如同时使用 usersusers1users3 这种序列;
可以尝试换词来简化复合变量名,比如用 is_special 来代替 is_not_normal
如果你苦思冥想都想不出一个合适的名字,请打开 GitHub,到其他人的开源项目里找找灵感吧!

1.1.4 注释基础知识

注释(comment)是代码非常重要的组成部分。通常来说,注释泛指那些不影响代码实际行为的文字,它们主要起额外说明作用。
Python 里的注释主要分为两种,一种是最常见的代码内注释,通过在行首输入 # 号来表示:
# 用户输入可能会有空格,使用 strip 去掉空格
username = extract_username(input_string.strip())
当注释包含多行内容时,同样使用 # 号:
# 使用 strip() 去掉空格的好处:
# 1. 数据库保存时占用空间更小
# 2. 不必因为用户多打了一个空格而要求用户重新输入
username = extract_username(input_string.strip())
除使用 # 的注释外,另一种注释则是我们前面看到过的函数(类)文档(docstring),这些文档也称接口注释(interface comment)。
class Person:
"""人
:param name: 姓名
:param age: 年龄
:param favorite_color: 最喜欢的颜色
"""
def __init__(self, name, age, favorite_color):
self.name = name
self.age = age
self.favorite_color = favorite_color
接口注释有好几种流行的风格,比如 Sphinx 文档风格、Google 风格等,其中 Sphinx 文档风格目前应用得最为广泛。上面的 Person 类的接口注释就属于 Sphinx 文档风格。
虽然注释一般不影响代码的执行效果,却会极大地影响代码的可读性。在编写注释时,编程新手们常常会犯同类型的错误,以下是我整理的最常见的 3 种。
用注释屏蔽代码
有时,人们会把注释当作临时屏蔽代码的工具。当某些代码暂时不需要执行时,就把它们都注释了,未来需要时再解除注释。
# 源码里有大段大段暂时不需要执行的代码
# trip = get_trip(request)
# trip.refresh()
# ... ...
其实根本没必要这么做。这些被临时注释掉的大段内容,对于阅读代码的人来说是一种干扰,没有任何意义。对于不再需要的代码,我们应该直接把它们删掉,而不是注释掉。如果未来有人真的需要用到这些旧代码,他直接去 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()

# 调用服务获取数据,然后进行过滤
data = service_client.fetch_full_data()
for item in data:
if item.value > SOME_VALUE:
...
指引性注释并不提供代码里读不到的东西——假如没有注释,耐心读完所有代码,你也能知道代码做了什么事儿。指引性注释的主要作用是降低代码的认知成本,让我们能更容易理解代码的意图。
在编写指引性注释时,有一点需要注意,那就是你得判断何时该写注释,何时该将代码提炼为独立的函数(或方法)。比如上面的代码,其实可以通过抽象两个新函数改成下面这样:
service_client = make_client()
data = fetch_and_filter(service_client)
这么改以后,代码里的指引性注释就可以删掉了,因为有意义的函数名已经达到了概括和指引的作用。
正是因为如此,一部分人认为:只要代码里有指引性注释,就说明代码的可读性不高,无法“自说明”,一定得抽象新函数把其优化成第二种样子。
但我倒认为事情没那么绝对。无论代码写得多好,多么“自说明”,同读代码相比,读注释通常让人觉得更轻松。注释会让人们觉得亲切(尤其当注释是中文时),高质量的指引性注释确实会让代码更易读。有时抽象一个新函数,不见得就一定比一行注释加上几行代码更好。
弄错接口注释的受众
在编写接口注释时,人们有时会写出下面这样的内容:
def resize_image(image, size):
"""将图片缩放到指定尺寸,并返回新的图片。

该函数将使用 Pilot 模块读取文件对象,然后调用 .resize() 方法将其缩放到指定尺寸。

但由于 Pilot 模块自身限制,这个函数不能很好地处理过大的文件,当文件大小超过 5MB 时,
resize() 方法的性能就会因为内存分配问题急剧下降,详见 Pilot 模块的Issue #007。因此,
对于超过 5MB 的图片文件,请使用 resize_big_image() 替代,后者基于 Pillow 模块开发,
很好地解决了内存分配问题,确保性能更好了。

:param image: 图片文件对象
:param size: 包含宽高的元组:(width, height)
:return: 新图片对象
"""
上面这段注释虽然有些夸张,但像它一样的注释在项目中其实并不少见。这段接口注释最主要的问题在于过多阐述了函数的实现细节,提供了太多其他人并不关心的内容。
接口文档主要是给函数(或类)的使用者看的,它最主要的存在价值,是让人们不用逐行阅读函数代码,也能很快通过文档知道该如何使用这个函数,以及在使用时有什么注意事项。
在编写接口文档时,我们应该站在函数设计者的角度,着重描述函数的功能、参数说明等。而函数自身的实现细节,比如调用了哪个第三方模块、为何有性能问题等,无须放在接口文档里。
对于上面的 resize_image() 函数来说,文档里提供以下内容就足够了:
def resize_image(image, size):
"""将图片缩放到指定尺寸,并返回新的图片。

注意:当文件超过 5MB 时,请使用 resize_big_image()

:param image: 图片文件对象
:param size: 包含宽高的元组:(width, height)
:return: 新图片对象
"""
至于那些使用了 Pilot 模块、为何有内存问题的细节说明,全都可以丢进函数内部的代码注释里。

1.2 案例故事

下面是 Python 程序员小 R 去其他公司面试的故事。
在本书剩下的案例故事里,你还会多次看到“小 R”的身影。
小 R 这个名字来自作者的英文名(Raymond)的首字母缩写。随着故事的不同,小 R 有时是一位 Python 初学者,有时又是一名有多年经验的 Python 老手。但无论扮演什么角色,他总会在每个故事里获得新的成长。
下面,我们看一看本书的第一个案例故事。

奇怪的冒泡排序算法

上午 10 点,在 T 公司的会议室里,小 R 正在参加一场他准备了好几天的技术面试。
整体来说,他在这场面试中的表现还不错。无论坐在小 R 对面的面试官提出什么问题,他都能侃侃而谈、对答如流。从单体应用聊到微服务,从虚拟机聊到云计算,每一块小 R 都说得滴水不漏。就在他认为自己胜券在握,可以通过这家自己憧憬已久的公司面试时,对面的面试官突然说道:“技术问题我问得差不多了。最后有一道编程题,希望你可以做一下。”
说完,面试官低头从包里拿出了一台笔记本电脑,递给了小 R。小 R 有些紧张地接过电脑,发现屏幕上是一道算法题。
题目 冒泡排序算法
请用 Python 语言实现冒泡排序算法,把较大的数字放在后面。注意:默认所有的偶数都比奇数大。
>>> numbers = [23, 32, 1, 3, 4, 19, 20, 2, 4]
>>> magic_bubble_sort(numbers)
[1, 3, 19, 23, 2, 4, 4, 20, 32]
“冒泡排序,这不是所有排序算法里最简单的一种吗?虽然加了一点儿变化,但看起来没有什么难度啊。”小 R 一边在心里这么想着,一边打开编辑器开始写代码。
五分钟后,他把笔记本电脑递给面试官并说道:“写完了!”
代码清单 1-1 就是他写的代码。
代码清单 1-1 小 R 写的冒泡排序函数
def magic_bubble_sort(numbers):
j = len(numbers) - 1  
while j > 0:
for i in range(j):
if numbers[i] % 2 == 0 and numbers[i + 1] % 2 == 1:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
continue
elif (numbers[i + 1] % 2 == numbers[i] % 2) and numbers[i] > numbers[i + 1]:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
continue
j -= 1
return numbers
这段代码没有任何多余的逻辑,可以通过所有的测试用例。面试官看着小 R 演示完函数功能后,盯着代码似乎想说点儿什么,但最后只是微微点了点头,说:“好,今天的面试就到这儿吧,有后续面试我再通知你。”
小 R 高高兴兴地回到家,一心觉得这次面试稳了,可没想到,他后来却再也没接到任何后续面试的通知。
问题出在哪里
究竟是哪里出了问题呢?小 R 思来想去,觉得自己回答问题时表现挺好,最有可能出问题的是最后一道编程题,肯定是漏掉了什么边界情况没处理。
于是他找到一位有着十年编程经验的前辈小 Q,凭着记忆把题目和自己的答案还原给对方看。
“题目大概就是这样,这是我当时写的代码。Q 哥,你帮忙看看,我是不是有什么情况没考虑到?”小 R 问道。
小 Q 盯着他写的代码,足足两分钟没说一句话,然后突然开口道:“小 R 啊,你这个函数功能实现得没毛病,就是实在太难看懂了。”
“总共就 10 行代码。难看懂?怎么会呢?”小 R 在心里泛起了嘀咕。这时,前辈小 Q 说道:“这样,你把笔记本电脑给我,我来给你稍微改改这段代码,然后你再看看。”
三分钟后,小 Q 把修改过的代码递了过来,如代码清单 1-2 所示。
代码清单 1-2 小 Q 修改后的冒泡排序函数
def magic_bubble_sort(numbers: List[int]):
"""有魔力的冒泡排序算法,默认所有的偶数都比奇数大

:param numbers: 需要排序的列表,函数会直接修改原始列表
"""
stop_position = len(numbers) - 1
while stop_position > 0:
for i in range(stop_position):
current, next_ = numbers[i], numbers[i + 1] ➊
current_is_even, next_is_even = current % 2 == 0, next_ % 2 == 0
should_swap = False

# 交换位置的两个条件:
# - 前面是偶数,后面是奇数

# - 前面和后面同为奇数或者偶数,但是前面比后面大
if current_is_even and not next_is_even:
should_swap = True
elif current_is_even == next_is_even and current > next_:
should_swap = True

if should_swap:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
stop_position -= 1
return numbers
❶ 注意:此处变量名是 next_ 而非 next,这是因为已经有一个内置函数使用了 next 这个名字。PEP 8 规定在这种情况下,应该给变量名增加 _ 后缀来避免冲突
小 R 盯着这段代码,发现它的核心逻辑和之前没有任何不同。但不知为何,这段代码看上去就是比自己写的代码更舒服。小 R 若有所思,好像一下明白了自己没通过面试的原因。
故事讲完了。看上去,前辈小 Q 只是在小 R 的代码之上做了些“无关痛痒”的改动,但正是这些“无关痛痒”的改动,改善了代码的观感,提升了整个函数的可读性。
“无关痛痒”的改动
和小 R 写的代码相比,前辈小 Q 的新代码主要进行了以下改进。
(1) 变量名变成了可读的、有意义的名字,比如在旧代码里,“停止位”是无意义的 j,新代码里变成了 stop_position
(2) 增加了有意义的临时变量,比如 current/next_ 代表前一个 / 后一个元素、{}_is_even 代表元素是否为偶数、should_swap 代表是否应该交换元素。
(3) 多了一点儿恰到好处的指引性注释,比如说明交换元素顺序的详细条件。
这些变化让整段代码变得更易读,也让整个算法变得更好理解。所以,哪怕是一段不到 10 行代码的简单函数,对变量和注释的不同处理方式,也会让代码发生质的变化。

1.3 编程建议

“编程建议”是本书大部分章节存在的板块,我将在其中分享与每章主题有关的一些编程建议、技巧,这里并没有什么高谈阔论的大道理,多是些专注细节、务实好用的小点子。比如定义临时变量有什么好处,为什么应该先写注释再写代码,等等。希望这些“小点子”能帮助你写出更棒的代码。
下面,我们一起来看看那些跟变量与注释有关的“小点子”吧。

1.3.1 保持变量的一致性

在使用变量时,你需要保证它在两个方面的一致性:名字一致性与类型一致性。
名字一致性是指在同一个项目(或者模块、函数)中,对一类事物的称呼不要变来变去。如果你把项目里的“用户头像”叫作 user_avatar_url,那么在其他地方就别把它改成 user_profile_url。否则会让读代码的人犯迷糊:“user_avatar_urluser_profile_url 到底是不是一个东西?”
类型一致性则是指不要把同一个变量重复指向不同类型的值,举个例子:
def foo():
# users 本身是一个 Dict
users = {'data': ['piglei', 'raymond']}
...
# users 这个名字真不错!尝试复用它,把它变成 List 类型
users = []
...
foo() 函数的作用域内,users 变量被使用了两次:第一次指向字典,第二次则变成了列表。虽然 Python 的类型系统允许我们这么做,但这样做其实有很多坏处,比如变量的辨识度会因此降低,还很容易引入 bug。
所以,我建议在这种情况下启用一个新变量:
def foo():
users = {'data': ['piglei', 'raymond']}
...
# 使用一个新名字
user_list = []
...
 如果使用 mypy 工具(13.1.5 节会详细讲解),它在静态检查时就会报出这种“变量类型不一致”的错误。对于上面的代码,mypy 就会输出 error: Incompatible types in assignment(变量赋值时类型不兼容)错误。

1.3.2 变量定义尽量靠近使用

包括我自己在内的很多人在初学编程时有一种很不好的习惯——喜欢把所有变量初始化定义写在一起,放在函数最前面,就像下面这样:
def generate_trip_png(trip):
"""
根据旅途数据生成 PNG 图片
"""
# 预先定义好所有的局部变量
waypoints = []
photo_markers, text_markers = [], []
marker_count = 0
# 开始初始化 waypoints 数据
waypoints.append(...)
...
# 经过几行代码后,开始处理 photo_markers、text_markers
photo_markers.append(...)
...
# 经过更多代码后,开始计算 marker_count
marker_count += ...
# 拼接图片:已省略……
之所以这么写代码,是因为我们觉得“初始化变量”语句是类似的,应该将其归类到一起,放到最前面,这样代码会整洁很多。
但是,这样的代码只是看上去整洁,它的可读性不会得到任何提升,反而会变差。
在组织代码时,我们应该谨记:总是从代码的职责出发,而不是其他东西。比如,在上面的 generate_trip_png() 函数里,代码的职责主要分为三块:
初始化 waypoints 数据
处理 markers 数据
计算 marker_count
那代码可以这么调整:
def generate_trip_png(trip):
"""
根据旅途数据生成 PNG 图片
"""
# 开始初始化 waypoints 数据
waypoints = []
waypoints.append(...)
...
# 开始处理 photo_markers、text_markers
photo_markers, text_markers = [], []
photo_markers.append(...)
...
# 开始计算 marker_count
marker_count = 0
marker_count += ...
# 拼接图片:已省略……
通过把变量定义移动到每段“各司其职”的代码头部,大大缩短了变量从初始化到被使用的“距离”。当读者阅读代码时,可以更容易理解代码的逻辑,而不是来回翻阅代码,心想:“这个变量是什么时候定义的?是干什么用的?”

1.3.3 定义临时变量提升可读性

随着业务逻辑变得复杂,我们的代码里也会经常出现一些复杂的表达式,就像下面这样:
# 为所有性别为女或者级别大于 3 的活跃用户发放 10 000 个金币
if user.is_active and (user.sex == 'female' or user.level > 3):
user.add_coins(10000)
return
看见 if 后面那一长串代码了吗?有点儿难读对不对?但这也没办法,毕竟产品经理就是明明白白这么跟我说的——业务逻辑如此。
逻辑虽然如此,不代表我们就得把代码直白地写成这样。如果把后面的复杂表达式赋值为一个临时变量,代码可以变得更易读:
# 为所有性别为女或者级别大于 3 的活跃用户发放 10 000 个金币
user_is_eligible = user.is_active and (user.sex == 'female' or user.level > 3)
if user_is_eligible:
user.add_coins(10000)
return
在新代码里,“计算用户合规的表达式”和“判断合规发送金币的条件分支”这两段代码不再直接杂糅在一起,而是添加了一个可读性强的变量 user_is_elegible 作为缓冲。不论是代码的可读性还是可维护性,都因为这个变量而增强了。
 直接翻译业务逻辑的代码,大多不是好代码。优秀的程序设计需要在理解原需求的基础上,恰到好处地抽象,只有这样才能同时满足可读性和可扩展性方面的需求。抽象有许多种方式,比如定义新函数、定义新类型,“定义一个临时变量”是诸多方式里不太起眼的一个,但用得恰当的话效果也很巧妙。

1.3.4 同一作用域内不要有太多变量

通常来说,函数越长,用到的变量也会越多。但是人脑的记忆力是很有限的。研究表明,人类的短期记忆只能同时记住不超过 10 个名字。变量过多,代码肯定就会变得难读,以代码清单 1-3 为例。
代码清单 1-3 局部变量过多的函数
def import_users_from_file(fp):
"""尝试从文件对象读取用户,然后导入数据库
:param fp: 可读文件对象
:return: 成功与失败的数量
"""
# 初始化变量:重复用户、黑名单用户、正常用户
duplicated_users, banned_users, normal_users = [], [], []
for line in fp:
parsed_user = parse_user(line)
# …… 进行判断处理,修改前面定义的 _users 变量
succeeded_count, failed_count = 0, 0
# …… 读取 _users 变量,写入数据库并修改成功与失败的数量
return succeeded_count, failed_count
import_users_from_file() 函数里的变量数量就有点儿多,比如用来暂存用户的 _users,用来保存结果的 succeeded_countfailed_count 等。
要减少函数里的变量数量,最直接的方式是给这些变量分组,建立新的模型。比如,我们可以将代码里的 succeeded_countfailed_count 建模为 ImportedSummary 类,用 ImportedSummary.succeeded_count 来替代现有变量;对 _users 也可以执行同样的操作。相关操作如代码清单 1-4 所示。
代码清单 1-4 对局部变量分组并建模
class ImportedSummary:
"""保存导入结果摘要的数据类"""
def __init__(self):
self.succeeded_count = 0
self.failed_count = 0
class ImportingUserGroup:
"""用于暂存用户导入处理的数据类"""
def __init__(self):
self.duplicated = []
self.banned = []
self.normal = []
def import_users_from_file(fp):
"""尝试从文件对象读取用户,然后导入数据库  
:param fp: 可读文件对象
:return: 成功与失败的数量
"""
importing_user_group = ImportingUserGroup()
for line in fp:
parsed_user = parse_user(line)
# …… 进行判断处理,修改上面定义的 importing_user_group 变量
summary = ImportedSummary()
# …… 读取 importing_user_group,写入数据库并修改成功与失败的数量
return summary.succeeded_count, summary.failed_count
通过增加两个数据类,函数内的变量被更有逻辑地组织了起来,数量变少了许多。
需要说明的一点是,大多数情况下,只是执行上面这样的操作是远远不够的。函数内变量的数量太多,通常意味着函数过于复杂,承担了太多职责。只有把复杂函数拆分为多个小函数,代码的整体复杂度才可能实现根本性的降低。
 在 7.3.1 节中,你可以找到更多与函数复杂度有关的内容,看到更多与拆分函数相关的建议。

1.3.5 能不定义变量就别定义

前面提到过,定义临时变量可以提高代码的可读性。但有时,把不必要的东西赋值为临时变量,反而会让代码显得啰唆:
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)
}
这样的代码就像删掉赘语的句子,变得更精练、更易读。所以,不必为了那些未来可能出现的变动,牺牲代码此时此刻的可读性。如果以后需要定义变量,那就以后再做吧!

1.3.6 不要使用 locals()

locals() 是 Python 的一个内置函数,调用它会返回当前作用域中的所有局部变量:
def foo():
name = 'piglei'
bar = 1
print(locals())
# 调用 foo() 将输出:
{'name': 'piglei', 'bar': 1}
在有些场景下,我们需要一次性拿到当前作用域下的所有(或绝大部分)变量,比如在渲染 Django 模板时:
def render_trip_page(request, user_id, trip_id):
"""渲染旅程页面"""
user = User.objects.get(id=user_id)
trip = get_object_or_404(Trip, pk=trip_id)
is_suggested = check_if_suggested(user, trip)
return render(request, 'trip.html', {
'user': user,
'trip': trip,
'is_suggested': is_suggested
})
看上去使用 locals() 函数正合适,假如调用 locals(),上面的代码会简化许多:
def render_trip_page(request, user_id, trip_id):
...
# 利用 locals() 把当前所有变量作为模板渲染参数返回
# 节约了三行代码,我简直是个天才!
return render(request, 'trip.html', locals())
第一眼看上去非常“简洁”,但是,这样的代码真的更好吗?
答案并非如此。locals() 看似简洁,但其他人在阅读代码时,为了搞明白模板渲染到底用了哪些变量,必须记住当前作用域里的所有变量。假如函数非常复杂,“记住所有局部变量”简直是个不可能完成的任务。
使用 locals() 还有一个缺点,那就是它会把一些并没有真正使用的变量也一并暴露。
因此,比起使用 locals(),建议老老实实把代码写成这样:
return render(request, 'trip.html', {
'user': user,
'trip': trip,
'is_suggested': is_suggested
})
Python 之禅:显式优于隐式
在 Python 命令行中输入 import this,你可以看到 Tim Peters 写的一段编程原则: The Zen of Python(“Python 之禅”)。这些原则字字珠玑,里面蕴藏着许多 Python 编程智慧。
“Python 之禅”中有一句“Explicit is better than implicit”(显式优于隐式),这条原则完全可以套用到 locals() 的例子上——locals() 实在是太隐晦了,直接写出变量名显然更好。

1.3.7 空行也是一种“注释”

代码里的注释不只是那些常规的描述性语句,有时候,没有一个字符的空行,也算得上一种特殊的“注释”。
在写代码时,我们可以适当地在代码中插入空行,把代码按不同的逻辑块分隔开,这样能有效提升代码的可读性。
举个例子,拿本章案例故事里的代码来说,假如删掉所有空行,代码会变成代码清单 1-5 这样,请你试着读读看。
代码清单 1-5 没有任何空行的冒泡排序(所有文字类注释已删除)
def magic_bubble_sort(numbers: List[int]):
stop_position = len(numbers) - 1
while stop_position > 0:
for i in range(stop_position):
current, next_ = numbers[i], numbers[i + 1]
current_is_even, next_is_even = current % 2 == 0, next_ % 2 == 0
should_swap = False
if current_is_even and not next_is_even:
should_swap = True
elif current_is_even == next_is_even and current > next_:
should_swap = True
if should_swap:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
stop_position -= 1
return numbers
怎么样?是不是感觉代码特别局促,连喘口气的机会都找不到?这就是缺少空行导致的。只要在代码里加上一丁点儿空行(不多,就两行),函数的可读性马上会得到可观的提升,如代码清单 1-6 所示。
代码清单 1-6 增加了空行的冒泡排序
def magic_bubble_sort(numbers: List[int]):
stop_position = len(numbers) - 1
while stop_position > 0:
for i in range(stop_position):
previous, latter = numbers[i], numbers[i + 1]
previous_is_even, latter_is_even = previous % 2 == 0, latter % 2 == 0
should_swap = False
if previous_is_even and not latter_is_even:
should_swap = True
elif previous_is_even == latter_is_even and previous > latter:
should_swap = True
if should_swap:
numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
stop_position -= 1
return numbers

1.3.8 先写注释,后写代码

在编写了许多函数以后,我总结出了一个值得推广的好习惯:先写注释,后写代码。
每个函数的名称与接口注释(也就是 docstring),其实是一种比函数内部代码更为抽象的东西。你需要在函数名和短短几行注释里,把函数内代码所做的事情,高度浓缩地表达清楚。
正因如此,接口注释其实完全可以当成一种协助你设计函数的前置工具。这个工具的用法很简单:假如你没法通过几行注释把函数职责描述清楚,那么整个函数的合理性就应该打一个问号。
举个例子,你在编辑器里写下了 def process_user(...):,准备实现一个名为 process_user 的新函数。在编写函数注释时,你发现在写了好几行文字后,仍然没法把 process_user() 的职责描述清楚,因为它可以同时完成好多件不同的事情。
这时你就应该意识到,process_user() 函数承担了太多职责,解决办法就是直接删掉它,设计更多单一职责的子函数来替代之。
先写注释的另一个好处是:不会漏掉任何应该写的注释。
我常常在审查代码时发现,一些关键函数的 docstring 位置一片空白,而那里本该备注详尽的接口注释。每当遇到这种情况,我都会不厌其烦地请代码提交者补充和完善接口注释。
为什么大家总会漏掉注释?我的一个猜测是:程序员在编写函数时,总是跳过接口注释直接开始写代码。而当写完代码,实现函数的所有功能后,他就对这个函数失去了兴趣。这时,他最不愿意做的事,就是回过头去补写函数的接口注释,即便写了,也只是草草对付了事。
如果遵守“先写注释,后写代码”的习惯,我们就能完全避免上面的问题。要养成这个习惯其实很简单:在写出一句有说服力的接口注释前,别写任何函数代码

1.4 总结

在一段代码里,变量和注释是最接近自然语言的东西。因此,好的变量名、简明扼要的注释,都可以显著提升代码的质量。在给变量起名时,请尽量使用描述性强的名字,但也得注意别过了头。
从小 R 的面试故事来看,即使是两段功能完全一样的代码,也会因为变量和注释的区别,给其他人截然不同的感觉。因此,要想让你的代码给人留下“漂亮”的第一印象,请记得在变量和注释上多下功夫。
以下是本章要点知识总结。
(1) 变量和注释决定“第一印象”
变量和注释是代码里最接近自然语言的东西,它们的可读性非常重要
即使是实现同一个算法,变量和注释不一样,给人的感觉也会截然不同
(2) 基础知识
Python 的变量赋值语法非常灵活,可以使用 *variables 星号表达式灵活赋值
编写注释的两个要点:不要用来屏蔽代码,而是用来解释“为什么”
接口注释是为使用者而写,因此应该简明扼要地描述函数职责,而不必包含太多内部细节
可以用 Sphinx 格式文档或类型注解给变量标明类型
(3) 变量名字很重要
给变量起名要遵循 PEP 8 原则,代码的其他部分也同样如此
尽量给变量起描述性强的名字,但评价描述性也需要结合场景
在保证描述性的前提下,变量名要尽量短
变量名要匹配它所表达的类型
可以使用一两个字母的超短名字,但注意不要过度使用
(4) 代码组织技巧
按照代码的职责来组织代码:让变量定义靠近使用
适当定义临时变量可以提升代码的可读性
不必要的变量会让代码显得冗长、啰唆
同一个作用域内不要有太多变量,解决办法:提炼数据类、拆分函数
空行也是一种特殊的“注释”,适当的空行可以让代码更易读
(5) 代码可维护性技巧
保持变量在两个方面的一致性:名字一致性与类型一致性
显式优于隐式:不要使用 locals() 批量获取变量
把接口注释当成一种函数设计工具:先写注释,后写代码
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了Python编程中变量与注释的重要性以及基础知识。良好的变量命名和清晰的注释对于代码的理解至关重要。文章介绍了Python中变量的定义和常见用法,包括变量解包和单下划线变量名的特殊用法。此外,还探讨了在Python中给变量注明类型的重要性,以及变量命名原则,包括遵循PEP 8原则和描述性要强等。通过示例代码和解释,读者可以快速了解Python编程中变量与注释的基础知识,并学习如何写出易于理解的代码。文章还提到了变量命名的原则,包括描述性强的名字、尽量短的名字以及匹配类型的名字。此外,还介绍了匹配布尔值类型、int/float类型和其他类型的变量名的建议。文章还强调了在代码中明确标注变量类型的重要性。此外,还分享了一些给变量命名的小技巧,如避免出现多个相似的变量名以及尝试换词来简化复合变量名。文章还介绍了Python中的注释基础知识,包括单行注释和多行注释的使用方法。总的来说,本文内容涵盖了Python编程中变量与注释的重要性和基础知识,对于初学者快速入门具有一定的参考价值。文章还提到了Python之禅中的编程原则,强调了显式优于隐式的重要性。此外,还介绍了空行在代码中的作用,以及先写注释后写代码的好习惯。通过本文的总结,读者可以快速了解Python编程中变量与注释的重要性和基础知识,以及一些编程技巧和原则的应用。

[1]:“贪婪”一词在计算机领域具有特殊含义。比方说,某个行为要捕获一批对象,它既可以选择捕获 1 个,也可以选择捕获 10 个,两种做法都合法,但它总是选择结果更多的那种:捕获 10 个,这种行为就称得上是“贪婪”。

[2]:具体来说,针对变量的类型注解语法是在 Python 3.6 版本引入的,而 3.5 版本只支持注解函数参数。

[3]:IDE 是 integrated development environment(集成开发环境)的缩写,在满足代码编辑的基本需求外,IDE 通常还集成了许多方便开发者的功能。常见的 Python IDE 有 PyCharm、VS Code 等。

[4]:相比之下,类型注解在开源领域的接受度更高一些,许多流行的 Python 开源项目(比如 Web 开发框架 Flask 和 Tornado 等),早早地给代码加上了类型注解。

[5]:世界上规模最大的开源项目源码托管网站。

[6]:“自说明”是指代码在命名、结构等方面都非常规范,可读性强。读者无须借助任何其他资料,只通过阅读代码本身就能理解代码意图。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Python 工匠:案例、技巧与工程实践》
立即购买
登录 后留言

精选留言

由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。
收起评论
大纲
固定大纲
1.1 基础知识
1.1.1 变量常见用法
1.1.2 给变量注明类型
1.1.3 变量命名原则
1.1.4 注释基础知识
1.2 案例故事
奇怪的冒泡排序算法
1.3 编程建议
1.3.1 保持变量的一致性
1.3.2 变量定义尽量靠近使用
1.3.3 定义临时变量提升可读性
1.3.4 同一作用域内不要有太多变量
1.3.5 能不定义变量就别定义
1.3.6 不要使用 locals()
1.3.7 空行也是一种“注释”
1.3.8 先写注释,后写代码
1.4 总结
显示
设置
留言
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部