Python 编程基础与工程实践(二):语法、名称绑定与基础类型

Python 语法简洁,但“看得懂”不等于理解它的运行方式。尤其是变量、赋值和对象,如果沿用其他语言中的直觉,很容易在可变参数、复制和并发代码里踩坑。

本文以 CPython 3.14 为基线,从名称绑定开始讲清基础类型、表达式和控制流。读者只需完成上一篇的解释器与虚拟环境准备。

1. 缩进是语法,不是装饰

Python 使用缩进划分代码块,不使用花括号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
temperature = 26.5

if temperature >= 30:
    level = "hot"
elif temperature >= 20:
    level = "comfortable"
else:
    level = "cold"

print(level)

同一代码块必须保持一致缩进。工程中通常使用 4 个空格,不要混用 Tab 和空格。冒号表示即将开始一个代码块,常见于 ifforwhiledefclasstrywithmatch

较长表达式应借助括号自然换行:

1
2
3
4
5
total = (
    subtotal
    + shipping_fee
    - discount
)

不要依赖反斜杠拼接复杂表达式;括号内换行更稳健,也更容易被格式化工具处理。

2. 变量更准确地说是“名称”

在 Python 中,赋值把名称绑定到对象:

1
2
3
4
5
6
first = [1, 2]
second = first
second.append(3)

print(first)          # [1, 2, 3]
print(first is second)  # True

这里没有复制列表。firstsecond 指向同一个对象,所以通过任一名称修改列表,都能从另一个名称观察到。

每个对象都有类型、标识和值:

1
2
3
4
value = 42

print(type(value))
print(id(value))
  • type(value) 返回对象的类型;
  • id(value) 返回对象在当前生命周期内唯一的标识;
  • is 比较对象标识,== 比较值是否相等。

因此,判断空值应写:

1
2
if result is None:
    print("没有结果")

不要用 is 比较普通数字或字符串。CPython 可能复用某些对象,但那是实现细节,不是业务语义。

3. 数值类型与除法

常用内置数值类型包括:

  • int:任意精度整数,大小主要受可用内存限制;
  • float:通常对应平台的双精度二进制浮点数;
  • complex:复数;
  • bool:逻辑值,是 int 的子类,但业务代码应把它当布尔类型使用。
1
2
3
4
5
count = 1_000_000
ratio = 3 / 2       # 1.5,真除法
quotient = 7 // 3   # 2,向下取整除法
remainder = 7 % 3   # 1
power = 2 ** 10     # 1024

// 是向负无穷方向取整,不是简单“截掉小数”:

1
2
assert -7 // 3 == -3
assert -7 % 3 == 2

二进制浮点不能精确表示所有十进制小数:

1
print(0.1 + 0.2)  # 0.30000000000000004

这不是 Python 独有问题。金额等需要确定十进制语义的场景可使用 decimal.Decimal,但必须从字符串构造,避免先引入浮点误差:

1
2
3
4
5
from decimal import Decimal

price = Decimal("0.10")
tax = Decimal("0.20")
assert price + tax == Decimal("0.30")

4. 文本 str 与字节 bytes

Python 3 的 str 表示 Unicode 文本,bytes 表示 0 到 255 的字节序列。两者不能直接拼接:

1
2
3
4
5
message = "温度正常"
payload = message.encode("utf-8")

assert payload == b"\xe6\xb8\xa9\xe5\xba\xa6\xe6\xad\xa3\xe5\xb8\xb8"
assert payload.decode("utf-8") == message

字符串是不可变对象。所谓“修改字符串”实际会创建新字符串并重新绑定名称:

1
2
3
name = "python"
name = name.capitalize()
print(name)  # Python

常用格式化方式是 f-string:

1
2
3
product = "sensor"
price = 12.3456
print(f"{product=}, price={price:.2f}")

输出为 product='sensor', price=12.35。格式说明符控制的是展示,不会改变原始数值。

Python 3.14 还新增了模板字符串字面量(t-string)。它使用类似 f-string 的语法,但结果不是 str,而是保留静态片段与插值信息的 string.templatelib.Template

1
2
3
4
5
6
7
8
9
from string.templatelib import Template


name = "sensor"
template = t"Device: {name}"

assert isinstance(template, Template)
assert template.strings == ("Device: ", "")
assert template.values == ("sensor",)

