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
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承

前面我们学习了 Python 的数据类型、流程控制,以及如何使用标准库中的模块。

在实际编程中,数字几乎无处不在。

比如,检查服务器磁盘使用率时,需要计算百分比;统计业务数据时,可能需要计算平均值;处理金额时,需要避免浮点数带来的精度问题;编写监控脚本时,还可能需要计算平方根、向上取整或向下取整。

这些需求,Python 都提供了相应的解决方案。

本篇我们重点学习两个标准库模块:

  • math:提供常用的数学函数,例如平方根、向上取整、向下取整等。
  • decimal:提供十进制数的精确计算能力,适合对金额等十进制精度有要求的场景。

先从最熟悉的数学运算开始。

使用 math 模块进行数学运算

在 Python 中,我们可以直接使用 +、-、*、/ 等运算符完成基本的数学计算。

但是,如果需要计算平方根、对数字向上取整,或者进行三角函数运算,仅靠这些运算符就不够了。

这时候,可以使用 Python 标准库中的 math 模块。

使用之前,先导入模块:

1
import math

导入后,就可以通过 math.函数名() 的方式调用其中的功能。

例如,计算一个数字的平方根:

1
2
3
4
5
import math

print(math.sqrt(16))
print(math.sqrt(25))
print(math.sqrt(2))

运行结果:

1
2
3
4.0
5.0
1.4142135623730951

这里的 math.sqrt() 用于计算平方根。

需要注意,计算结果通常是 float 类型。即使 16 的平方根恰好是整数 4,返回值仍然是 4.0。

这并不影响后续计算,因为整数和浮点数可以参与常见的混合算术运算。

向上取整与向下取整

在处理监控数据、分页数量或资源分配时,经常会遇到一个问题:计算结果不是整数,应该如何处理?

例如,我们需要把 101 个任务分配给每批最多处理 10 个任务的工作进程。

显然,不能只处理 10 批,因为还剩下 1 个任务。

这时候就需要向上取整。

1
2
3
4
5
6
import math

print(math.ceil(10.1))
print(math.ceil(10.9))
print(math.floor(10.9))
print(math.floor(10.1))

运行结果:

1
2
3
4
11
11
10
10

其中:

  • math.ceil():向上取整,返回大于或等于原数字的最小整数。
  • math.floor():向下取整,返回小于或等于原数字的最大整数。

需要特别注意,向上取整不等于直接删除小数部分,向下取整也不完全等于截断小数部分。

例如:

1
2
3
4
5
import math

print(math.ceil(-2.3))
print(math.floor(-2.3))
print(int(-2.3))

运行结果:

1
2
3
-2
-3
-2

为什么会出现这样的结果?

因为 math.floor(-2.3) 是向负无穷方向取整,所以结果是 -3;而 int(-2.3) 是直接截掉小数部分,结果是 -2。

对于正数而言,这几个操作有时看起来很相似;但处理负数时,它们的区别就非常明显了。

在 Linux 运维中计算磁盘使用率

了解了基本的数学函数,我们再来看一个更贴近实际工作的例子。

假设我们希望通过 Python 检查 Linux 服务器的磁盘使用率。当使用率达到 80% 时,输出一条警告信息。

前面我们已经接触过 shutil.disk_usage(),它可以返回文件系统的总容量、已使用空间和剩余空间。

现在,我们将它与数学运算结合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import shutil

usage = shutil.disk_usage("/")

# 计算磁盘使用率
percent = usage.used / usage.total * 100

print(f"磁盘总容量:{usage.total / 1024**3:.2f} GiB")
print(f"已使用空间:{usage.used / 1024**3:.2f} GiB")
print(f"磁盘使用率:{percent:.2f}%")

if percent >= 80:
print("警告:磁盘使用率已经达到 80%,请及时检查!")
else:
print("磁盘空间正常。")

这段代码涉及几个值得理解的知识点。

首先:

1
percent = usage.used / usage.total * 100

这里通过除法计算已使用空间占总容量的比例,再乘以 100,转换为百分比。

其次:

1
usage.total / 1024**3

这里的 ** 是 Python 的乘方运算符,1024**3 表示 1024 的三次方。

