1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑

为什么 7 / 2 的结果是 3.5,而 7 // 2 的结果却是 3?

为什么 0.1 + 0.2 得不到我们预期的 0.3?

为什么计算磁盘容量、分页数量时,一个运算符用错,结果就可能完全不一样?

这些问题看起来简单,却是编写 Python 程序时经常遇到的细节。尤其是在 Linux 运维脚本中,容量换算、资源统计、日志分析都离不开数值计算。

这一篇,我们就从数值类型开始,逐步搞懂 Python 的运算符、进制表示、类型转换和浮点精度,再通过几个实战例子,把这些知识真正用起来。

上一篇我们学习了 Python 的输入与输出,掌握了 input()、print() 以及 sep、end 等参数。

现在,程序已经能够接收用户输入,也能输出处理结果。接下来,我们要解决一个更重要的问题:如何让程序正确地计算数据?

Python 支持哪些数值类型?

Python 内置了三种主要的数值类型:

类型示例说明
int10、-20整数,支持任意精度
float3.14、-0.5浮点数,通常采用二进制浮点表示
complex3 + 2j复数,由实部和虚部组成

日常编写 Linux 运维脚本时,最常用的是 int 和 float。complex 在一般的服务器管理、日志分析和资源统计中用得相对较少。

先看一个简单的例子:

1
2
3
4
5
6
7
age = 28
price = 19.9
z = 1 + 2j

print(type(age))
print(type(price))
print(type(z))

运行结果:

1
2
3
<class 'int'>
<class 'float'>
<class 'complex'>

这里使用了 type() 函数,用来查看对象的类型。

整数 int

Python 的整数没有固定的 32 位或 64 位精度上限,可以表示非常大的整数,实际限制主要来自可用内存等系统资源。

例如:

1
2
3
4
number = 123456789012345678901234567890

print(number)
print(type(number))

运行结果:

1
2
123456789012345678901234567890
<class 'int'>

对于整数而言,Python 不会仅仅因为数值超过 32 位或 64 位整数的范围,就像某些固定宽度整数类型那样发生自动溢出。

这对于处理计数、容量换算和大整数计算很方便,但也不代表计算大整数没有性能和资源成本。

浮点数 float

浮点数主要用于表示带小数的数值。

1
2
3
4
5
cpu_usage = 78.5
memory_usage = 63.2

print(cpu_usage)
print(memory_usage)

运行结果:

1
2
78.5
63.2

需要注意,Python 的普通 float 通常采用 IEEE 754 双精度二进制浮点格式。这意味着很多十进制小数无法被精确表示,只能存储最接近的二进制浮点值。

后面讲解浮点精度时,我们会专门验证这个问题。

复数 complex

复数由实部和虚部组成,Python 使用 j 表示虚数单位。

1
2
3
4
5
z = 3 + 2j

print(z)
print(z.real)
print(z.imag)

运行结果:

1
2
3
(3+2j)
3.0
2.0

其中:

  • z.real 获取实部。
  • z.imag 获取虚部。

如果没有特殊的数学计算需求,日常运维脚本通常不需要主动使用复数类型。

常用数值运算符详解

数值类型解决了数据如何表示的问题,接下来就是如何对它们进行计算。

Python 支持常见的算术运算符:

运算符名称示例结果
+加法20 + 626
-减法20 - 614
*乘法20 * 6120
/除法20 / 63.333...
//整除20 // 63
%取余20 % 62
**幂运算2 ** 38

我们把这些运算符放在一段代码中运行:

1
2
3
4
5
6
7
8
9
10
x = 20
y = 6