t-string 供模板引擎、HTML 转义或领域专用处理器检查插值后再渲染,本身不会自动完成转义,也不能当普通字符串直接交给所有 API。普通展示继续使用 f-string;只有接收方明确支持 Template 时才使用 t-string。

处理文件和网络数据时,应该在系统边界明确编码:内部尽量使用 str,读写时显式使用 UTF-8。

5. None、布尔值与真值判断

None 表示缺少值,它不是空字符串、零或 False。函数没有显式 return 时会返回 None

下列对象在布尔上下文中为假:

  • FalseNone
  • 数值零;
  • 空字符串和空容器;
  • 自定义类型通过 __bool__()__len__() 定义的假值。
1
2
3
4
items: list[str] = []

if not items:
    print("列表为空")

但是“没有值”和“值恰好为零”可能代表不同业务状态:

1
2
3
4
def display_count(count: int | None) -> str:
    if count is None:
        return "尚未统计"
    return f"数量:{count}"

不要把这里写成 if not count,否则合法的 0 会被误判为缺失。

逻辑运算符 andor 会短路,并返回参与运算的对象,不保证返回 bool

1
2
3
configured_name = ""
name = configured_name or "default"
assert name == "default"

这个习惯写法适合“所有假值都等价”的场景;若空字符串和 None 含义不同,仍要显式判断。

6. 比较、成员检查与链式比较

1
2
3
4
5
age = 25

assert 18 <= age < 65
assert "py" in "cpython"
assert 3 not in {1, 2}

链式比较 18 <= age < 65 只求值中间表达式一次,语义接近 18 <= age and age < 65。不同类型通常不能随意排序,例如 1 < "2" 会抛出 TypeError,这能避免隐式转换掩盖数据问题。

7. ifforwhile

Python 的 for 遍历可迭代对象,不是传统 C 风格计数循环:

1
2
3
4
temperatures = [21.5, 23.0, 25.2]

for index, value in enumerate(temperatures, start=1):
    print(index, value)

同时遍历多个序列可使用 zip()

1
2
3
4
5
names = ["A", "B"]
values = [10, 20]

for name, value in zip(names, values, strict=True):
    print(name, value)

strict=True 会在序列长度不一致时抛出 ValueError,适合长度不一致代表数据错误的场景。

while 适合由状态而非固定集合驱动的循环:

1
2
3
4
5
remaining = 3

while remaining > 0:
    print(remaining)
    remaining -= 1

break 退出当前循环,continue 跳到下一轮。循环还支持 else:只有循环正常结束、没有被 break 打断时才执行。

1
2
3
4
5
6
7
8
target = 7

for value in [2, 4, 6]:
    if value == target:
        print("found")
        break
else:
    print("not found")

8. 结构化模式匹配

match/case 不是简单的 switch,它可以按数据结构解构对象:

1
2
3
4
5
6
7
8
def describe(command: tuple[str, ...]) -> str:
    match command:
        case ("move", x, y):
            return f"移动到 ({x}, {y})"
        case ("stop",):
            return "停止"
        case _:
            return "未知命令"

模式中的裸名称通常是捕获变量,不是与同名常量比较。需要匹配枚举或常量时,应使用限定名称,例如 Color.RED。简单条件继续使用 if 更清楚;只有当数据形状本身就是分支依据时,模式匹配才真正有价值。

9. 输入、转换与错误边界

input() 始终返回字符串:

1
2
raw = input("请输入数量:")
count = int(raw)

int() 遇到非法文本会抛出 ValueError,实际程序需要定义错误处理策略:

1
2
3
4
5
6
7
8
raw = input("请输入数量:")

try:
    count = int(raw)
except ValueError:
    print("数量必须是整数")
else:
    print(f"已记录:{count}")

不要使用 eval(input(...)) 解析用户输入。eval 会执行任意表达式,无法替代正常的格式解析与校验。

10. 小结

  • Python 赋值是名称绑定,不会自动复制对象;is 比较标识,== 比较值。
  • str 是 Unicode 文本,bytes 是字节;编码和解码应发生在系统边界。
  • 真值判断很方便,但不能混淆 None、零和空字符串的业务含义。
  • for 面向可迭代对象,zip(strict=True) 能主动发现长度错位。
  • match 适合按数据结构分支,不应机械替代 if

下一篇将学习列表、元组、字典和集合,并深入解释可变性、哈希与复制。

参考资料

Licensed under CC BY-NC-SA 4.0