在 Linux 运维中,我们经常使用 GiB 表示以 (1024^3) 字节为单位的容量,因此这里将字节数转换为 GiB。

最后:

1
f"{percent:.2f}%"

这里使用了 f-string 格式化字符串,.2f 表示显示两位小数。

例如,计算结果为 83.4567 时,输出会显示为 83.46%。

需要注意,这个示例计算的是 shutil.disk_usage("/") 返回的文件系统使用率,并不等同于 df -h 所有场景下的显示结果,也不代表整个服务器所有磁盘的使用率。实际监控时,应根据业务需要检查指定挂载点,并考虑告警阈值和异常处理。

为什么 0.1 + 0.2 不等于 0.3?

接下来,我们讨论一个 Python 初学者经常遇到的问题。

运行下面的代码:

1
print(0.1 + 0.2)

输出结果是:

1
0.30000000000000004

咦?明明是 0.1 + 0.2,为什么结果不是 0.3?

这并不是 Python 的计算错误,而是因为 Python 中的 float 通常使用二进制浮点数表示数字。

有些十进制小数无法用有限长度的二进制小数精确表示,就像十进制中 (1/3) 无法用有限位小数精确表示一样。

因此,0.1 和 0.2 转换为二进制浮点数后,存储的值只是非常接近原始十进制数的近似值。进行计算时,这种微小的表示误差就可能显现出来。

对于一般的科学计算、性能数据统计等场景,浮点数通常已经足够使用。

但是,如果涉及金额计算、财务数据或其他要求十进制结果精确的场景,就需要更加谨慎。

这时候,可以考虑使用 decimal 模块。

使用 decimal 进行精确的十进制计算

decimal 是 Python 标准库中的模块,提供十进制数的算术运算能力。

先看一个例子:

1
2
3
from decimal import Decimal

print(Decimal("0.1") + Decimal("0.2"))

运行结果:

1
0.3

这次结果符合我们的预期。

这里有一个非常重要的细节:

1
Decimal("0.1")

我们传入的是字符串 "0.1",而不是浮点数 0.1。

为什么推荐这样写?

因为字符串 "0.1" 可以准确地表达十进制数 0.1。Decimal 会根据这个十进制表示创建相应的数值。

如果先将 0.1 转换为二进制浮点数,再把它传给 Decimal,原有的浮点数表示误差可能也会被保留下来。

例如:

1
2
3
4
from decimal import Decimal

print(Decimal("0.1") + Decimal("0.2"))
print(Decimal(0.1))

第二行的输出会显示一个较长的十进制数值,反映出浮点数 0.1 的实际近似表示。

所以,如果业务要求十进制精度,通常应该从字符串创建 Decimal,避免先经过 float。

模拟一个简单的金额计算

假设我们需要计算两项费用的总金额。

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

server_cost = Decimal("199.90")
bandwidth_cost = Decimal("29.90")

total = server_cost + bandwidth_cost

print(f"服务器费用:{server_cost}")
print(f"带宽费用:{bandwidth_cost}")
print(f"总费用:{total}")

运行结果:

1
2
3
服务器费用:199.90
带宽费用:29.90
总费用:229.80

这里使用 Decimal,可以避免普通二进制浮点数表示带来的典型精度问题。

不过,需要明确一点:使用 Decimal 并不意味着所有计算都自动符合财务规则。对于金额计算,还应根据业务要求确定小数位数、舍入方式以及中间计算的精度。

例如,需要将金额保留两位小数时,可以使用 quantize():

1
2
3
4
5
6
7
8
9
10
from decimal import Decimal, ROUND_HALF_UP

amount = Decimal("19.995")

result = amount.quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)

print(result)

运行结果:

1
20.00

这里的 Decimal("0.01") 表示保留到小数点后两位,ROUND_HALF_UP 指定了相应的舍入方式。

在金额计算中,舍入规则可能会影响最终结果,因此应该根据实际业务要求选择,而不是简单地认为所有场景都应该使用同一种规则。

Decimal 综合实战:编写云服务器费用结算脚本

前面我们已经了解了 Decimal 的基本用法。

但是,只会计算 0.1 + 0.2 还不够。实际工作中,我们更关心的是:当一笔费用涉及多个资源、折扣和金额舍入时,如何保证计算过程清晰、结果符合业务规则?

