为什么Python不用var或let声明变量?

免费编程软件「python+pycharm」 链接:pan.quark.cn/s/48a86be2f...

一个JavaScript程序员转Python的第一天

小王做了三年前端,最近开始学Python。他打开编辑器,敲下了人生中第一段Python代码:

ini 复制代码
var name = "张三"
let age = 18
const PI = 3.14

运行,报错。他懵了。

"没有var?没有let?也没有const?那Python怎么声明变量?"

他把var删掉,直接写:

ini 复制代码
name = "张三"
age = 18
PI = 3.14

居然可以。他更懵了:"不用声明就能用?这不会乱套吗?"

这个问题问到了点子上。今天我们就来聊聊:Python为什么不用varlet来声明变量? 没有声明关键字,Python是怎么保证代码不乱套的?


先看看别人家是怎么做的

在解释Python之前,先看看其他语言是怎么声明变量的。

JavaScript:三种方式

ini 复制代码
var x = 10;      // 函数级作用域,有变量提升
let y = 20;      // 块级作用域,没有变量提升
const z = 30;    // 块级作用域,不能重新赋值

三个关键字做同一件事(声明变量),但行为完全不同。新手看到这些规则,头都大了。

Java:类型在前

ini 复制代码
int x = 10;
String name = "张三";
boolean flag = true;

声明变量时必须带上类型。好处是类型安全,坏处是啰嗦。

C语言:和Java类似

ini 复制代码
int x = 10;
char *name = "张三";

声明变量时也必须指定类型。

Rust:let是必须的

ini 复制代码
let x = 10;
let mut y = 20;  // mut表示可修改

Rust用let声明变量,默认不可变,要修改必须加mut

Go:用:=或者var

go 复制代码
x := 10          // 短变量声明
var name = "张三"
var age int = 18 // 完整写法

回到Python,你会发现它做了一件很特别的事:声明变量不需要任何关键字,赋值就是声明

ini 复制代码
x = 10          # 声明一个整数
name = "张三"    # 声明一个字符串
person = {"name": "张三"}  # 声明一个字典

你写x = 10,Python就知道你要创建一个变量叫x,指向整数10。不需要var,不需要let,不需要类型。

这个设计看起来"太随意了",但其实背后有很深的设计哲学。


Python的设计哲学:赋值即声明

Python的创造者Guido van Rossum在设计变量声明时,遵循了一个核心原则:简单就是王道

在Python里,变量的声明和赋值是同一件事。你给一个名字赋值,这个名字就变成了变量。

ini 复制代码
# 这两行是等价的
x = 10           # 声明+赋值,一次性完成
# 在Python里没有单独的"声明"操作

如果你用过JavaScript,你知道var x;只是声明,x = 10;才是赋值。声明和赋值是两步。

Python把这两步合并了。你永远不需要提前说"我要用这个名字",直接用就行。

为什么这样设计?

Guido的想法是:程序员写代码时,90%的变量都是在赋值的同时声明的。既然几乎总是同时做,为什么不合并成一个操作?

这减少了键盘敲击,减少了视觉噪音,让代码看起来更干净。

ini 复制代码
# Python
name = "张三"
age = 18
items = []

# JavaScript
var name = "张三";
let age = 18;
const items = [];

Python少打了varletconst、分号,代码更短。这符合Python"简洁"的追求。


没有关键字,怎么区分变量和常量?

你可能会问:"那const呢?Python怎么定义常量?"

答案是:Python没有真正的常量。

在Python里,一切都是变量。你定义一个名字,它就可以被重新赋值。

ini 复制代码
PI = 3.14
PI = 3.14159   # 合法!Python不会阻止你

那怎么办?约定成俗:所有字母都大写的名字,视为常量,不要修改它。

ini 复制代码
MAX_CONNECTIONS = 100
API_KEY = "sk-123456"

这只是程序员之间的约定,Python解释器不会强制保护。如果你修改了它,Python也不会报错。

听起来很危险?其实还好。在团队开发中,代码审查和规范检查会帮你发现问题。而且真正的"不可变"需求,可以用property__slots__或者数据类来实现更强的保护。

为什么Python不内置const

