🧮 第2章 变量和数组
📄 一句话概括

名字是写给下一个读代码的人的,那个人半年后可能是你自己。 常见的数据配常见的名字:个数叫 count,合计叫 total,下标叫 i。 比「好名字」更重要的是一致——同一类东西,全书用一个叫法。

📝 本章和前面几章的关系

这是第 2 章的专题,教材把它放在最后一节,因为前面所有例子里的名字都出现了。 2.3 讲过命名的三条硬规矩(字母开头、不含符号、不撞关键字)。这一篇补的是软规矩。 软规矩不遵守代码也能跑——这正是它容易被忽略的原因。

一、名字是写给人看的,编译器不看

s1、s2、t、m、x 这些名字,机器一样能跑。它只关心「这是同一个变量」。 受苦的是读的人。读到 avg = t / m 的时候,你得往上翻十几行才知道 t 是总分、m 是人数。 教材这一节的重点其实不在那张表,在「一致」两个字。 名字起得一般但前后一致,比起得漂亮但换一个说法就换一个名字强得多。

理由很实在:读代码时人是靠「形状」认东西的。同一个概念总是同一个词,眼睛扫过去就能连起来; 换个说法,就得重新判断一次。

二、一张对照表:常见的数据配常见的名字

下面这张表不是标准,是惯例。用惯例的好处是别人不用学你的习惯。

装的东西 常用名字 别写成
个数 count n1、a
合计 total t
平均值 average m
最大值、最小值 max_value、min_value max(和函数重名)
下标 i、index x
临时存放 temp tmp2、tmp3
开关标志 found、is_ready flag(说不清是什么标志)

最后两行值得停一下。temp 短命,用完就扔,叫 temp 最省事; 但 tmp2、tmp3 一多,就没人记得哪个是哪个了。 flag 的问题不是难看,是没信息。if flag: 读完不知道在判断什么,而 if found: 一眼就懂。 名字要把「是什么」写进去,不是把「是个布尔值」写进去。 还有 max 那一行:拿内置函数名当变量名,后面再想调 max() 就调不到了。 这类错在 Python 里不报错,只是行为突然变了。

三、拿一个检查器,把候选名字过一遍

演示里写了个小检查器,扫九个候选名字。三个通过:score、max_score、cnt。 六个被挡下来,但它们不是一类问题。前三条是语法,后两条是约定。

挡下来的原因 属于 不改会怎样
数字开头(2nd_score) 语法 直接报错,跑不起来
有非法字符(max-score) 语法 直接报错,跑不起来
是关键字(for) 语法 直接报错,跑不起来
l、O、I 单字母 约定 和数字 1、0 长得像,看错人
名字太长 约定 能跑,但一行放不下,读着累

演示最后那句还提了「太短」这条约定,只是这一批候选没撞上——最短的是 cnt,三个字母还说得过去。 名字叫 a 的时候它才会响。 这五行合起来说明一件事:语法只挡前三行,后两行没人替你挡。

四、「能跑」和「好读」之间隔着一张检查表

语法错误编译器会喊你,约定不合没人喊。所以后者只能靠两样东西:自己定的规矩,和能自动跑的检查。 检查器可以很简单:长度在 3 到 24 之间、只含字母数字下划线、不在关键字表里、不是 l/O/I。 二十行代码,能挡掉大部分低级问题。 再往上一步是团队约定:这份代码里「个数」永远叫 count,永远不叫 num、cnt、n。 这条约定写进文档,比任何一次代码评审都省事。

这也是本书后面所有例子的写法依据——看后面章节的代码时,先认名字,往往比先看逻辑更快读懂。

五、这一章收个尾

第 2 章从「数据是什么」讲到「数据怎么摆」,再到「数据叫什么」。三件事凑齐了,才谈得上写算法。 回顾一下这条线:值有类型(2.1),值装进变量(2.2、2.3),同类的值排成数组(2.4、 2.5),数组摞成表格、字符排成字符串(2.6)。 下一章换一个维度:同样一堆数据,摆放的方式不同,取用的代价能差上千倍。那就是数据结构。

六、动手 Demo:九个候选名字,过一遍检查器