假设你正在维护一个云平台,需要统计某台服务器一个月的资源费用。

本次模拟结算包含以下项目:

资源项目计费方式单价
云服务器按小时计费0.85 元/小时
云硬盘按 GB·月计费0.30 元/GB·月
公网带宽按 GB 流量计费0.80 元/GB

假设本月云服务器运行了 720 小时,使用了 100 GB 云硬盘,产生了 125.5 GB 公网流量,并且符合 9 折优惠条件。

我们希望程序能够完成以下任务:

  • 计算每项资源的费用。
  • 汇总资源费用。
  • 计算折扣金额和折后金额。
  • 将最终金额保留两位小数。
  • 输出清晰的费用清单。

这里使用的是演示价格和计费规则,并不代表任何云厂商的真实报价。

编写完整的费用结算脚本

创建 cloud_cost.py 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from decimal import Decimal, ROUND_HALF_UP

# 资源单价
server_price = Decimal("0.85")
disk_price = Decimal("0.30")
traffic_price = Decimal("0.80")

# 资源使用量
server_hours = Decimal("720")
disk_gb = Decimal("100")
traffic_gb = Decimal("125.5")

# 折扣:9 折
discount_rate = Decimal("0.90")

# 计算各项资源费用
server_cost = server_price * server_hours
disk_cost = disk_price * disk_gb
traffic_cost = traffic_price * traffic_gb

# 汇总原始费用
subtotal = server_cost + disk_cost + traffic_cost

# 计算折后金额
discounted_total = subtotal * discount_rate

# 金额统一保留两位小数
cent = Decimal("0.01")

server_cost_display = server_cost.quantize(
cent, rounding=ROUND_HALF_UP
)
disk_cost_display = disk_cost.quantize(
cent, rounding=ROUND_HALF_UP
)
traffic_cost_display = traffic_cost.quantize(
cent, rounding=ROUND_HALF_UP
)
subtotal_display = subtotal.quantize(
cent, rounding=ROUND_HALF_UP
)
discounted_total_display = discounted_total.quantize(
cent, rounding=ROUND_HALF_UP
)

# 计算优惠金额
discount_amount = subtotal - discounted_total
discount_amount_display = discount_amount.quantize(
cent, rounding=ROUND_HALF_UP
)

# 输出结算结果
print("=" * 36)
print(" 云服务器费用结算单")
print("=" * 36)

print(f"云服务器费用:{server_cost_display} 元")
print(f"云硬盘费用: {disk_cost_display} 元")
print(f"公网流量费用:{traffic_cost_display} 元")

print("-" * 36)
print(f"原始费用合计:{subtotal_display} 元")
print(f"优惠金额: {discount_amount_display} 元")
print(f"最终应付金额:{discounted_total_display} 元")
print("=" * 36)

运行结果

1
2
3
4
5
6
7
8
9
10
11
====================================
云服务器费用结算单
====================================
云服务器费用:612.00 元
云硬盘费用: 30.00 元
公网流量费用:100.40 元
------------------------------------
原始费用合计:742.40 元
优惠金额: 74.24 元
最终应付金额:668.16 元
====================================

这样,一份简单的云资源费用结算单就完成了。

接下来,我们逐步拆解其中的关键知识点。

为什么金额必须使用字符串创建 Decimal?

先看这几行代码:

1
2
3
server_price = Decimal("0.85")
disk_price = Decimal("0.30")
traffic_price = Decimal("0.80")

这里没有直接写成:

1
server_price = Decimal(0.85)

原因在于,0.85 作为 Python 的 float,通常无法用二进制精确表示。如果先创建浮点数,再传给 Decimal,浮点数中已有的表示误差就可能被带入计算。

而 Decimal("0.85") 直接根据十进制字符串创建数值,避免了这条转换路径。

因此,在金额计算中,建议遵循一个简单的习惯:

从输入、计算到结果,尽量让金额数据始终使用 Decimal,不要中途转换成 float。

如果金额来自 JSON、数据库或接口,也要注意数据的实际类型。对于以十进制字符串传递的金额,可以直接使用 Decimal 解析;如果上游已经把金额转换成浮点数,就不能假设精度仍然完整。

