page contents

图解 Python:变量不声明类型,计算机怎么知道 age 是整数?

因为环境变量读出来的是字符串 "30",不是整数 30。Python 不会在 timeout 第一次出现时,要求你提前声明它的类型;等程序真正执行 timeout + 5,解释器才根据两个对象当时的类型判断这次运算是否成立。

attachments-2026-08-z8E3VXv96a792e0534191.png凌晨两点,一段巡检脚本突然报错:

import os
timeout = os.getenv("TIMEOUT", "30")
next_check = timeout + 5  # 运行到这里触发 TypeError

配置里明明写的是 30,为什么不能加 5?

因为环境变量读出来的是字符串 "30",不是整数 30。Python 不会在 timeout 第一次出现时,要求你提前声明它的类型;等程序真正执行 timeout + 5,解释器才根据两个对象当时的类型判断这次运算是否成立。

这正是很多人从 Java、C 或 C++ 转到 Python 时最不习惯的地方:

int age = 18;

age = 18

Python 少写了一个 int,计算机怎么还知道 age 是整数?

答案先放在这里:「Python 没有取消类型。18 是一个 int 对象,age 是绑定到这个对象的名称。」

01 少写了 int,类型并没有消失

 

Java 和 Python 都有类型系统,差别在于类型约束放在哪里、什么时候检查。

在 Java 中:

int age = 18;

变量 age 的声明类型在编译期已经确定。后面如果写:

age = "18";

编译器会拒绝这次赋值,因为字符串与 int 不兼容。

在 Python 中:

age = 18

名称 age 没有被永久声明成 int。类型信息在它当前绑定的对象 18 上。程序执行加法、比较或方法调用等具体操作时,再依据对象的实际类型处理。

所以,“Python 变量没有类型”这句话容易造成误解。更准确的说法是:

Python 的对象有类型,名称通常没有被固定为某一种声明类型。

 

02 Java 的“盒子”模型,要加一个边界

 

为了入门,我们常把 Java 的基本类型变量画成一个带门禁的盒子:

名称:age
声明类型:int
当前值:18

这个盒子只接收与 int 兼容的值。它很好地表达了语言规则,但不是 JVM 真实内存布局的截图。实际运行时,局部变量可能使用虚拟机槽位,也可能被优化到寄存器里。

Java 的引用类型变量也能改绑到另一个对象,只是新对象仍要满足已经声明的类型约束。即使使用类型推断,规则也没有改变:

var age = 18;   // 编译器推断为 int
age = "18";    // 仍然无法通过编译

Python 采用的是另一套语言模型:名称与对象分开,名称通过引用关系绑定对象。图里的箭头表示“绑定”,不等同于一个可以在 Python 代码中直接操作的原始内存地址。

03 age = 18到底做了什么

 

执行下面这行代码时:

age = 18

从语言层面,可以拆成两个动作:

先对等号右侧求值,得到一个值为 18、类型为 int 的对象;

再让当前名称空间中的名称 age 绑定到这个对象。

这里说“得到对象”,可能是创建新对象,也可能是复用已有对象;那属于具体 Python 实现的细节,不影响赋值语义。

现在看这段完整代码:

age = 18

print(age)
print(type(age).__name__)

输出:

18
int

type(age) 不是在查询“名称 age 被声明成了什么类型”,而是在查询:age 当前绑定的对象是什么类型。

04 x为什么能先是整数,再是字符串

 

来看一段更能说明问题的代码:

x = 10
y = x
x = "hello"

print(x)
print(y)

输出:

hello
10

执行 y = x 后,x 和 y 都绑定着整数对象 10。随后执行 x = "hello",只是让 x 改绑到字符串对象 "hello";y 仍然绑定原来的整数对象。

整数 10 没有变成字符串。变化的是名称 x 与对象之间的引用关系。

这就是动态类型最核心的机制:

同一个名称可以在不同时间绑定不同类型的对象,对象自身的类型不会被这次改绑改变。

 

05 动态类型,不等于随便混用

 

名称可以动态改绑,不代表不同类型可以任意混算。

下面的示例可以直接运行:

print(10 + 1)

try:
    print(10 + "1")
except TypeError as error:
    print(type(error).__name__)

print(10 + int("1"))

输出:

11
TypeError
11

第一行执行整数加法;第二次运算遇到 int + str,Python 不会偷偷把字符串 "1" 猜成整数,于是抛出 TypeError;显式执行 int("1") 后,运算才成立。

Python 因此通常被称为动态、较强类型语言:

动态,指类型信息跟随对象,相关检查主要发生在运行时;

较强类型,指不相关的类型通常不会为了完成运算而被随意、悄悄地转换。

“强类型”并没有一套适用于所有语言的严格统一定义,Python 也存在有规则的数值兼容,例如 1 + 2.5会得到 3.5。这里真正需要记住的是:动态类型和没有类型不是一回事。

06 灵活从哪里来,代价又在哪里

 

动态类型让 Python 代码简洁、组合灵活,写自动化脚本、运维工具和数据处理程序时,开发节奏通常很快。

但它也把一部分问题推迟到了具体代码路径真正运行的时候。没有被测试覆盖的异常分支,可能直到线上收到特殊配置或接口数据才报错。

开头的巡检脚本,应该在外部数据进入程序的边界完成转换:

import os

timeout: int = int(os.getenv("TIMEOUT", "30"))
next_check = timeout + 5

print(next_check)

常用的工程护栏包括:

环境变量、命令行参数、配置文件和接口响应进入程序时,立即校验并转换;

为函数参数、返回值和关键变量添加类型注解;

使用 Pyright、mypy 等静态检查工具;

用自动化测试覆盖真实运行路径。

类型注解很有价值,但它不等于 Java 式的变量声明。Python 解释器默认不会仅凭注解阻止后续赋值:

age: int = 18
age = "eighteen"

print(age)  # 仍会输出 eighteen

编辑器和静态检查器可以提前报告这类不一致;运行时若需要强制校验,则要显式检查,或使用会执行校验的框架。

07 一张图总结

 

现在再回答开头的问题:

age = 18

Python 之所以不要求先写 int,不是因为它不关心类型,而是因为:

18 是一个带有 int 类型的对象;

age 是名称,它与对象建立绑定关系;

名称以后可以重新绑定到其他对象;

真正执行操作时,解释器依据参与对象的类型处理;

类型注解、静态检查与测试,可以帮助部分问题更早暴露。

到这里,一个新问题已经出现:如果 18 是对象,它为什么还能携带方法?

number = 10
print(number.bit_length())

数字有方法,字符串有方法,函数甚至也能被赋值、传递和返回。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2307 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. Pack 2307 文章
  3. 小柒 2228 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章