先看一张「什么数据配什么名字」的对照表。 再用一个二十来行的检查器,把九个候选名字逐个过一遍,看它挡下哪几个、为什么。

import keyword

def check(name):
    if not name[0].isalpha():
        return '不过:数字开头'
    if not all(c.isalnum() or c == '_' for c in name):
        return '不过:有非法字符'
    if keyword.iskeyword(name):
        return '不过:是关键字'
    if name in ('l', 'O', 'I'):
        return '不过:l/O/I 单字母,和数字像'
    if len(name) > 24:
        return '不过:太长'
    return '通过'

for n in ('score', 'max_score', '2nd_score', 'max-score',
          'for', 'cnt', 'l', 'O', 'score_of_the_student_in_class'):
    print(n, ' -> ', check(n))

实跑输出:

--------------------------------------------------------------------
常用变量命名:一张对照表和一个检查器
--------------------------------------------------------------------


  ① 数据是什么,名字就用什么
  --------------------------------------------------------------------

| 装的东西 | 常用名字 | 别写成 |
|---|---|---|
| 个数 | count | n1、a |
| 合计 | total / sum | t |
| 平均值 | average / avg | m |
| 最大值、最小值 | max_value / min_value | max(和函数重名) |
| 下标 | i / index | x |
| 临时存放 | temp | tmp2、tmp3 |
| 开关标志 | found / is_ready | flag(说不清是什么标志) |

  这些名字不神圣,但用得一致,别人扫一眼就知道这一行在算什么。
  教材这一节的重点不在表,在「一致」两个字。

  ② 拿一个检查器,扫一批候选名字
  --------------------------------------------------------------------
  score  ->  通过
  max_score  ->  通过
  2nd_score  ->  不过:数字开头
  max-score  ->  不过:有非法字符
  for  ->  不过:是关键字
  cnt  ->  通过
  l  ->  不过:l/O/I 单字母,和数字像
  O  ->  不过:l/O/I 单字母,和数字像
  score_of_the_student_in_class  ->  不过:太长
  后三条(l/O/I、太长、太短)是约定,不是语法。
  语法只管前三条——所以「能跑」和「好读」之间,永远隔着这么一张检查表。

①检查器里前三条是语法(数字开头、非法字符、关键字),后两条是约定(单字母、太长)。 顺序不能反:语法先挡,约定后挡。 ②keyword.iskeyword 是标准库给的,不用自己维护关键字表。 ③输出里 cnt 是通过的——它不违规,只是不如 count 清楚。 检查器只能挡「不能跑」和「明显坑人」,挡不住「不够好」。 ④最后那个长名字被挡是因为超过 24 个字符。这个上限是随手定的,换成 32 结论也一样,别当成标准。

30 秒实验

  1. 把 check 里的 24 改成 32,看那个长名字还能不能通过。
  2. 给检查器加一条:「不能和内置函数重名」(max、sum、list),用 dir(__builtins__) 试。
  3. 翻一翻你自己写过的代码,找出三个名字,看看过不过得了这个检查器。

常见误区

  1. 「名字起什么都行,反正能跑。」能跑,但读的人要多花几倍时间,包括三个月后的你。
  2. 「名字越长越清楚。」超过二十多个字符,一行放不下,反而难读。清楚靠选词,不靠堆词。
  3. 「flag 是标准写法。」它只说了「这是个开关」,没说「是什么开关」。用 found、is_ready。
自测三问(点开对答案) 1. 命名的硬规矩和软规矩各是什么? —— 硬规矩(语法):字母开头、只含字母数字下划线、不撞关键字。软规矩是约定,靠人和检查器。 2. 为什么不该用 `max` 当变量名? —— 它是内置函数名。覆盖之后再用 `max()` 就调不到了,而且不报错,只是行为变了。 3. 为什么说「一致」比「好名字」更重要? —— 读代码靠形状认东西。同一概念总是同一个词,眼睛能直接连起来;不断换说法就得重新判断。

第 2 章到此结束。 下一章讲同样一堆数据,摆放方式不同,取用代价差上千倍——从堆栈、队列开始,到链表、树、图。

相关笔记