Decimal 不只是加减法,也可以进行乘法和折扣计算

在这个案例中,我们使用了:

1
server_cost = server_price * server_hours

以及:

1
discounted_total = subtotal * discount_rate

Decimal 支持常见的算术运算,包括加法、减法、乘法和除法。

因此,它不仅适用于两项金额相加,也适合处理计费单价、资源使用量和折扣比例等计算。

这里需要区分两个概念:

  • 计算精度:使用 Decimal 避免典型的二进制浮点表示误差。
  • 业务舍入规则:决定什么时候保留几位小数,以及采用什么舍入方式。

即使使用 Decimal,也仍然需要明确业务舍入规则。

为什么使用 quantize() 保留两位小数?

在代码中,我们定义了:

1
cent = Decimal("0.01")

它表示金额保留到小数点后两位。

然后使用:

1
2
3
4
discounted_total.quantize(
cent,
rounding=ROUND_HALF_UP
)

这里的 quantize() 会按照指定的舍入规则,将结果量化到相应的小数位数。

ROUND_HALF_UP 表示遇到正好位于中间的舍入情况时,按远离零的方向舍入。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from decimal import Decimal, ROUND_HALF_UP

print(
Decimal("12.344").quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)
)

print(
Decimal("12.345").quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)
)

输出结果:

1
2
12.34
12.35

需要注意,ROUND_HALF_UP 并不是所有财务系统都必须采用的规则。不同业务可能使用不同的舍入方式,应该根据合同、计费规范或财务要求进行选择。

费用应该在什么时候舍入?

这是金额计算中很容易被忽略的问题。

假设三项资源费用分别计算得到:

1
2
3
4
5
from decimal import Decimal

item1 = Decimal("10.005")
item2 = Decimal("10.005")
item3 = Decimal("10.005")

如果每项费用先独立舍入到两位小数,再进行汇总:

1
2
3
4
5
6
7
8
9
10
11
from decimal import Decimal, ROUND_HALF_UP

cent = Decimal("0.01")

total_rounded_items = (
item1.quantize(cent, rounding=ROUND_HALF_UP)
+ item2.quantize(cent, rounding=ROUND_HALF_UP)
+ item3.quantize(cent, rounding=ROUND_HALF_UP)
)

print(total_rounded_items)

结果为:

1
30.03

如果先汇总,再对总金额舍入:

1
2
3
4
5
6
7
8
total_then_round = (
item1 + item2 + item3
).quantize(
cent,
rounding=ROUND_HALF_UP
)

print(total_then_round)

结果为:

1
30.02

看到了吗?两种方式相差了 0.01 元。

这不是 Decimal 算错了,而是因为两种计算方式采用了不同的舍入时机。

在实际计费系统中,需要根据业务规则确定是逐项舍入,还是先汇总再舍入。不能为了让结果看起来整齐,就随意修改计算顺序。

进阶练习:增加费用输入和异常处理

现在,我们可以进一步把固定的资源使用量改成用户输入,让脚本更接近真实的运维工具。

例如,要求用户输入云服务器运行小时数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from decimal import Decimal, InvalidOperation

try:
hours = Decimal(input("请输入服务器运行小时数:"))

if not hours.is_finite():
raise ValueError("运行小时数必须是有限数字")

if hours < 0:
raise ValueError("运行小时数不能为负数")

price = Decimal("0.85")
cost = price * hours

print(f"服务器费用:{cost.quantize(Decimal('0.01'))} 元")

except (InvalidOperation, ValueError) as exc:
print(f"输入错误:{exc}")

运行时输入:

1
2
请输入服务器运行小时数:720
服务器费用:612.00 元

如果输入 abc、负数或者 NaN,程序会给出错误提示,而不是继续进行不合理的费用计算。

这里使用 Decimal.is_finite() 检查输入是否为有限数值,避免 NaN 和无穷大等特殊值参与业务计算。

注意,这只是一个入门练习。生产环境中还需要根据计费规则校验输入范围、单位、精度和资源计费周期,并明确舍入规则及异常处理策略。

本节小结

通过这个实战,我们把 Decimal 应用到了更完整的业务场景中:

