《Python编程入门》3.1 数值、字符串与 f-string 格式化

本节讲透 Python 的四大标量类型:int 的任意精度与位数限制、float 的 IEEE 754 二进制表示误差、bool 作为 int 子类的真相,以及 complex 复数。随后深入字符串的不可变性与 Unicode 码位和字节的区别,系统讲解 f-string 的对齐、精度、千分位与嵌套宽度等格式化语法,并对比 % 与 .format() 的适用场景,为后续容器与文本处理打好基础。

本节目标:读完这一节,你能说清 int、float、bool、complex 四种数值类型各自的边界,能解释 0.1 + 0.2 != 0.3 的根因并知道何时该用 decimal,能熟练使用 f-string 的全部格式化语法输出对齐、精度、千分位与百分比,并理解字符串为什么不可变、len 与字节长度为何不同。
适用版本:Python 3.12+(实测 3.14.6)

3.1 数值、字符串与 f-string 格式化

在 2.3 项目结构与 pyproject.toml 初探 里,我们已经把项目骨架搭好了。从这一节开始,我们真正进入语言本身:先把「一个值」搞清楚,再在下一节谈「一组值」。Python 把一切都当作对象,而最基本的对象就是数值与字符串——它们是所有程序的原子。

int:没有上限的整数

在 C 或 Java 里,int 是固定 32 位或 64 位,超出就溢出。Python 不一样:int 是任意精度的,多大都能表示,代价是运算会随位数变慢。

n = 2 ** 1000
print(len(str(n)))          # 2 的 1000 次方有多少位十进制数字
print(str(n)[:20], "...", str(n)[-20:])
302
10715086071862673209 ... 24386837205668069376

2 ** 1000 是一个 302 位的十进制数,Python 眼都不眨就存下了。它的内部实现是一串 30 位的「数字位」(digit)组成的数组,可以用 sys.int_info 观察:

import sys
print(sys.int_info)
sys.int_info(bits_per_digit=30, sizeof_digit=4, default_max_str_digits=4300, str_digits_check_threshold=640)

注意最后两项:把一个超过 4300 位的整数转成字符串(或反过来)会直接抛错。这是 CPython 3.11 引入的保护,防止恶意的超大整数转换拖垮服务:

x = 10 ** 5000
len(str(x))
ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit

需要处理超长整数时,用 sys.set_int_max_str_digits(10000) 调高上限即可。日常业务里很少碰到,但做密码学或大数计算时要记得这个坑。

float:IEEE 754 双精度的真相

float 是 64 位双精度浮点数,遵循 IEEE 754 标准。它用二进制表示小数,而绝大多数十进制小数在二进制里是无限循环的,只能截断存储。于是就有了那个著名的现象:

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
0.30000000000000004
False

把 0.1 用 20 位小数打印出来,就能看到它存储的真实值:

print(repr(0.1))
print(f"{0.1:.20f}")
0.1
0.10000000000000000555

repr 只显示最短的、能唯一还原该浮点数的十进制形式,所以看起来是干净的 0.1;但它的二进制近似值其实带着误差。这就是为什么浮点数永远不要用 == 直接比较。正确做法是用 math.isclose:

import math
print(math.isclose(0.1 + 0.2, 0.3))
True

isclose 默认的相对容差是 1e-9,也可以用 rel_tol 与 abs_tol 自行调整。

decimal:需要精确计算时

涉及金额、税率、财务结算这类场景,二进制浮点的误差不可接受。标准库的 decimal.Decimal 提供十进制定点运算,精度可控:

from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2"))
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
0.3
True

务必用字符串构造 Decimal,如果传的是浮点数,误差会原样带进来:

print(repr(Decimal(0.1)))
Decimal('0.1000000000000000055511151231257827021181583404541015625')

bool 是 int 的子类

Python 的 bool 只有 True 和 False 两个值,但它在类型系统里继承自 int——True 就是 1,False 就是 0:

print(True + True)              # 2
print(isinstance(True, int))    # True
print(True == 1, False == 0)    # True True
print(sum([True, True, False, True]))  # 3
2
True
True True
3

这个设计让「数一数有多少个条件成立」变得极简:把布尔列表丢给 sum 就行,无需手写循环累加。

复数 complex

Python 原生支持复数,字面量用 j 表示虚部:

c = 3 + 4j
print(c, c.real, c.imag, abs(c))
print((1 + 2j) * (3 + 4j))
(3+4j) 3.0 4.0 5.0
(-5+10j)

注意 c.real 和 c.imag 都是 float。复数在信号处理、电路分析、科学计算里用得上,业务代码中不常见。

字符串:不可变与 Unicode

字符串是不可变的——任何「修改」都会产生新对象:

s = "hello"
s[0] = "H"
TypeError: 'str' object does not support item assignment

要改就得重建:s = "H" + s[1:]。

