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() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue

为什么 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,避免单位混淆。

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