Python 基础体系 · 第 6/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。
Python 数值类型:int、float、complex、bool 与运算边界
Python 3.14 内置了三种主要数值类型:int、float 和 complex;bool 并列看似是逻辑类型,但在类型层次上是 int 的子类。理解它们不能只停留在“整数、浮点数、复数、布尔值”的分类上,还必须继续追踪三个问题:
- 一个字面量最终创建了什么类型的对象;
- 不同数值类型混合运算时如何转换;
- 运算结果在哪些地方不再满足直觉中的数学规则。
Python 的数值语义同时受到数学模型、对象类型和具体浮点表示的影响。int 可以保持任意精度,float 通常受机器浮点格式限制,complex 的实部和虚部又分别依赖浮点数;bool 虽然只有两个值,却能够参与整数运算。(docs.python.org)
1. 先建立类型模型:值、类型与数值协议
在 Python 中,变量本身没有固定的“数值槽位类型”。变量只是绑定到对象:
x = 10
print(type(x)) # <class 'int'>
x = 10.0
print(type(x)) # <class 'float'>
x = 10 + 2j
print(type(x)) # <class 'complex'>
因此,x 并不是先声明为整数再“变成”浮点数;而是先绑定整数对象,随后重新绑定浮点数对象。
可以用一个简化的数值层次理解内置类型:
bool
└── int
└── Real
└── Complex
这里的 Real、Complex 是 numbers 模块中的抽象基类概念,不表示对象会被自动存储成某种统一格式。它们主要用于描述一个对象支持哪些数值行为。
from numbers import Integral, Real, Complex
print(isinstance(True, Integral)) # True
print(isinstance(10, Integral)) # True
print(isinstance(10.0, Real)) # True
print(isinstance(1 + 2j, Complex)) # True
bool 是 int 的子类,因此:
print(isinstance(True, int)) # True
print(True == 1) # True
print(False == 0) # True
print(type(True) is int) # False
print(type(True) is bool) # True
这里必须区分两件事:
isinstance(True, int)询问的是继承关系;type(True) is int询问的是精确类型。
bool 只有 True 和 False 两个实例值,且在需要整数的上下文中分别表现为 1 和 0。这解释了为什么 True + 2 的结果是 3,但不能因此把所有布尔值都当成普通整数使用。(docs.python.org)
2. 字面量如何决定数值类型
2.1 整数字面量:十进制、二进制、八进制与十六进制
不含小数点和指数的数字字面量会创建 int:
a = 42
b = 0b101010
c = 0o52
d = 0x2A
print(a, b, c, d)
print({type(a), type(b), type(c), type(d)})
输出:
42 42 42 42
{<class 'int'>}
不同进制只是源码表示不同,运行时都得到整数值。下划线可以提高长数字的可读性:
budget = 1_000_000
mask = 0b1111_0000
下划线不会改变数值:
print(1_000_000 == 1000000) # True
整数字面量本身没有固定的 32 位或 64 位上限。Python 的 int 具有任意精度,算术运算会在需要时使用更多空间,而不是像固定宽度整数那样自然回绕。(docs.python.org)
n = 10 ** 100
print(len(str(n))) # 101
这段代码中的 n 是精确整数,不是近似值。
2.2 浮点数字面量:小数点或指数会产生 float
含有小数点或指数标记的数字字面量会创建 float:
x = 3.14
y = 314e-2
z = 1.
print(type(x)) # <class 'float'>
print(x == y) # True
print(type(z)) # <class 'float'>
科学计数法中的 e 或 E 表示十进制指数:
但 float 的内部表示通常是二进制浮点数。十进制小数是否能被二进制有限位精确表示,取决于其分母约分后是否只含因子 2。
例如:
可以精确表示;而:
包含因子 5,通常不能用有限长度的二进制小数精确表示。
print(0.1 + 0.2) # 0.30000000000000004
print((0.1 + 0.2) == 0.3) # False
这不是 Python 独有的语法错误,而是有限精度二进制浮点表示的结果。可以通过 as_integer_ratio() 查看某个 float 实际代表的精确有理数:
print((0.1).as_integer_ratio())
典型输出:
(3602879701896397, 36028797018963968)
也就是说,0.1 这个 float 实际保存的是:
而不是数学上的精确 。float.as_integer_ratio() 返回的是与当前浮点值完全相等的最简整数比;对无穷大和 NaN 使用该方法会分别产生 OverflowError 和 ValueError。(docs.python.org)
2.3 复数字面量:j 表示虚部
Python 使用 j 或 J 表示虚数单位:
z = 3 + 4j
print(type(z)) # <class 'complex'>
print(z.real) # 3.0
print(z.imag) # 4.0
这里的数学含义是:
Python 代码中写作 j,但其数学意义仍然是虚数单位 。
3j 是实部为零的复数:
print(3j) # 3j
print((3j).real) # 0.0
print((3j).imag) # 3.0
整数或浮点数与复数相加时,会被纳入复数运算:
print(2 + 3j) # (2+3j)
print(2.5 + 3j) # (2.5+3j)
复数的实部和虚部都是浮点数,因此即使输入实部使用整数,读取 .real 和 .imag 时也会得到 float。(docs.python.org)
2.4 bool 不是由 0 和 1 字面量创建的
a = 0
b = False
print(type(a)) # <class 'int'>
print(type(b)) # <class 'bool'>
False 与 0 比较相等,是因为布尔类型继承自整数类型并遵循相应的数值关系;但二者仍然是不同类型的对象。
3. int:任意精度不等于无成本
3.1 任意精度整数如何表现
固定宽度整数常见这样的行为:
最大值 + 1 -> 最小值
Python int 不会因为普通算术自然溢出:
n = 2 ** 1000
print(n > 0) # True
但是“任意精度”只表示数值范围不受固定机器字长直接限制,并不表示操作没有资源边界。整数越大,所需内存越多,加法、乘法、转换为字符串等操作也可能变慢。
n = 10 ** 1_000_000
这类代码可能消耗大量内存和 CPU。生产系统仍然需要根据输入大小设置资源限制。
3.2 除法:/ 总是产生浮点结果
对整数使用 /,结果是 float:
print(5 / 2) # 2.5
print(type(5 / 2)) # <class 'float'>
这意味着整数除法可能在转换到浮点时遇到边界:
n = 10 ** 400
try:
print(n / 1)
except OverflowError as exc:
print(type(exc).__name__, exc)
在常见 Python 实现中,整数与浮点数进行混合算术时,需要把整数转换到浮点数表示;如果整数超出浮点数可表示范围,就可能产生 OverflowError。整数本身仍然可以保存该值,失败发生在“转换为浮点参与运算”这一步。
如果业务要求结果仍是精确整数,应使用 //、divmod() 或分数模型,而不是先使用 / 再转换。
3.3 // 是向负无穷取整,不是向零截断
Python 的整除定义为:
并且要求余数与除数同号或为零。对于正数,结果和“截去小数部分”相同:
print(5 // 2) # 2
print(5 % 2) # 1
负数时差异变得重要:
print(-5 // 2) # -3
print(-5 % 2) # 1
推导过程:
向负无穷取整得到:
为了满足恒等式:
所以余数是 1。
如果想要向零截断,应使用 int() 或 math.trunc():
import math
print(int(-2.5)) # -2
print(math.trunc(-2.5)) # -2
print(math.floor(-2.5)) # -3
print(math.ceil(-2.5)) # -2
四者的语义不同:
| 操作 | -2.5 的结果 |
|---|---|
int(-2.5) |
-2 |
math.trunc(-2.5) |
-2 |
math.floor(-2.5) |
-3 |
math.ceil(-2.5) |
-2 |
Python 对 // 的定义是向负无穷方向取整;从 float 转换到 int 则是丢弃小数部分,也就是向零截断。(docs.python.org)
3.4 % 与 divmod() 应成对理解
q, r = divmod(-5, 2)
print(q) # -3
print(r) # 1
print(q * 2 + r) # -5
divmod(x, y) 等价于:
(x // y, x % y)
对于分页、分桶、周期索引等算法,负数输入是否允许必须明确。直接把 % 当作“数学余数”往往会掩盖符号约定;在 Python 中,-5 % 2 为 1 是语言定义的结果,不是实现偶然行为。(docs.python.org)
3.5 幂运算与负指数
整数的非负整数次幂仍然是整数:
print(2 ** 10) # 1024
print(type(2 ** 10)) # <class 'int'>
但负指数会产生浮点数:
print(2 ** -1) # 0.5
print(type(2 ** -1)) # <class 'float'>
原因是:
而普通 / 运算的结果类型是 float。
Python 定义 0 ** 0 为 1:
print(0 ** 0) # 1
这遵循编程语言中常用的幂运算约定,并不意味着所有数学语境都把 当作无争议的数学值。(docs.python.org)
4. 位运算:Python int 的“无限符号位”模型
位运算只适用于整数:
a = 0b1100
b = 0b1010
print(bin(a & b)) # 0b1000
print(bin(a | b)) # 0b1110
print(bin(a ^ b)) # 0b0110
print(bin(~a)) # -0b1101
Python 对整数位运算的定义,可以理解为使用“带无限符号扩展位的二进制补码”计算。对于非负数,这与直觉中的二进制位操作一致;对负数,不能简单套用固定 8 位或 32 位的位图。
一元按位取反满足:
因此:
print(~5) # -6
print(~-6) # 5
移位也有明确的数学含义:
print(5 << 3) # 40
print(40 >> 3) # 5
print(-5 >> 1) # -3
左移:
右移:
所以负数右移仍然是向负无穷取整:
负移位数量非法:
try:
1 << -1
except ValueError as exc:
print(type(exc).__name__) # ValueError
Python 不会把整数位宽自动限制为 8 位、32 位或 64 位;如果协议、文件格式或硬件寄存器要求固定宽度,必须显式使用掩码或字节转换。(docs.python.org)
5. float:有限精度、舍入、无穷大与 NaN
5.1 机器浮点数的基本边界
Python 文档说明,float 通常使用 C 语言中的双精度浮点格式;当前解释器的有效精度、最大值、最小正规格等信息可通过 sys.float_info 查询。(docs.python.org)
import sys
print(sys.float_info.max)
print(sys.float_info.epsilon)
print(sys.float_info.dig)
这些字段描述的是当前运行环境中的浮点能力,而不是 Python 语言对所有平台强制规定的相同硬件格式。因此,需要依赖浮点边界时,应在目标环境检查,而不要硬编码假设。
5.2 为什么 0.1 + 0.2 不等于 0.3
浮点数运算通常可以抽象为:
其中:
- 、 是浮点近似值;
- 是加法、乘法等运算;
- 表示将真实结果舍入到可表示的浮点数。
因此,下面的过程不是:
而更接近:
如果程序需要判断“足够接近”,应根据误差模型使用 math.isclose(),而不是盲目使用固定小数位比较:
import math
result = 0.1 + 0.2
print(math.isclose(result, 0.3)) # True
但“接近”不是“相等”。例如金额、计量结果或协议字段的精度要求必须由业务规则决定,不能由 math.isclose() 的默认容差替代。
5.3 round() 不是传统的“逢五进一”
Python 的 round() 对半数采用“舍入到偶数”:
print(round(2.5)) # 2
print(round(3.5)) # 4
print(round(1.25, 1)) # 1.2
print(round(1.35, 1)) # 1.4
对 2.5:
- 相邻整数是
2和3; 2是偶数;- 所以结果为
2。
对 3.5:
- 相邻整数是
3和4; 4是偶数;- 所以结果为
4。
但是 round(1.35, 1) 的行为还受到 1.35 本身并非通常意义上的精确十进制值这一事实影响。因此不能把 round(x, 2) 理解为“先精确得到十进制金额,再按两位舍入”。
round() 在不提供 ndigits 时返回整数;提供 ndigits 时通常返回与输入数值类型相同或相容的结果。对于要求明确十进制舍入模式的场景,应使用 decimal.Decimal 配合上下文和舍入模式,而不是依赖二进制 float。(docs.python.org)
5.4 int(float) 是截断,不是四舍五入
print(int(3.9)) # 3
print(int(-3.9)) # -3
int() 丢弃小数部分,方向是向零:
print(int(3.9) == 3)
print(int(-3.9) == -3)
如果需求是:
- 向下取整:
math.floor(); - 向上取整:
math.ceil(); - 向零截断:
int()或math.trunc(); - 按指定舍入规则:
round()或Decimal。
这些操作不能互相替代。
5.5 inf 与 nan 不是普通数字
Python 可以构造无穷大和 NaN:
import math
positive_inf = float("inf")
negative_inf = float("-inf")
not_a_number = float("nan")
print(math.isinf(positive_inf)) # True
print(math.isnan(not_a_number)) # True
NaN 的特殊之处在于,它不等于包括自身在内的任何值:
nan = float("nan")
print(nan == nan) # False
print(nan != nan) # True
因此,不能这样判断 NaN:
if value == float("nan"):
...
应使用:
if math.isnan(value):
...
无穷大也可能传播:
print(float("inf") + 100) # inf
print(float("inf") - float("inf")) # nan
把浮点数转换为整数时,这些特殊值会触发异常:
for value in [float("inf"), float("-inf"), float("nan")]:
try:
print(int(value))
except (OverflowError, ValueError) as exc:
print(type(exc).__name__)
输出通常为:
OverflowError
OverflowError
ValueError
因此,外部输入进入整数边界、数组下标、分页参数或数据库字段前,应明确处理 NaN 和无穷大,而不能只检查 isinstance(value, float)。
5.6 用十六进制形式检查浮点值
由于十进制字符串与二进制浮点之间通常需要舍入,调试浮点边界时可以使用十六进制形式:
x = 0.1
text = x.hex()
print(text)
print(float.fromhex(text) == x) # True
float.hex() 可以给出当前有限浮点值的精确十六进制表示,float.fromhex() 可以恢复同一个浮点值。这种形式适合定位“两个值打印相同但底层表示不同”或跨语言传递浮点值的情况。(docs.python.org)
6. complex:代数运算成立,大小排序不成立
6.1 复数的四则运算
设:
则:
Python 直接实现这些规则:
z1 = 2 + 3j
z2 = 4 - 1j
print(z1 + z2) # (6+2j)
print(z1 * z2) # (11+10j)
乘法推导如下:
因为 ,所以:
Python 将复数的实部和虚部存储为浮点数,因此过大的实部或虚部仍然可能受到浮点范围和精度影响。(docs.python.org)
6.2 复数没有自然的 < 和 >
复数支持相等性比较:
print((1 + 2j) == (1 + 2j)) # True
print((1 + 2j) == (1 + 3j)) # False
但不支持通常意义上的大小排序:
try:
print(1 + 2j < 2 + 3j)
except TypeError as exc:
print(type(exc).__name__) # TypeError
原因不是 Python “缺少一个实现”,而是复数在数学上没有与实数相同的、同时保持通常加法和乘法性质的全序关系。若需要按照复数大小排序,必须先定义业务含义,例如按模长:
values = [3 + 4j, 1 + 1j, 5 + 0j]
ordered = sorted(values, key=abs)
print(ordered)
这里排序依据是模长,而不是复数本身的大小。
6.3 复数不支持 //、% 和 divmod()
try:
print((3 + 4j) // 2)
except TypeError as exc:
print(type(exc).__name__) # TypeError
整除和余数依赖“商向下取整”的顺序概念,而复数没有这种自然顺序。若要计算复数的距离、幅度或阈值,应使用 abs(z);若要进行复数函数运算,可使用 cmath。内置数值操作中,//、% 和 divmod() 不适用于复数。(docs.python.org)
6.4 共轭、模长与实部虚部
复数 的共轭为:
Python:
z = 3 + 4j
print(z.real) # 3.0
print(z.imag) # 4.0
print(z.conjugate()) # (3-4j)
print(abs(z)) # 5.0
模长满足:
因此:
比较复数时,如果业务含义是“距离原点的远近”,应比较 abs(z);如果含义是“实部优先、虚部次之”,则应显式写出元组键:
ordered = sorted(values, key=lambda z: (z.real, z.imag))
不要依赖隐含的复数排序,因为该操作本身会失败。
7. 混合数值运算:窄类型向宽类型提升
Python 对内置数值类型支持混合算术。可以把类型提升概括为:
int + int -> int
int + float -> float
int + complex -> complex
float + complex -> complex
示例:
print(type(1 + 2)) # int
print(type(1 + 2.0)) # float
print(type(1 + 2j)) # complex
print(type(1.0 + 2j)) # complex
规则可以表述为:
- 两个操作数都是
complex:不再转换; - 任一操作数是
complex:另一个转换到复数运算; - 任一操作数是
float:另一个转换到浮点运算; - 否则两个操作数都是整数。
文档使用“更窄类型向更宽类型提升”描述这种行为。(docs.python.org)
需要特别注意:bool 由于是 int 子类,会参与整数级别的运算:
print(True + 2) # 3
print(False * 100) # 0
print(1.5 + True) # 2.5
print((True + 2j)) # (1+2j)
但 bool 不会形成新的“布尔算术类型”。运算结果一般是 int、float 或 complex:
result = True + True
print(result) # 2
print(type(result)) # <class 'int'>
8. 类型转换:语法相似,语义不同
8.1 int() 的输入边界
print(int("42")) # 42
print(int("101", 2)) # 5
print(int(3.9)) # 3
print(int(True)) # 1
字符串转换可以指定进制:
print(int("ff", 16)) # 255
print(int("0b101", 2)) # 5
但浮点转整数是截断,不是舍入。非法字符串会产生 ValueError:
try:
int("3.14")
except ValueError as exc:
print(type(exc).__name__) # ValueError
如果输入可能来自用户或网络,不应直接假设 int() 一定成功。
8.2 float() 的输入边界
print(float("3.14")) # 3.14
print(float("inf")) # inf
print(float("nan")) # nan
print(float(10)) # 10.0
但大整数转浮点可能超出浮点范围:
n = 10 ** 400
try:
print(float(n))
except OverflowError as exc:
print(type(exc).__name__)
float() 的成功只说明输入可以映射到一个浮点对象,不说明转换保持了原整数的全部精度。
Python 3.14 新增了 float.from_number(x)。它面向数值对象,优先调用 x.__float__(),若没有则回退到 x.__index__();整数或浮点数转换时仍然受到 Python 浮点精度与范围限制。(docs.python.org)
print(float.from_number(10)) # 10.0
这是 Python 3.14 的版本敏感能力;编写兼容旧版本的库时,不能无条件假设该类方法存在。
8.3 complex() 的两种主要形式
可以传入实部和虚部:
print(complex(3, 4)) # (3+4j)
也可以解析复数字符串:
print(complex("3+4j")) # (3+4j)
需要注意,字符串形式必须符合复数字面量的语法;外部输入不符合格式时会失败。
Python 3.14 还新增了 complex.from_number(x)。它会优先使用 x.__complex__(),再依次回退到 __float__() 和 __index__()。(docs.python.org)
9. 比较、相等与哈希:数值相等不等于类型相同
9.1 数值类型之间可以比较相等性
print(1 == 1.0) # True
print(1 == True) # True
print(0 == False) # True
print(1 + 0j == 1) # True
Python 对不同数值类型的比较,按照它们代表的数值进行比较,而不是要求类型完全一致。(docs.python.org)
但是,== 和 is 完全不同:
x = 1000
y = 1000
print(x == y) # True:值相等
print(x is y) # 不应依赖:是否为同一个对象
数值判断通常使用 ==;对象身份判断才使用 is。不要用对象身份判断两个数字是否相等。
9.2 相等的数值必须具有相同哈希值
Python 要求:
因此:
print(hash(1) == hash(1.0)) # True
print(hash(True) == hash(1)) # True
这会影响字典键和集合:
data = {
1: "integer",
True: "boolean",
1.0: "float",
}
print(data)
print(len(data))
典型结果是字典只有一个键,因为 1、True 和 1.0 既相等又具有相同哈希值,后写入的值会覆盖先写入的值。
因此,如果系统必须区分“整数 1”“布尔 True”和“浮点 1.0”,不能只把它们直接作为字典键;应显式加入类型信息:
def typed_key(value):
return type(value), value
data = {
typed_key(1): "integer",
typed_key(True): "boolean",
typed_key(1.0): "float",
}
print(data)
复数的哈希则由实部和虚部的哈希组合得到。数值哈希规则是跨 int、float、Fraction、Decimal 等数值类型协调设计的。(docs.python.org)
10. 布尔运算与数值真值测试
任何对象都可以进行真值测试。对于数值类型,零值为假,非零值为真:
for value in [0, 0.0, 0j, 1, -1, 0.1]:
print(value, bool(value))
输出:
0 False
0.0 False
0j False
1 True
-1 True
0.1 True
特别要注意:
print(bool(float("nan"))) # True
print(bool(float("inf"))) # True
NaN 虽然不是一个正常的数值,但它不是零,因此真值测试结果为 True。如果业务需要判断“是否存在有效测量值”,应单独检查 math.isnan() 和 math.isfinite()。
and 与 or 返回的是操作数本身,不一定返回布尔值:
print(0 or 5) # 5
print(3 and 5) # 5
print(0 and 5) # 0
规则是:
x or y:如果x为真,返回x;否则返回y;x and y:如果x为假,返回x;否则返回y。
它们还具有短路求值特性:不需要计算的右操作数不会执行。(docs.python.org)
这段代码中的 or 不是“把两个值转换后做逻辑或”:
timeout = configured_timeout or 30
如果 configured_timeout 为 0,它会被当作假值并替换为 30。如果 0 是合法配置,这段代码就改变了业务含义,应改为显式判断 None:
timeout = 30 if configured_timeout is None else configured_timeout
11. 运算优先级:先确认语法树,再解释结果
数值边界有时不是类型问题,而是表达式分组问题。
print(-2 ** 2) # -4
print((-2) ** 2) # 4
Python 将第一句理解为:
-(2 ** 2)
而不是:
(-2) ** 2
幂运算还具有右结合性:
print(2 ** 3 ** 2) # 512
print(2 ** (3 ** 2)) # 512
它不是:
(2 ** 3) ** 2 # 64
当表达式同时包含负号、幂、位运算和比较时,应使用括号表达意图:
result = (-base) ** exponent
mask = (value & MASK) == expected
括号不仅提高可读性,也避免把运算优先级误认为数学记号中的默认分组。
12. 整数、浮点数和复数的边界对照
| 类型 | 主要表示 | 精度特征 | 支持排序 | 典型失败边界 |
|---|---|---|---|---|
int |
整数 | 任意精度 | 支持 | 内存、时间、转浮点溢出 |
float |
二进制浮点数 | 有限精度 | 支持,但 NaN 会破坏直觉 | 舍入误差、inf、nan |
complex |
实部与虚部 | 两个浮点分量 | 不支持 <、> |
无序比较、浮点分量范围 |
bool |
True / False |
语义上是逻辑值,类型上是 int 子类 |
支持整数关系 | 隐式参与算术、字典键碰撞 |
这个表不能替代具体语义。例如,int 的“任意精度”并不意味着可以安全地转换为 float;float 的“支持排序”也不意味着含 NaN 的数据可以直接获得全序;bool 的“是整数子类”也不意味着应该用 True 代替所有数值 1。
13. 真实失败路径与诊断方法
13.1 需要精确整数,却意外进入浮点
n = 10 ** 400
try:
result = n / 1
except OverflowError as exc:
print("浮点转换失败:", type(exc).__name__)
诊断思路:
- 确认原始值是否仍为
int; - 检查表达式中是否出现
/、浮点字面量或float(); - 如果目标是精确商,改用
Fraction、divmod()或整数缩放; - 如果目标确实是浮点结果,先定义超范围时的业务处理。
13.2 浮点比较失败
actual = 0.1 + 0.2
expected = 0.3
print(actual == expected) # False
诊断代码:
import math
print(actual.hex())
print(expected.hex())
print(actual - expected)
print(math.isclose(actual, expected))
如果结果用于控制流程,应明确:
- 是要求数学上严格相等;
- 还是允许绝对误差;
- 还是允许相对误差;
- 是否必须排除 NaN 和无穷大。
math.isclose() 适合近似比较,但不能自动解决单位错误、累计误差过大或错误的舍入时机。
13.3 复数排序失败
values = [1 + 2j, 2 + 1j]
try:
print(sorted(values))
except TypeError as exc:
print(type(exc).__name__) # TypeError
修复方式取决于领域定义:
print(sorted(values, key=abs))
print(sorted(values, key=lambda z: (z.real, z.imag)))
第一种按模长排序,第二种按实部和虚部的字典序排序。二者都不是“复数自然排序”,而是程序员明确选择的投影或排序规则。
13.4 NaN 进入统计或过滤流程
values = [1.0, 2.0, float("nan"), 4.0]
print(max(values)) # 结果不应被当作可靠统计结论
含 NaN 的比较会产生非直觉结果,统计函数是否传播 NaN 也取决于具体 API。进入计算前应先清洗:
import math
valid_values = [
value for value in values
if math.isfinite(value)
]
print(valid_values) # [1.0, 2.0, 4.0]
如果无穷大在业务中是合法哨兵值,则不能简单使用 isfinite() 丢弃它,而应分别定义 NaN、正无穷和负无穷的业务语义。
14. 何时不应继续使用 float
float 适合:
- 科学计算中的近似值;
- 图形、信号和测量数据;
- 允许误差的比例、坐标和物理量;
- 对性能和硬件浮点运算友好的场景。
但以下场景通常需要其他数值模型:
14.1 货币和十进制定点金额
from decimal import Decimal
price = Decimal("0.10")
quantity = Decimal("3")
print(price * quantity) # 0.30
关键在于从字符串构造 Decimal,而不是先构造 float:
print(Decimal("0.1"))
print(Decimal(0.1))
后者会把已经存在的二进制浮点近似值带入 Decimal。Decimal 的精度、舍入模式和上下文适合需要十进制规则的场景,但它并不是本文四种内置类型之一,且运算混用时需要显式处理类型边界。
14.2 需要精确有理数
from fractions import Fraction
value = Fraction(1, 10) + Fraction(2, 10)
print(value) # 3/10
print(float(value)) # 0.3
Fraction 可以保持有理数的精确分子和分母;转换为 float 的瞬间,仍会回到浮点精度边界。
这也是数值模型选择的核心:先决定“值的数学含义”,再决定存储类型,而不是看到输入有小数就自动使用 float。
15. 与随机数和密码学边界的连接
数值类型还会影响随机数 API 的使用方式。
普通伪随机数常用于模拟、抽样和测试:
import random
random.seed(1234)
print(random.randrange(10))
这里的整数参数表示范围边界,随机状态由伪随机数生成器维护;固定种子通常用于复现实验,不应被误认为安全密钥来源。
需要生成密码、令牌或安全随机数时,应使用 secrets:
import secrets
token = secrets.token_urlsafe(32)
print(token)
密码学安全随机数的核心不是“返回一个很大的 int”,而是随机源和生成器的安全性质。random 产生的结果即使输出类型是 int,也不能因此获得密码学安全性。
随机抽样中的边界也必须区分:
import random
print(random.randrange(5)) # 可能是 0、1、2、3、4
右端点 5 不包含在结果中;如果把它误当作闭区间,就会出现越界或分布错误。这个边界属于 API 语义,但最终仍然通过 int 的范围和比较规则表现出来。
16. 一组可执行的自检代码
下面的代码集中验证本文最容易混淆的边界:
import math
from decimal import Decimal
from fractions import Fraction
# 类型
print(type(1).__name__) # int
print(type(1.0).__name__) # float
print(type(1 + 2j).__name__) # complex
print(type(True).__name__) # bool
# bool 是 int 的子类
print(isinstance(True, int)) # True
print(True + 2) # 3
# 混合运算
print(type(1 + 1.0).__name__) # float
print(type(1 + 1j).__name__) # complex
# 整除与余数
print(-5 // 2, -5 % 2) # -3 1
print(divmod(-5, 2)) # (-3, 1)
# 浮点误差
print(0.1 + 0.2 == 0.3) # False
print(math.isclose(0.1 + 0.2, 0.3)) # True
# 舍入
print(round(2.5)) # 2
print(round(3.5)) # 4
# 复数
z = 3 + 4j
print(z.real, z.imag) # 3.0 4.0
print(abs(z)) # 5.0
# NaN
nan = float("nan")
print(math.isnan(nan)) # True
print(nan == nan) # False
# Decimal 与 Fraction
print(Decimal("0.1") + Decimal("0.2")) # 0.3
print(Fraction(1, 10) + Fraction(2, 10)) # 3/10
运行这些示例时,重点不是记住每一行输出,而是观察每个结果背后的转换路径:
bool参与整数运算;/把整数计算带入浮点模型;//依赖向负无穷取整;float可能不精确表示十进制小数;complex支持代数运算但不支持自然排序;Decimal和Fraction通过不同方式解决特定精度问题。
Python 数值类型的真正边界,不在于类型名称本身,而在于“数学值如何被表示、转换和比较”。int 保证整数运算的任意精度,却不能保证转换到 float 后仍然精确;float 提供高效的近似实数,却可能产生舍入误差、NaN 和无穷大;complex 扩展了代数运算,却放弃了实数意义下的大小排序;bool 只有逻辑语义,却由于继承关系能够进入整数计算。
当表达式跨越这些边界时,应先写出需要保持的性质:精确整数、近似实数、十进制金额、有理数、复数模长、逻辑真假,还是固定宽度字节表示。只有先确定性质,再选择类型和运算符,代码的结果才不会被 Python 的隐式转换和数学直觉之间的差异所左右。
系列导航与关联阅读
- 系列入口:Python 完整学习路线:从语言模型、并发到 Web、数据、AI 与生产交付
- 上一篇:Python 名称、对象与绑定:变量不是盒子,赋值不是复制
- 下一篇:Python 字符串、bytes 与 Unicode:编码、解码和文本边界
- 延伸:Python Decimal 与 Fraction:精度、舍入、上下文和金融计算
- 延伸:Python 随机数与 secrets:伪随机、采样、种子和密码学边界
官方资料
本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论