Python 3 的字符串是 Unicode 码位序列,不是字节序列。len 数的是码位个数,而 UTF-8 编码后的字节数往往不同:

s = "Python编程"
print(len(s))                        # 码位数
print(len(s.encode("utf-8")))        # 字节数
print(ord("中"), hex(ord("中")))     # 码位值
8
12
20013 0x4e2d

「编程」两个汉字各占 3 个 UTF-8 字节,所以 8 个字符编码后是 12 字节。用 ord 取码位、chr 从码位还原字符,两者互逆。再看内存占用,sys.getsizeof 会把对象的头部开销也算进去:

import sys
print(sys.getsizeof(""), sys.getsizeof("a"), sys.getsizeof("Python编程"))
41 42 74

空字符串也要 41 字节(对象头 + 长度等元数据),每多一个 ASCII 字符加 1 字节,而中文在内部用更宽的表示,占用更大。

f-string 格式化全解

f-string(3.6 起)是目前最推荐的字符串格式化方式:在字符串前加 f,花括号里直接写表达式,冒号后跟格式规格。下面这张表覆盖了日常九成以上的用法:

写法含义示例输出
{x:.2f}保留 2 位小数3.14
{x:>10}右对齐,总宽 10␣␣␣␣␣␣␣Ada
{x:<10}左对齐Ada␣␣␣␣␣␣␣
{x:^10}居中␣␣␣Ada␣␣␣␣
{x:,}千分位分隔1,234,567,890
{x:.1%}百分比,1 位小数85.7%
{x:05d}补零到 5 位00042
{x:#x}十六进制带前缀0xff
{x=}自文档化调试x=42

实际跑一遍:

name = "Ada"
pi = 3.14159265
big = 1234567890
ratio = 0.8567

print(f"Hello, {name}!")
print(f"{pi:.2f}")
print(f"{name=}")
print(f"{pi=:.3f}")
print(f"{name:>10}|")
print(f"{big:,}")
print(f"{ratio:.1%}")
Hello, Ada!
3.14
name='Ada'
pi=3.142
       Ada|
1,234,567,890
85.7%

宽度还可以嵌套——宽度值本身也能是变量或表达式,写两对花括号即可:

width = 12
print(f"{name:>{width}}|")
print(f"{pi:{width}.3f}|")
         Ada|
       3.142|

这个技巧在生成对齐的表格、日志时非常好用,比手工拼空格可靠得多。

%、.format() 与模板字符串

老式的 % 格式化来自 C 语言,适合简单场景;.format() 在 3.6 之前是主流,支持位置与关键字参数:

print("%s is %d years old, pi=%.2f" % ("Ada", 36, 3.14159))
print("{} is {} years old".format("Ada", 36))
print("{name} scored {score:.1f}".format(name="Ada", score=92.5))
Ada is 36 years old, pi=3.14
Ada is 36 years old
Ada scored 92.5

三种方式该怎么选?新代码一律用 f-string:可读性最好、性能也最高。只有两种情况例外——需要把格式串存成变量稍后再填(% 或 .format() 更自然),以及处理不可信的格式串时(f-string 会立即求值,反而可能被注入)。

Python 3.14 新增了 模板字符串 t"..."(PEP 750),它不立即拼成 str,而是返回一个 Template 对象,把字面量与插值分开保存,便于做安全转义、结构化日志或 SQL 参数化:

name = "Ada"
t = t"Hello {name}"
print(type(t))
print(t.strings, t.values)
<class 'string.templatelib.Template'>
('Hello ', '') ('Ada',)

t"..." 是 3.14 起才有的语法,在 3.12 / 3.13 上会直接报 SyntaxError,书写时请按基线版本决定是否使用。

小结

这一节我们把「单个值」彻底摸清了:

  • int 任意精度,但有 4300 位的字符串转换上限;bool 是 int 的子类,True + True == 2。
  • float 是 IEEE 754 双精度,0.1 + 0.2 != 0.3 是二进制表示的必然,比较要用 math.isclose。
  • 精确十进制计算用 decimal.Decimal,且必须用字符串构造。
  • 字符串不可变;len 数码位、encode 后才是字节数,中文一个字占 3 个 UTF-8 字节。
  • f-string 是首选格式化方式,对齐、精度、千分位、百分比、嵌套宽度一应俱全;t"..." 是 3.14 的新特性。

掌握了标量,接下来自然要面对「一组值」。下一节 3.2 列表、元组、字典与集合 会把四种内建容器一次讲清,并用复杂度表告诉你什么时候该用哪一种。

阅读导航:上一节:2.3 项目结构与 pyproject.toml 初探 · 下一节:3.2 列表、元组、字典与集合 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「python」更多文章

  1. 《Python高级编程》目录
  2. 《Python高级编程》11.3 PEP 流程与版本迁移策略
  3. 《Python高级编程》11.2 嵌入式与自由线程运行时