print("x + y =", x + y)
print("x - y =", x - y)
print("x * y =", x * y)
print("x / y =", x / y)
print("x // y =", x // y)
print("x % y =", x % y)
print("x ** y =", x ** y)

运行结果:

1
2
3
4
5
6
7
x + y = 26
x - y = 14
x * y = 120
x / y = 3.3333333333333335
x // y = 3
x % y = 2
x ** y = 64000000

接下来重点讲解几个最容易出错的运算符。

/ 和 // 到底有什么区别?

这是 Python 初学者最容易混淆的地方。

/ 表示普通除法。两个整数使用 / 运算时,结果是浮点数,即使能够整除也是如此。

1
2
print(10 / 2)
print(7 / 2)

运行结果:

1
2
5.0
3.5

而 // 表示向下取整的整除运算。

1
2
print(10 // 2)
print(7 // 2)

运行结果:

1
2
5
3

这里需要特别注意:// 并不是简单地删除小数部分,而是向负无穷方向取整。

看下面这个例子:

1
2
print(7 // 2)
print(-7 // 2)

运行结果:

1
2
3
-4

为什么负数的结果是 -4,而不是 -3?

因为:

  • 7 / 2 = 3.5,向下取整得到 3。
  • -7 / 2 = -3.5,向下取整得到 -4。

如果你需要的是向零截断的整数转换,int() 的行为又有所不同:

1
2
print(int(3.9))
print(int(-3.9))

运行结果:

1
2
3
-3

所以,在计算分页数量、资源分组等场景时,应该先弄清楚自己需要的是向下取整、向零截断,还是其他舍入规则。

% 取余运算

% 用来计算余数。

1
2
3
print(20 % 6)
print(10 % 2)
print(7 % 2)

运行结果:

1
2
3
2
0
1

取余运算在日常开发中非常实用,例如:

  • 判断一个数是否为偶数。
  • 判断任务是否达到指定执行次数。
  • 将数据分组或循环分配。
  • 计算分页过程中剩余的数据量。

例如,判断一个数是不是偶数:

1
2
3
4
5
6
number = 18

if number % 2 == 0:
print("偶数")
else:
print("奇数")

运行结果:

1
偶数

如果一个整数除以 2 的余数为 0,就说明它是偶数。

不过,取余运算 % 也有一个容易让初学者困惑的地方。

1
print(-7 % 2)

运行结果:

1
1

为什么结果是 1,而不是 -1?

这是因为 Python 的整除运算 // 会向负无穷方向取整。对于 -7 // 2,结果是 -4。

Python 的整除和取余运算满足下面的关系:

1
a == (a // b) * b + (a % b)

因此:

1
-7 == (-4) * 2 + 1

也就是 -7 == -8 + 1,所以余数是 1。

记住:Python 的整除和取余是配套的。遇到负数时,不要只凭日常的除法直觉判断余数,可以利用这个等式进行验证。

** 幂运算

** 表示幂运算。

1
2
3
print(2 ** 3)
print(2 ** 10)
print(1024 ** 3)

运行结果:

1
2
3
8
1024
1073741824

其中,1024 ** 3 就是 (1024^3)。

在 Linux 运维场景中,计算 KiB、MiB、GiB 等二进制容量时,经常会用到这种运算。

需要注意,幂运算可能产生非常大的数值。虽然 Python 整数支持任意精度,但计算规模过大仍然可能消耗大量时间和内存。

运算符优先级:为什么计算结果和想象的不一样?

假设我们执行下面这段代码:

1
2
3
result = 2 + 3 * 4

print(result)

运行结果:

1
14

有些初学者可能会认为程序会先计算 2 + 3,然后再乘以 4,得到 20。

实际上,Python 会优先计算乘法,再计算加法。

常见算术运算符的优先级,可以简单记成:

  1. 括号 ()
  2. 幂运算 **
  3. 正负号 +x、-x
  4. 乘法、除法、整除、取余 *、/、//、%
  5. 加法、减法 +、-

例如:

1
2
3
print(2 + 3 * 4)
print((2 + 3) * 4)
print(2 ** 3 * 4)

运行结果:

1
2
3
14
20
32

第一条先计算乘法,第二条先计算括号内的加法,第三条先计算幂运算。

幂运算还有一个容易忽略的细节:

1
print(2 ** 3 ** 2)

运行结果:

1
512

这是因为连续的幂运算按从右向左的方向结合,相当于:

1
print(2 ** (3 ** 2))

实战建议:遇到复杂表达式,不要过度依赖记忆运算符优先级。适当添加括号,可以让代码更清晰,也更容易进行代码审查。

数值类型转换:字符串怎么参与计算?

上一篇我们学习了 input(),知道它始终返回字符串。

这意味着,下面的代码不会得到预期的数学结果:

1
2
3
4
a = input("请输入第一个数字:")
b = input("请输入第二个数字:")

print(a + b)

如果分别输入 10 和 20,运行结果是:

1
2
3
请输入第一个数字:10
请输入第二个数字:20
1020

为什么不是 30?

因为 a 和 b 都是字符串,字符串之间使用 + 表示拼接,而不是数值加法。

需要进行数值计算时,就要进行类型转换。

使用 int() 转换为整数

1
2
3
4
a = int(input("请输入第一个整数:"))
b = int(input("请输入第二个整数:"))

print("两数之和:", a + b)

输入 10 和 20 后,运行结果:

1
2
3
请输入第一个整数:10
请输入第二个整数:20
两数之和: 30

int() 可以将符合要求的字符串转换为整数,也可以将浮点数转换为整数。

但是,浮点数转换为整数时,int() 是向零截断,而不是四舍五入。

1
2
print(int(3.9))
print(int(-3.9))

运行结果:

1
2
3
-3

此外,int("3.9") 会报 ValueError,因为带小数点的字符串不是合法的整数字符串。

使用 float() 转换为浮点数

如果输入的数据可能包含小数,就可以使用 float()。

1
2
3
4
5
6
price = float(input("请输入商品价格:"))
quantity = int(input("请输入购买数量:"))

total = price * quantity

print("总价:", total)

输入价格 19.9,数量 3 后,运行结果为:

1
2
3
请输入商品价格:19.9
请输入购买数量:3
总价: 59.699999999999996

具体浮点结果可能因计算而有所不同,但这个例子引出了另一个重要问题:浮点数并不总能精确表示十进制小数。

输入转换失败怎么办?

生产脚本不能假设用户永远输入正确的数据。

例如,用户在需要整数的地方输入了 abc,程序就会因为 ValueError 而异常退出。

可以使用 try/except 进行处理:

1
2
3
4
5
try:
age = int(input("请输入年龄:"))
print("你的年龄是:", age)
except ValueError:
print("输入错误,请输入有效的整数。")

如果输入 abc,程序会输出:

1
输入错误,请输入有效的整数。

对于需要长期运行的交互式脚本,还可以在发生错误后重新提示用户输入,而不是直接结束程序。

浮点数精度:为什么 0.1 + 0.2 不等于 0.3?

这是 Python 入门过程中非常经典的问题。

先运行下面这段代码:

1
2
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)

运行结果:

1
2
0.30000000000000004
False

明明是 0.1 + 0.2,为什么结果不是我们期待的 0.3?

原因并不是 Python 的加法出错了,而是计算机使用二进制浮点格式表示数值时,很多十进制小数无法被精确表示。

这与我们日常使用十进制表示数字类似:某些数在一种进制中能够有限表示,换成另一种进制却可能需要无限多位。

因此,普通浮点数计算的结果可能包含微小的表示误差。

比较浮点数时怎么办?

如果只是展示计算结果,可以使用 round() 控制显示精度:

1
2
3
result = 0.1 + 0.2

print(round(result, 2))

运行结果:

1
0.3

不过,round() 不是万能的精度修复工具。它返回的仍然是相应的数值类型,不能保证所有十进制小数都变成精确的二进制浮点值。

如果需要判断两个浮点数是否足够接近,可以使用 math.isclose():

1
2
3
4
5
6
import math

a = 0.1 + 0.2
b = 0.3

print(math.isclose(a, b))

运行结果:

1
True

对于需要容忍微小误差的数值比较,这种方式通常比直接使用 == 更合适。涉及实际业务时,还应根据数据规模和精度要求选择合适的 rel_tol 或 abs_tol。

金额计算为什么要考虑 Decimal?

如果涉及金额、价格或需要精确十进制舍入的业务,可以考虑使用标准库中的 decimal.Decimal。

例如:

1
2
3
4
5
6
7
8
from decimal import Decimal

price = Decimal("19.90")
quantity = Decimal("3")

total = price * quantity

print(total)

运行结果:

1
59.70

这里使用字符串构造 Decimal,可以避免先把十进制小数转换成二进制浮点数再传入所带来的误差。

不推荐这样写:

1
2
3
4
5
from decimal import Decimal

price = Decimal(0.1)

print(price)

因为传入的 0.1 已经是一个浮点数,Decimal 会根据这个浮点数实际表示的值进行转换,而不是自动恢复成精确的十进制 0.1。

如果业务要求固定的小数位数,还需要根据明确的舍入规则使用 quantize() 等方法进行处理。

数字字面量与进制转换

日常生活中,我们习惯使用十进制数字。但在计算机领域,二进制、八进制和十六进制也很常见。

例如,查看文件权限、分析位掩码或处理十六进制标识时,都可能接触不同的进制。

Python 如何表示不同进制?

Python 支持以下数字字面量:

进制前缀示例十进制结果
十进制无255255
二进制0b 或 0B0b11111111255
八进制0o 或 0O0o377255
十六进制0x 或 0X0xff255

我们可以直接运行:

1
2
3
4
5
6
7
8
9
decimal_value = 255
binary_value = 0b11111111
octal_value = 0o377
hex_value = 0xff

print(decimal_value)
print(binary_value)
print(octal_value)
print(hex_value)

运行结果:

1
2
3
4
255
255
255
255

虽然源代码中的写法不同,但这些值在 Python 中都是整数,打印时默认以十进制形式显示。

这里需要记住一个细节:Python 3 不允许使用类似 077 的旧式八进制整数写法。如果要表示八进制,应使用 0o77。

如何将整数转换成其他进制?

Python 提供了几个非常方便的内置函数:

  • bin():转换为二进制字符串。
  • oct():转换为八进制字符串。
  • hex():转换为十六进制字符串。

例如:

1
2
3
4
5
number = 255

print(bin(number))
print(oct(number))
print(hex(number))

运行结果:

1
2
3
0b11111111
0o377
0xff

注意,这些函数返回的是字符串,而不是另一种数值类型。

如何将不同进制的字符串转换为整数?

可以使用 int() 的第二个参数指定原始进制:

1
2
3
print(int("ff", 16))
print(int("377", 8))
print(int("11111111", 2))

运行结果:

1
2
3
255
255
255

这在读取外部配置、处理日志或解析用户输入时很有用。

1
2
3
4
hex_string = "ff"
number = int(hex_string, 16)

print(number + 1)

运行结果:

1
256

这里的 "ff" 是字符串,int(hex_string, 16) 将它按照十六进制解析为整数。

常用数值函数:abs、round、divmod 与 pow

除了基本运算符,Python 还提供了一些常用的数值函数。

abs():计算绝对值

1
2
print(abs(-5))
print(abs(3.14))

运行结果:

1
2
5
3.14

abs() 可以用于整数、浮点数等支持绝对值运算的数值类型。

round():舍入数值

1
2
print(round(3.14159, 2))
print(round(3.14159, 3))

运行结果:

1
2
3.14
3.142

第二个参数表示保留的小数位数。

不过,需要注意,Python 的 round() 在遇到恰好处于两个相邻舍入结果中间的情况时,采用 ties-to-even 规则,也就是选择末位为偶数的结果。

例如:

1
2
print(round(2.5))
print(round(3.5))

运行结果:

1
2
2
4

这与某些人习惯的「逢五就进一」不同。如果金额或统计报表有明确的舍入要求,应先确认业务规则,不要想当然地依赖 round()。

divmod():一次获得商和余数

divmod(a, b) 返回一个二元组,内容相当于 (a // b, a % b)。

1
print(divmod(20, 6))

运行结果:

1
(3, 2)

这在分页计算、批量任务分组和单位换算等场景中非常方便。

pow():计算幂

pow(a, b) 通常相当于 a ** b:

1
2
print(pow(2, 10))
print(2 ** 10)

运行结果:

1
2
1024
1024

但 pow() 还有一个额外能力:可以提供第三个参数,进行取模幂运算。

1
print(pow(2, 10, 1000))

运行结果:

1
24

这相当于计算 (2^{10} \bmod 1000),适用于某些需要控制中间计算结果规模的数学场景。

Linux 运维实战:容量换算与分页计算

前面介绍的运算符和数值函数,真正放进实际脚本后才更容易理解。

下面通过两个小例子,把数值类型、整除、取余和幂运算串起来。

计算磁盘容量

假设我们需要将 GiB 转换成字节。

按照二进制容量单位:

  • 1 KiB = 1024 字节
  • 1 MiB = 1024 KiB
  • 1 GiB = 1024 MiB

因此,1 GiB 等于 (1024^3) 字节。

编写如下脚本:

1
2
3
4
5
gib = 10
bytes_value = gib * 1024 ** 3

print("容量:", gib, "GiB")
print("字节数:", bytes_value)

运行结果:

1
2
容量: 10 GiB
字节数: 10737418240

这里需要区分 GB 和 GiB:

  • GB 是十进制单位,1 GB = 1,000,000,000 字节。
  • GiB 是二进制单位,1 GiB = 1,073,741,824 字节。

在处理磁盘容量、内存容量时,应明确使用的单位,避免将两种口径混用。

如果数据来自 df、free 或其他系统命令,还要注意这些工具的输出单位和格式可能不同,不能在未确认单位的情况下直接换算。

计算分页总数

假设有 103 条记录,每页显示 10 条,我们需要计算一共需要多少页。

一个直观的方法是使用 math.ceil():

1
2
3
4
5
6
7
8
9
10
import math

total_records = 103
page_size = 10

total_pages = math.ceil(total_records / page_size)

print("总记录数:", total_records)
print("每页记录数:", page_size)
print("总页数:", total_pages)

运行结果:

1
2
3
总记录数: 103
每页记录数: 10
总页数: 11

也可以通过整数运算完成:

1
2
3
4
5
6
7
8
total_records = 103
page_size = 10

total_pages = (
total_records + page_size - 1
) // page_size

print(total_pages)

运行结果:

1
11

为什么要先加上 page_size - 1?

因为普通的 // 会向下取整。对于 103 条记录和每页 10 条的情况,直接计算 103 // 10 只能得到 10,但实际上还剩 3 条记录,需要额外一页。

通过 (total_records + page_size - 1) // page_size,就能在整数运算中实现向上取整。

在真实业务中,还需要检查 page_size 是否大于零,避免除零错误或无效的分页配置。

生产环境常见错误与排查建议

数值计算看起来简单,但在实际脚本中,下面这些问题都值得注意。

除数为零

1
print(10 / 0)

会引发 ZeroDivisionError。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-148>", line 1, in <module>
print(10 / 0)
~~~^~~
ZeroDivisionError: division by zero

在接收用户输入或处理外部数据时,应先检查除数是否为零。

把字符串当成数字计算

1
2
number = input("请输入数字:")
print(number + 1)

这会引发 TypeError,因为 input() 返回字符串,而这里的 1 是整数。

1
2
3
4
5
Traceback (most recent call last):
File "<python-input-153>", line 2, in <module>
print(number + 1)
~~~~~~~^~~
TypeError: can only concatenate str (not "int") to str

解决方法是先进行合适的类型转换,并对无效输入进行异常处理。

混淆整除与截断

// 是向负无穷方向取整,int() 对浮点数则是向零截断。

对于负数,这两种操作可能得到不同的结果。进行分页、时间计算或负数运算时尤其需要注意。

直接比较浮点数

对于可能存在表示误差的浮点计算,不应随意假设结果一定精确。

需要判断数值是否接近时,可以根据场景考虑 math.isclose();对于金额计算,则可以考虑 Decimal。

忽略输入范围和业务单位

将字符串转换成整数,并不意味着输入一定合理。

例如,用户输入负数的磁盘容量、零作为分页大小,或者将 GB 直接当成 GiB,都可能导致错误的业务结果。

可靠的运维脚本不仅要保证语法正确,还要检查输入类型、取值范围、单位和异常情况。

综合练习:编写一个简单的容量换算程序

现在我们把前面学习的输入输出、类型转换、算术运算和浮点数知识结合起来。

要求:

  1. 提示用户输入磁盘容量,单位为 GiB。
  2. 将输入转换为浮点数。
  3. 计算对应的字节数。
  4. 输出换算结果。
  5. 拒绝负数和无法转换的输入。

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
gib = float(input("请输入磁盘容量(GiB):"))

if gib < 0:
print("容量不能为负数。")
else:
bytes_value = gib * 1024 ** 3

print("磁盘容量:", gib, "GiB")
print("对应字节数:", bytes_value)

except ValueError:
print("输入无效,请输入合法的数字。")

例如输入 10,运行结果为:

1
2
3
请输入磁盘容量(GiB):10
磁盘容量: 10.0 GiB
对应字节数: 10737418240.0

因为输入使用 float() 转换,所以即使输入的是整数 10,变量 gib 仍然是浮点数,计算结果也会以浮点数形式显示。

如果这个程序要用于实际的容量管理,还可以进一步增加输入范围检查、输出格式化和单位选择功能。

写在最后

这一篇,我们从 Python 的三种数值类型出发,学习了常见算术运算符、运算符优先级、数值类型转换、浮点数精度、数字进制和常用数值函数。

更重要的是,我们通过容量换算和分页计算,理解了这些知识在真实程序中的用途。

回顾一下几个特别容易出错的地方:

  • / 表示普通除法,两个整数相除也会得到浮点数。
  • // 是向下取整的整除运算,负数情况下尤其要注意。
  • input() 返回字符串,需要根据实际需求进行类型转换。
  • 浮点数存在表示误差,金额计算应根据业务要求选择合适的数值类型和舍入规则。
  • 二进制、八进制和十六进制都有明确的前缀,运行时转换可以使用 int() 等函数。
  • 计算容量时要区分 GB 与 GiB,避免单位混淆。

掌握这些基础知识之后,编写简单的计算程序就不再困难。