Guido在邮件列表里解释过:他认为不需要。Python的目标用户不是写大型企业系统的,而是希望快速完成工作的。加一个const会增加语言的复杂性,收益却不大。

如果需要真正的不可变性,可以用types.MappingProxyType创建只读字典,或者用@dataclass(frozen=True)创建不可变数据类。


没有关键字,怎么处理作用域?

这是很多人好奇的点。JavaScript有varletconst来处理作用域,Python什么都没有,它怎么区分局部和全局?

Python的规则很简单:赋值决定作用域。

看例子:

ini 复制代码
x = 10  # 全局变量

def func():
    y = 20  # 局部变量,因为这里赋值了
    print(x)  # 可以读全局的x

func()
print(y)  # 报错!y是func的局部变量

Python在编译函数时,看到y = 20这个赋值语句,就把y标记为局部变量。函数内的所有y都指向这个局部变量,除非你用了globalnonlocal声明。

这和JavaScript完全不同。JavaScript的var有变量提升,let有暂时性死区,规则复杂得多。

Python的做法是:在哪里赋值,就在哪里生效

  • 在函数外赋值 → 全局变量
  • 在函数内赋值 → 局部变量
  • global声明 → 明确指向全局
  • nonlocal声明 → 明确指向外层函数

这个规则简单、一致、可预测。


对比:JavaScript声明变量的混乱史

Python不引入声明关键字,还有一个原因:避免JavaScript踩过的坑

JavaScript的变量声明历史是一部"反复横跳"的血泪史。

最早的时候,只有var

ini 复制代码
if (true) {
    var x = 10;
}
console.log(x);  // 10,x泄漏出来了

var没有块级作用域,只有函数级作用域。这导致了很多bug。

var还有变量提升

ini 复制代码
console.log(x);  // undefined,而不是报错
var x = 10;

变量声明被提升到了作用域顶部,但赋值留在了原地。这让人非常困惑。

ES6引入了letconst

ini 复制代码
if (true) {
    let y = 10;
}
console.log(y);  // 报错!y is not defined

终于有了块级作用域,但代价是语言变得更复杂了。现在JavaScript有三种声明变量的方式,各有各的规则。

Python避免了这一切

从一开始,Python就没有这些历史包袱。它的设计是统一的:

  • 赋值就是声明,没有两步操作
  • 作用域由赋值位置决定,规则清晰
  • 不存在变量提升,使用前必须赋值

Python不需要varlet,因为它从一开始就设计对了。


Python的变量声明,真的没有坑吗?

当然不是。Python的"赋值即声明"也有自己的坑。

坑1:在函数内意外创建局部变量

这是最常见的坑:

ini 复制代码
name = "张三"

def change():
    name = "李四"   # 创建了局部变量,不是修改全局

change()
print(name)  # "张三"------没变

你想修改全局name,结果创建了一个局部变量。Python不会报错,只是静默地做了另一件事。

解决方法 :如果要修改全局变量,用global声明。

坑2:在if/for里创建变量会泄漏

python 复制代码
for i in range(5):
    message = f"数字{i}"

print(message)  # "数字4"------泄漏了

因为Python没有块级作用域,message成为了所在函数或全局的变量。

解决方法:注意命名,或者把循环放进函数里隔离。

坑3:在嵌套函数里修改外层变量

csharp 复制代码
def outer():
    x = 10
    
    def inner():
        x = 20   # 创建了inner的局部变量
    
    inner()
    print(x)  # 10------没变

解决方法 :用nonlocal声明。

这些坑确实存在,但它们都和Python的设计一致------赋值即声明,在哪里赋值就在哪里生效。理解了这一点,这些坑就不难避免。


类型注解:Python的新"声明"

虽然Python不需要声明关键字,但从Python 3.5开始,引入了类型注解(Type Hints)

python 复制代码
name: str = "张三"
age: int = 18
items: list[str] = ["a", "b", "c"]

def greet(name: str) -> str:
    return f"你好,{name}"

这看起来有点像"声明变量",但它不是强制性的

  • 类型注解只是提示,Python解释器会忽略它们
  • 静态类型检查工具(mypy、Pylance)可以利用这些注解做检查
  • 你不写类型注解,代码照样运行

这很Python:给你工具,但不强迫你用