知识点实际用途
Decimal("0.85")创建十进制单价
Decimal 加减乘除计算资源费用、汇总账单和折扣
quantize()将金额舍入到指定小数位
ROUND_HALF_UP明确指定一种舍入规则
is_finite()检查输入是否为有限数值
异常处理处理不合法的输入

记住:Decimal 能帮助我们控制十进制计算精度,但不能代替业务规则设计。金额系统真正需要保证的,是数值表示、计算顺序、舍入时机和异常处理的一致性。

math 和 decimal 应该怎么选?

学习了这两个模块后,我们可以根据实际需求选择工具。

使用场景推荐方式原因
计算平方根math.sqrt()提供常见数学函数
向上取整math.ceil()返回向上取整结果
向下取整math.floor()返回向下取整结果
计算磁盘使用率普通算术运算百分比计算通常使用浮点数即可
科学计算、性能数据分析float 或 math适合常见近似数值计算
金额加减与十进制计算Decimal避免典型的二进制浮点表示误差
金额保留指定小数位Decimal.quantize()可以明确指定舍入规则

需要记住的是,math 和 decimal 解决的问题并不完全相同。

math 主要提供数学函数;decimal 主要用于十进制数的精确表示与运算。它们都是 Python 标准库的一部分,使用之前不需要额外安装第三方软件包。

如果想继续了解更多功能,可以查看 Python 官方文档:

综合运维实战:编写 Linux 磁盘空间检查脚本

前面我们分别学习了 math 和 decimal 模块,也通过简单的例子了解了它们的使用方法。

现在,我们把这些知识组合起来,编写一个更完整的 Linux 运维脚本。

假设你需要检查 Linux 服务器的根分区,并完成以下任务:

  • 获取根分区的总容量、已使用空间和剩余空间。
  • 将字节转换为 GiB,方便阅读。
  • 计算磁盘使用率,并保留两位小数。
  • 当磁盘使用率达到 80% 时,输出告警信息。
  • 根据磁盘使用率判断当前磁盘状态,并返回相应的退出状态码,方便后续接入 Shell 自动化任务。

编写脚本

创建 disk_check.py 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python3

import shutil
import sys
from decimal import Decimal, ROUND_HALF_UP

# 检查根分区
mount_point = "/"

try:
usage = shutil.disk_usage(mount_point)
except OSError as exc:
print(f"错误:无法检查 {mount_point}:{exc}", file=sys.stderr)
sys.exit(2)

# 将字节转换为 GiB
gib = 1024 ** 3

total_gib = usage.total / gib
used_gib = usage.used / gib
free_gib = usage.free / gib

# 计算磁盘使用率
percent = usage.used / usage.total * 100

# 使用 Decimal 对显示结果进行十进制舍入
percent_display = Decimal(str(percent)).quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)

print(f"检查挂载点:{mount_point}")
print(f"磁盘总容量:{total_gib:.2f} GiB")
print(f"已使用空间:{used_gib:.2f} GiB")
print(f"剩余空间:{free_gib:.2f} GiB")
print(f"磁盘使用率:{percent_display}%")

# 使用未舍入的数值进行告警判断
if percent >= 80:
print("告警:磁盘使用率已达到 80%,请及时检查!")
sys.exit(1)
else:
print("状态正常:磁盘空间充足。")
sys.exit(0)

逐步理解代码

第一步:获取磁盘使用情况

1
usage = shutil.disk_usage("/")

shutil.disk_usage() 返回一个包含总容量、已使用空间和剩余空间的结果对象,分别通过 usage.total、usage.used 和 usage.free 获取,单位都是字节。

这里使用 try...except 捕获可能发生的 OSError,例如挂载点无法访问等情况。如果检查失败,脚本会向标准错误输出错误信息,并通过 sys.exit(2) 以非零状态退出。

第二步:将字节转换为 GiB

1
2
3
gib = 1024 ** 3

total_gib = usage.total / gib

这里的 ** 是乘方运算符,1024 ** 3 表示 (1024^3)。

Linux 运维中经常需要把字节转换成 GiB,否则输出一长串数字,不利于人工阅读。

第三步:计算磁盘使用率

1
percent = usage.used / usage.total * 100

例如,假设总容量为 100 GiB,已使用空间为 85 GiB,那么使用率就是 85%。

