很多新手会觉得,变量名就是个代号,只要不报错,怎么起都行。我刚学 Python 那阵也这么干,a = 1、b = 2、tmp满天飞。后来接手一个写了半年的项目,满屏data1、data2、temp_list,改一个功能要在三个文件里来回翻找,才意识到“变量名”这事,真的一点也不小。关于变量名是什么,Python 对变量命名到底有哪些要求,这篇文章一次讲透。我会先把硬性语法规则说清楚,再讲比语法更重要的行业习惯,也就是 PEP 8 里的命名风格,最后附上我自己踩过的坑和一份可以直接照抄的速查清单。适合刚入门 Python 的同学,也适合写了段时间但从来没认真梳理过命名规则的人。
1. 先搞明白变量名到底是什么
1.1 变量名本质上是“标识符”
很多人把变量理解成一个盒子,赋值就是把数据装进盒子。这个比喻在 Python 里其实不太准确,更准确的说法是:变量名是贴在数据上的“标签”,或者说,它是一个标识符(identifier)。
user_name = "张三"这行代码做了三件事:创建了一个字符串对象"张三";生成了一个变量名user_name;把这个变量名绑定到了那个字符串对象上。之后再写user_name,Python 就会顺着这个名字找到对应对象。
把变量名理解成标识符,能解释很多平时遇到的现象。比如为什么b = a之后去修改a指向的对象内容,b也会跟着变?因为a和b这两个标签,指向的是同一个对象,而不是把对象“复制”了一份。想通这一点,后面理解函数传参、列表修改、赋值引用都要轻松得多。
所以变量名不是一个简单的“代号”,它是你在代码里引用某一个对象的唯一入口。它决定了你以后怎么写、怎么读、怎么改这段代码,也直接决定了别人(以及三个月后的你自己)能不能轻松理解你的意图。
1.2 命名越自由,维护成本越高
Python 是动态语言,变量不需要提前声明类型,解释器在运行时会自动绑定。这就意味着:一段代码能跑起来,并不代表它写得好。命名这件事,很多时候不是“解释器管不管”的问题,而是“人能不能看懂”的问题。
举个例子,d = 1.8,这到底是什么意思?有人会猜是折扣(discount),有人觉得是密度(density),还有人可能认为是距离(distance)。但如果写成discount_rate = 0.9,所有人都能一眼看出是折扣率。这就是“能运行”和“好维护”之间的差距。
很多新手抱怨自己代码写得乱,后来仔细一看,问题不在逻辑,而在命名:满屏的单字母、拼音缩写、随手敲的temp。名字越模糊,阅读代码时大脑需要做的“解码”工作就越多,改起 bug 来自然非常痛苦。所以要重视命名,但第一步,还是先把语法规则搞清楚。
2. 硬性语法规则:想被解释器接受,至少要满足这些
2.1 合法字符范围:数字、字母、下划线,还有中文?
Python 3 中,一个合法的变量名可以由字母、数字和下划线_组成,但有一个硬性限制:首字符不能是数字。更规范的表述是:
- 首字符可以是字母(包括 Unicode 字母)或下划线;
- 后续字符可以是字母、数字或下划线。
注意这里的“字母”范围比很多人想象的大。Python 3 的标识符规则继承自 Unicode 标准,所以中文、日文、韩文、阿拉伯文等字符,只要符合 Unicode 字母属性,都可以作为变量名使用。这一点和早期 Python 2 有本质区别。
# 这些变量名在 Python 3 里都是合法的 user_name = "张三" user1 = "李四" _private = "王五" 年龄 = 18 # 中文变量名,语法上没问题 print(年龄) # 能正常运行那中文到底能不能当变量名用?从语法层面讲,可以,而且运行完全正常。但从实用层面讲,我很少在正式项目里看到好的中文命名实践。原因不是“语法不行”,而是协作成本:中文输入法容易把全角括号、逗号混进代码;某些命令行工具、日志分析工具对非 ASCII 标识符的处理不一致;代码评审时复制粘贴也会因为全角半角差异闹出幺蛾子。当然,在一些纯内部工具、教学示例里,中文变量名能明显降低理解门槛,用也无妨。核心原则是:团队约定优先,别让命名方式成为沟通障碍。
2.2 不能以数字开头:看似任性,其实是给编译器减负
1st_name = "张三" # SyntaxError: invalid decimal literal first_name = "张三" # 正确用数字开头为什么会直接报错?因为词法分析阶段会产生歧义。设想一下,如果允许某个名字以1abc开头,编译器遇到1abc时,到底是把它当成数字1后面跟着标识符abc,还是当成一个整体标识符?如果没有明确规则,解析器每一步都要浪费额外决策。
这就像门牌号一样:如果允许楼栋号从“栋”字开头,那“一号楼”和“1号楼”之间就得靠上下文猜。Python 选择了一刀切的方案:首字符不可以是数字。代价是像1st、2fast这种在自然语言里很自然的写法没法用,但换来了更清晰的词法边界,程序员也不用纠结“我这个命名法官怎么判”。
2.3 区分大小写:Python 这里是认真的
Python 对大小写是敏感的,Name、name、NAME三个名字,在解释器看来是完全不同的三个变量。
name = "小张" Name = "小李" NAME = "小王" print(name) # 小张 print(Name) # 小李 print(NAME) # 小王这个规则能造成很隐蔽的 bug。比如你在一个函数里先写了userID,后面某处写成user_id,Python 不会报错,而是会在运行到user_id的时候直接告诉你这个名字没定义。关键是,如果你用了 IDE 或全局搜索去查userID,很可能漏掉user_id这个“长得差不多但实际不同”的变量,导致排查半天。
大小写敏感本身不是问题,问题是团队要形成明确约定:不要靠大小写区分两个意义相近的变量,也尽量不要在同一个项目里混用user_id和userId。规范命名,才能避开这类本不该存在的坑。
2.4 关键字和内置名称:两条不同边界
先说关键字。关键字是 Python 语言预留的词,有自己的语法含义,不能当作变量名使用。下面是 Python 3.x 中常见的硬关键字清单:
False、None、True、and、as、assert、async、await、break、class、continue、def、del、elif、else、except、finally、for、from、global、if、import、in、is、lambda、nonlocal、not、or、pass、raise、return、try、while、with、yield
如果你尝试写:
class = "test" # SyntaxError: invalid syntax解释器会直接拒绝。想偷懒用某些关键字附近的名字,比如clazz、klass,虽然能跑,但很容易让人误读,并不推荐。
比硬关键字更隐蔽的是“软关键字”。Python 3.10 加入的match、case就是软关键字,它们在特定语法位置上有特殊含义,但作为普通变量名在多数场景下仍可以使用。例如:
match = "abc" case = "def"这两行代码在 Python 3.10+ 一般不报错,但这种写法很容易踩中模式匹配语法,所以我的建议是:遇到match、case这类软关键字,也尽量避开,没必要为了一个名字给自己留隐患。
再说内置名称。list、str、dict、int、len、type、print、open、input这些名字不是关键字,它们占据了内置命名空间。理论上你可以把它们当作变量名使用,但一旦覆盖,就相当于在局部作用域里“藏”了原本的函数名。后面会详细说这个问题,这里先记住一个结论:合法不等于推荐,语法放行不等于安全。
3. 从“能运行”到“好维护”:命名风格是关键
3.1 为什么 Python 社区默认 snake_case
语法规则保证了“能不能跑”,风格规则决定了“好不好改”。Python 官方的 PEP 8 风格指南对命名约定有明确建议:普通变量名和函数名使用全部小写字母,多个单词之间用下划线分隔。这种风格被叫做 snake_case。
# 推荐 user_name = "张三" max_retry = 3 http_status_code = 200 # 不推荐 userName = "张三" maxRetry = 3 httpStatusCode = 200很多从 Java、JavaScript 转过来的朋友习惯驼峰式(camelCase)命名,但在 Python 社区统一下使用 snake_case,能让代码风格高度一致。举个例子:标准库里几乎所有变量和函数命名都是小写加下划线,你也这样写,别人打开你的代码时会有一种“熟悉的节奏”。这不是审美问题,是减少认知负担问题。
另外一个细节:单个下划线_是“忽略”的常规符号。比如循环遍历时不需要用到索引:
for _ in range(5): print("hello")这里的_表示“当前这个变量我不关心”。在交互式终端里,_还会保存上一次表达式的结果,不过那属于另一个使用场景,写代码时注意区分就好。
3.2 常量、类名、私有变量和“魔术方法”
除了普通变量,Python 里还有几种约定俗成的命名方式,它们承担着不同的语义:
- 常量:全大写 + 下划线,例如
MAX_RETRY = 5、DEFAULT_TIMEOUT = 3。这只是一个约定,Python 没有真正的常量机制,但团队看到全大写名字后,就会默认“不要轻易改它”。 - 类名:首字母大写的驼峰式,例如
UserProfile、HttpClient。类名不算变量,但同样是标识符。 - 内部使用/私有变量:单下划线开头,例如
_internal_cache。表示“这是一个内部实现细节,外部不应该直接访问”,同样是约定,不是强制限制。 - 名称改写(name mangling):双下划线开头,例如
__secret_value。它也不是严格意义上的私有变量,而是触发了 Python 的名称改写机制,让外部访问变得很别扭,一般用于避免继承时的命名冲突。 - 魔术方法:双下划线包裹,例如
__init__、__str__。这些是 Python 内置的特殊方法名,一般不建议把它们当作普通变量名使用。
我见过有人给变量起名叫__item__,结果代码里到处去查询这个名字到底是不是标准魔术方法,非常浪费时间。规矩一点,把双下划线留给语言本身。
3.3 用命名表达意图:动词、名词和布尔值
好的变量名,应该让人一眼看出“这是什么”以及“要做什么”。这里有几个非常实用的小习惯:
- 普通数据值,用名词或名词短语:
user_name、order_total、report_date。 - 布尔变量,尽量以
is_、has_、can_、should_开头:is_active、has_error、can_edit、should_retry。 - 函数,用动词或动词短语:
get_user_name()、send_mail()、calc_total_price()。
反过来,一些看起来“通用”的词,在项目里往往是最危险的。比如:
d = {"name": "张三", "age": 18} # d 到底是什么? temp = "2024-01-01" # temp 是日期?是格式化后的字符串? result = calc(1, 2) # result 是什么类型的计算结果?如果多问自己一句“这个变量的业务含义是什么”,上面三行很容易改成:
user_info = {"name": "张三", "age": 18} report_date = "2024-01-01" total_price = calc(1, 2)当然也不是说所有变量都必须长篇大论。在极短的作用域里,比如一个只有三行的列表推导式,使用i、x、item完全没问题。关键是:名字长度要和作用域范围匹配。变量活的时间越长、影响范围越广,名字就应该越具体。
另外提一句循环索引。单层循环里for i in range(10)很常见,可读性也不差。但一旦出现两层以上嵌套循环,i、j、k很容易把人绕晕。如果循环逻辑比较复杂,宁可写成row_index、col_index、card_index,多敲几个字符换来的是更高可读性。
4. 最容易踩的坑:我的真实翻车记录
4.1 给变量起名 list、dict,程序当场报废
这是我见过最多的翻车场景,没有之一。看下面的代码:
list = [1, 2, 3] print(list) # 能打印,但你已经把 list 这个内置名称盖掉了 new_list = list("abc") # TypeError: 'list' object is not callable第二行看起来像是把字符串"abc"转成列表,但因为你在上一层给list赋了新值,所以这个作用域里的list不再是内置函数了。遇到这种报错,新手会非常困惑:我上一行明明只是创建了一个列表,为什么这里就调用不了list()了?
同理,str、dict、int、len、open、input、type、print这些内置名称都容易被误伤。覆盖之后,当前作用域对本该内置的函数访问就会失效,而报错信息往往不是直接告诉你“我把内置函数覆盖了”,而是一个很拗口的类型错误或属性错误。
如果已经发生了覆盖,可以用del把覆盖名删掉,重新恢复内置名称,例如del list。但更靠谱的做法是:从一开始就别用这些名字当变量。常见替代方案是user_list、items、key_value_map、text_value等。如果你真的想用type这类名字,可以写成user_type、kind之类,既表达了语义,又绕开了内置名。
4.2 大小写不一致造成的迷之 bug
另一个典型问题来自团队协作时,不同人用了不同的大小写风格。比如某个字段叫userId,有人写代码时图省事写成user_id,因为 Python 区分大小写,运行时必然报NameError: name 'user_id' is not defined。
这个报错出现的时机非常阴险。它不发生在你写错的那一行,而是发生在运行时第一次访问user_id的那一行。如果这一段代码在某个很深的分支逻辑里,可能上线前测试都没走到,直到客户录入某类数据时才触发,那时再排查就要多花很多精力。
我自己的习惯是:团队里一旦定了风格,所有代码都要统一执行。看到旧代码用了userID,新代码就必须跟着用userID,不要自作主张改成user_id。先统一,再重构。否则两套风格并存,很容易写出“半新半旧”的代码,bug 悄悄埋伏在风格切换的边界上。
4.3 相似命名和“魔法数字”的连锁反应
还有一类坑,来自几乎相同的名字:data_list1、data_list2、data_list_bak、data_list_new。这种名字在写的时候,你心里很清楚每个变量分别是什么;三天之后再回来看,就已经开始怀疑人生了。
更严重的是,你可能会在某次修改里把data_list2和data_list_bak搞混,然后业务结果全部错乱。要处理一组数据,正确做法通常是使用列表或字典结构,而不是起一堆data1、data2、data3。这种“序号式”命名往往说明数据结构选得不对,变量名只是问题浮出水面后的表现而已。
还有一个容易犯的问题是用魔法数字当默认值,比如:
timeout = 30不是不能写,但如果你在七八个地方用了30,等想改成60时就只能全局搜索。不妨一开始就定义为DEFAULT_TIMEOUT = 30,后面所有引用都用这个名字。改动时一行完成,而且语义也变得更清楚了。
5. 一页速查表:命名合法性和风格对照
5.1 合法性检查:用一段代码快速验证
Python 标准库提供了现成的工具,用来判断一个字符串是否能作为标识符、是不是关键字。
import keyword def check_name(name): if not name.isidentifier(): return "非法:不是合法标识符" if keyword.iskeyword(name): return "非法:是 Python 关键字" return "合法(但合不合法只是第一步)" for n in ["user_name", "1user", "class", "list", "_private", "年龄"]: print(n, "->", check_name(n))这段代码不是花架子,面试时也常拿来考察候选人对标识符规则的理解。isidentifier()用来判断字符串是否符合标识符字符规则,keyword.iskeyword()用来判断是否命中保留关键字。两者都检查通过,才算“语法上合法”。
需要提醒的是,这里的“合法”并不同时意味着“推荐”。list和print都能通过检查,但它们会踩中前面说的内置名称覆盖问题,所以这类命名我已经列入“不推荐”清单。
5.2 一页速查表:常见命名的选择
下面这张表是我在项目里经常发给新人的对照表,基本覆盖了常见场景:
| 场景 | 推荐写法 | 是否合法 | 是否推荐 |
|---|---|---|---|
| 普通用户变量 | user_name | 合法 | 推荐 |
| 带序号的同类数据 | user1、user2 | 合法 | 不推荐 |
| 以数字开头 | 1user | 非法 | 不可用 |
| 使用关键字 | class | 非法 | 不可用 |
| 覆盖内置函数 | list | 合法 | 不推荐 |
| 常量 | MAX_RETRY | 合法 | 推荐 |
| 类名 | UserProfile | 合法 | 推荐 |
| 内部私有约定 | _internal_cache | 合法 | 推荐 |
| 魔术方法 | __init__ | 合法 | 保留给语言使用 |
另外还有一个小习惯:如果你确实需要给数据变量起名,但常用词已经被占用或者怕冲突,可以在后面加一个下划线作为区分,比如class_、list_。这种写法在 Python 里是一个通用约定,但能避开关键字的场景其实很有限,我更建议直接换一个更准确的业务词。
5.3 自查清单:提交代码前快速过一遍
每次提交代码之前,我会问自己几个问题:
- 这个变量名语法上合法吗?首字符不是数字,不含空格和特殊符号。
- 是不是避开了关键字和内置名称?比如
list、str、print这些。 - 是不是符合全小写字母加下划线的风格?有没有因为手误用了驼峰?
- 不看注释,光看名字,别人能否猜到它表示什么?
- 如果它是布尔值,是否用了
is_、has_、can_开头? - 如果它表示常量,是否用了全大写?
这套问题看起来简单,但每一条都能拦住真实世界里的一批 bug。我在评审代码时发现,绝大多数“命名不合理”问题都能被这六条全覆盖。
6. 常见问题与排查技巧实录
6.1 一看到 SyntaxError 就慌?先从变量名开始查
很多新手看到SyntaxError就懵了,实际上不少语法错误直接和命名相关。我整理了一张快速排查表:
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
SyntaxError: invalid character '(' (U+FF08) | 混入全角中文括号 | 把全角符号改成英文半角符号 |
SyntaxError: invalid decimal literal | 变量名以数字开头 | 用字母或下划线作为首字符 |
SyntaxError: cannot assign to keyword | 给关键字赋值 | 换一个同义词当变量名 |
NameError: name 'xxx' is not defined | 变量未定义或前后大小写不一致 | 搜索整个文件确认统一名称 |
TypeError: 'list' object is not callable | 覆盖了内置list,又把它当函数调用 | 换个变量名,必要时在解释器里del list |
其中NameError最值得展开。它通常意味着两件事:要么这个变量从头到尾没被赋值过,要么你写错了名字(包括大小写不统一)。排查方式是先搜代码里所有出现这个名字的地方,尤其是赋值语句和引用语句,看看有没有名字不一致的问题。别只盯着报错行看,因为问题往往出在其他位置。
6.2 重构变量名时,安全改一轮的正确姿势
当你终于发现某个变量名起得太烂,决定改成更好的名字,怎么改才不出事?
我的建议是:先做全局搜索,确认旧名字改了哪些地方,尽量利用编辑器或 IDE 的“重命名符号”功能。不要在项目里手动一个个替换,因为同名变量加同名注释会让替换范围失控。
如果只能字符串替换,替换后一定要重新跑一次测试,或者至少把所有文件里的关键字搜索一遍,确认没有遗漏。比较稳妥的做法是先把要改的名字列出来,写进一个任务清单,然后逐个文件推进,而不是全局一把梭。我曾经在一次全局替换里把total_price误改到注释里的一处无关文案,差点让测试数据跟着变了。
6.3 给新手的三个实用命名心法
第一,每写完一段功能,回头扫一眼自己的变量名。把a、b、tmp这些明显敷衍的名字改掉,再往下写新功能。这个习惯坚持两周,效果立竿见影。
第二,命名时多想想业务语言。比如salary_without_tax虽然很长,但一眼就知道是“税前工资”;如果写成money1,后面谁也不知道它和money2有什么关系。业务词汇比技术缩写更适合做变量名,因为它不需要二次翻译。
第三,如果你发现自己需要写一行注释来解释这个变量名是什么意思,那多半意味着名字本身选得不好。好的变量名应该自解释,而不是依赖注释。这个判断标准非常高,但一旦养成,你会发现自己代码的阅读体验明显上了一个台阶。
我在实际项目里最深的一个体会是:变量名起得好不好,影响的不是写代码那几分钟,而是三个星期后回来改代码时,还能不能快速找回当时的思路。与其花时间追悔一个糟糕的名字,不如从写下的第一行变量开始就认真对待。先把硬性规则记住,再把 PEP 8 的习惯内化,最后让每个名字尽量接近业务含义。长期下来,这件事能省下的调试时间,远超你当初多思考命名时花费的精力。