如果你想让变量"不可变",目前Python仍然没有内置机制。但第三方工具(如pydanticattrs)可以帮你实现类似const的效果。


一张表总结各语言的做法

语言 声明关键字 作用域规则 需要类型吗?
Python 无(赋值即声明) 函数级作用域 否(注解可选)
JavaScript var/let/const let/const是块级,var是函数级
Java 类型在前 块级
C 类型在前 块级
Go var/:= 块级
Rust let/let mut 块级
C++ 类型在前/auto 块级

Python在这张表里是独一份的:没有任何声明关键字


如果Python引入var会怎样?

这是一个思想实验。假设Python加了var关键字:

ini 复制代码
var x = 10   # 现在的写法是 x = 10

会发生什么?

  • 现有代码全部不兼容(上百万个Python项目都要改)
  • 开发者面临两套写法:x = 10var x = 10,选哪个?
  • 语言的简洁性大打折扣
  • 并没有解决任何真实问题(Python的赋值已经很好用了)

没有任何好处,全是坏处。所以Python永远不会加varlet


为什么Python的设计"行得通"?

一个重要原因是:Python是强类型动态语言

  • 动态类型:变量不需要声明类型,Python在运行时能自动判断
  • 强类型:不同类型不会自动转换,减少了隐式错误

这两个特点结合起来,让"赋值即声明"变得安全且方便。

你可以随时给变量赋任何类型的值:

ini 复制代码
x = 10          # 整数
x = "hello"     # 字符串------合法
x = [1,2,3]     # 列表------合法

如果你用Java或C,这不行------类型在声明时就固定了,不能随意更换。

而Python灵活的类型系统,配合"赋值即声明",让代码非常灵活,特别适合快速原型开发。


一个实战例子:从JavaScript到Python

如果你从JavaScript转Python,记住一个简单的转换规则:

ini 复制代码
// JavaScript
var name = "张三";     // 函数级作用域
let age = 18;         // 块级作用域
const PI = 3.14;      // 不可变常量
ini 复制代码
# Python
name = "张三"          # 变量(作用域由赋值位置决定)
age = 18              # 变量
PI = 3.14             # 变量(按约定视为常量)

写Python的时候,你不需要想"用var还是let"?直接写名字就行。

但你需要想另外一件事:这个变量是在函数里赋值,还是在函数外赋值?这决定了它是局部变量还是全局变量。


最后总结

为什么Python不用var或let?

  1. 设计哲学:赋值即声明,简洁明了,减少冗余语法
  2. 历史因素:Python从一开始就这么设计,没有历史包袱
  3. 避免复杂 :JavaScript有var/let/const三种规则,Python不需要
  4. 动态类型:类型在运行时确定,不需要提前声明
  5. 实用主义:没必要引入新关键字来解决不存在的问题

记住一句话

在Python里,你不需要告诉Python"我要声明一个变量",你只需要告诉它"这个变量叫什么名字,它指向什么值"。赋值本身就是声明。

如果你习惯了varlet,刚开始可能会不习惯。但写一段时间Python后,你会感受到"赋值即声明"带来的流畅感------少敲几个关键字,想写的逻辑直接写出来。

这就是Python的风格:让你专注于做什么,而不是纠结于怎么声明

相关推荐
赴星半途1 小时前
NestJS实战-创建AuthService
后端
北冥有鱼1 小时前
mqtt 测试
前端·后端
代码丰1 小时前
使用 TtlExecutors 解决线程池中的 ThreadLocal 上下文丢失问题
后端
阿祖zu2 小时前
别再优化 RAG 了,适配 Agent 的 LLM Wiki 知识库理念
前端·后端·aigc
昵称为空C3 小时前
手撸一个动态 SQL 执行引擎:不重启服务,在线增删改查任意数据库
spring boot·后端
用户8356290780513 小时前
用 Python 自动化 PowerPoint 演讲者备注添加
后端·python
神奇小汤圆3 小时前
科研神器再升级!Claude Code 全套 Skills,16 大科研场景全覆盖!
后端
tyung3 小时前
Go 手写有界 SPSC 环形队列:无 CAS、无锁、Cache 友好的无锁模型
后端·go
咕白m6253 小时前
使用 C# 在 Excel 中应用多种字体样式
后端·c#