免费编程软件「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为什么不用var或let来声明变量? 没有声明关键字,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少打了var、let、const、分号,代码更短。这符合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有var、let、const来处理作用域,Python什么都没有,它怎么区分局部和全局?
Python的规则很简单:赋值决定作用域。
看例子:
ini
x = 10 # 全局变量
def func():
y = 20 # 局部变量,因为这里赋值了
print(x) # 可以读全局的x
func()
print(y) # 报错!y是func的局部变量
Python在编译函数时,看到y = 20这个赋值语句,就把y标记为局部变量。函数内的所有y都指向这个局部变量,除非你用了global或nonlocal声明。
这和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引入了let和const
ini
if (true) {
let y = 10;
}
console.log(y); // 报错!y is not defined
终于有了块级作用域,但代价是语言变得更复杂了。现在JavaScript有三种声明变量的方式,各有各的规则。
Python避免了这一切
从一开始,Python就没有这些历史包袱。它的设计是统一的:
- 赋值就是声明,没有两步操作
- 作用域由赋值位置决定,规则清晰
- 不存在变量提升,使用前必须赋值
Python不需要var或let,因为它从一开始就设计对了。
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仍然没有内置机制。但第三方工具(如pydantic、attrs)可以帮你实现类似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 = 10和var x = 10,选哪个? - 语言的简洁性大打折扣
- 并没有解决任何真实问题(Python的赋值已经很好用了)
没有任何好处,全是坏处。所以Python永远不会加var或let。
为什么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?
- 设计哲学:赋值即声明,简洁明了,减少冗余语法
- 历史因素:Python从一开始就这么设计,没有历史包袱
- 避免复杂 :JavaScript有
var/let/const三种规则,Python不需要 - 动态类型:类型在运行时确定,不需要提前声明
- 实用主义:没必要引入新关键字来解决不存在的问题
记住一句话:
在Python里,你不需要告诉Python"我要声明一个变量",你只需要告诉它"这个变量叫什么名字,它指向什么值"。赋值本身就是声明。
如果你习惯了var或let,刚开始可能会不习惯。但写一段时间Python后,你会感受到"赋值即声明"带来的流畅感------少敲几个关键字,想写的逻辑直接写出来。
这就是Python的风格:让你专注于做什么,而不是纠结于怎么声明。