这里使用普通浮点数计算即可,因为磁盘监控通常不需要财务级别的十进制精度。

第四步:使用 Decimal 格式化百分比

1
2
3
4
percent_display = Decimal(str(percent)).quantize(
Decimal("0.01"),
rounding=ROUND_HALF_UP
)

这里使用 Decimal 和 quantize() 明确指定两位小数的舍入规则。

需要注意,Decimal(str(percent)) 只是将浮点计算得到的结果转换为十进制表示,并不能消除前面浮点计算本身可能产生的误差。对于本例的磁盘监控显示,这通常已经足够;如果需要严格的十进制计算,应从精确的十进制数据源开始。

实际上,在普通磁盘监控中,直接使用 f-string 的 :.2f 格式化百分比通常也足够。这里保留 Decimal 主要是为了展示它的舍入功能。

第五步:根据阈值返回退出状态码

1
2
3
4
5
6
if percent >= 80:
print("告警:磁盘使用率已达到 80%,请及时检查!")
sys.exit(1)
else:
print("状态正常:磁盘空间充足。")
sys.exit(0)

这里的 sys.exit(0) 表示正常结束,sys.exit(1) 表示检测到告警情况,sys.exit(2) 则用于表示检查过程中发生错误。

这样,其他 Shell 脚本就可以通过退出状态码判断检查结果。

运行脚本

在 Linux 服务器上执行:

1
2
python3 disk_check.py
echo $?

假设某台测试服务器的根分区总容量为 100 GiB,已使用 85 GiB,输出可能如下:

1
2
3
4
5
6
7
检查挂载点:/
磁盘总容量:100.00 GiB
已使用空间:85.00 GiB
剩余空间:15.00 GiB
磁盘使用率:85.00%
告警:磁盘使用率已达到 80%,请及时检查!
1

以上是演示输出,实际数值取决于运行脚本的服务器。

如果磁盘使用率低于 80%,脚本会输出正常状态,并返回退出码 0。

需要注意,脚本判断使用率时使用的是未舍入的 percent,而不是格式化后的 percent_display。这样可以避免显示时的舍入影响告警判断。

进阶练习:批量检查多个挂载点

现在,可以尝试继续扩展脚本:

  • 将检查路径从固定的 / 改为列表,例如 ["/", "/var", "/home"]。
  • 使用循环逐个检查挂载点。
  • 当某个挂载点使用率达到 80% 时,输出对应的告警。
  • 当一个挂载点检查失败时,继续检查其他挂载点,最后通过退出状态码汇总检查结果。
  • 使用 math.ceil() 计算某个容量需要多少个存储单元。

最后一个问题可以这样计算。

假设有 101 GiB 的数据,每个存储单元最多存放 10 GiB,需要多少个存储单元?

1
2
3
4
5
6
7
8
import math

data_size = 101
unit_size = 10

units = math.ceil(data_size / unit_size)

print(f"需要的存储单元数量:{units}")

运行结果:

1
需要的存储单元数量:11

这里的 math.ceil() 可以确保只要还有剩余数据,就会多分配一个存储单元。

完成这些练习后,你就把数学函数、十进制舍入、条件判断、异常处理和退出状态码串联了起来。这些知识也正是后续编写自动化运维脚本时经常会用到的基础。

写在最后

本篇我们学习了 Python 标准库中的 math 和 decimal 模块。

通过 math.sqrt()、math.ceil() 和 math.floor(),我们可以完成常见的数学运算;通过 Decimal,我们也理解了为什么金额计算不能总是直接依赖 float。

在云服务器费用结算实战中,我们进一步学习了资源计费、折扣计算、金额舍入和异常输入处理;在磁盘空间检查实战中,我们将容量换算、使用率计算、告警判断和退出状态码组合起来,完成了一个可以在 Linux 环境中运行的基础检查脚本。

学习 Python,不只是记住几个函数,更重要的是理解它们适合解决什么问题,并能够将这些知识应用到真实工作中。

下一篇,我们将学习 Python 的日期与时间模块,看看如何获取当前时间、格式化日期,以及计算时间间隔,让脚本能够处理日志时间、任务执行时间和定时任务等常见场景。