前言学完零散的语法点之后真正决定代码能不能写大的是结构数据怎么组织、逻辑怎么分层、状态放在哪、错误怎么收场。很多初学者写出来的程序「能跑但不敢改」问题几乎都出在结构上——所有代码堆在一个文件顶层、状态全靠全局变量、出错就崩、函数改了这里坏那里。本文沿着「一个 Python 程序由哪些结构组成」这条线讲变量与类型、程序的控制结构、函数与作用域、模块与包、异常结构。每个部分都只讲会实际影响正确性的规则不铺常识。代码以 Python 3.8 及以上为基准。一、变量与类型名字绑定到对象Python 的变量不是「装值的盒子」而是指向对象的名字。语句x 1的含义是把名字x绑定到整数对象1。类型属于对象不属于名字# 适用于 Python 3 全版本x 1print(type(x)) # class intx 一 # 合法名字改指向另一个对象print(type(x)) # class str内置类型分两大类类别类型可变性能否作字典键数值int/float/complex/bool不可变可以序列str/tuple不可变可以元素也须可哈希序列list可变不可以映射/集合dict/set可变不可以映射/集合frozenset不可变可以不可变对象一旦创建内容就不会变。这也是「str拼接会产生新对象」的原因——s x不是原地追加而是新建一个字符串。相比之下list.append()是原地修改。变量必须先赋值再使用否则抛NameError。没有「声明」这一说。二、控制结构顺序、分支、循环程序的基本结构只有三种顺序自上而下执行语句。分支if / elif / else以及 3.10 的match / case。循环for遍历可迭代对象与while条件为真。# 适用于 Python 3 全版本def sequence_total(items):total 0for item in items: # 顺序 循环if not isinstance(item, int): # 分支continuetotal item # 顺序return totalprint(sequence_total([1, 2, x, 3])) # 6控制结构的两条硬规则冒号加缩进组成代码块缩进量必须一致break只跳出最近的一层循环。需要跳出多层应当把循环封进函数用return而不是堆标志变量。三、函数与作用域函数是「把一段逻辑命名并复用」的最小单位。定义与调用# 适用于 Python 3 全版本def greet(name, greeting你好):返回问候语。greeting 有默认值。return f{greeting}{name}print(greet(Ada)) # 你好Adaprint(greet(Ada, greetingHi)) # HiAdaprint(greet(Ada, Hi)) # HiAda3.1 作用域规则LEGB查找一个名字时Python 依次搜索四个作用域顺序名称说明1Local当前函数的局部变量2Enclosing外层函数的作用域闭包3Global模块全局4Built-in内置命名空间关键规则函数内部给一个名字赋值它默认就是局部的。想在函数里改全局变量必须用global声明# 适用于 Python 3 全版本counter 0def bump():global counter # 没有这行会报 UnboundLocalErrorcounter 1bump()bump()print(counter) # 2如果只是想读全局变量不用global只有重新绑定赋值时才需要。嵌套函数里想改外层函数的变量用nonlocal。3.2 参数与可变默认值默认参数只在函数定义时求值一次。所以默认值绝不能用可变对象# 适用于 Python 3 全版本# 错误示范默认列表被所有调用共享def append_bad(item, bucket[]):bucket.append(item)return bucketprint(append_bad(1)) # [1]print(append_bad(2)) # [1, 2] —— 上次的结果还在# 正确示范用 None 作哨兵def append_good(item, bucketNone):if bucket is None:bucket []bucket.append(item)return bucketprint(append_good(1)) # [1]print(append_good(2)) # [2]四、模块与包任何.py文件都是一个模块可以用import引入。目录下有__init__.py或符合命名空间包规则的目录构成包。# 适用于 Python 3 全版本import math # 整体导入from math import sqrt # 导入具体名字import os.path as osp # 起别名三种导入方式的取舍import math最清晰调用处写math.sqrt来源明确from math import sqrt更短但可能污染命名空间别名适合长包名。不要写from math import *它会把未知数量的名字塞进当前命名空间难以排查。被导入的模块顶层代码只在第一次导入时执行一次之后从sys.modules复用。五、异常结构try语句的完整形态是四段# 适用于 Python 3 全版本def parse_int(text):try:value int(text)except ValueError:print(f{text!r} 不是整数)return Noneelse:print(转换成功走 else 分支)return valuefinally:print(无论成功与否都会执行 finally)print(parse_int(42))执行顺序规则try块抛出异常 → 跳到匹配的excepttry块没有异常 → 执行elseelse是「没出错时」执行finally永远执行即使前面return了也先执行finallyexcept只捕获列出的异常类型不写类型等于捕获所有见坑点 5实战用正确的结构写一个小工具# 适用于 Python 3.8import sysfrom pathlib import Pathclass Counter:统计文本文件的行数与字符数。def __init__(self, path):self.path Path(path)def count(self):try:text self.path.read_text(encodingutf-8)except FileNotFoundError:raise ValueError(f文件不存在{self.path}) from Nonelines text.splitlines()return len(lines), len(text)def main(argv):if len(argv) ! 2:print(用法python count.py 目标文件)return 1try:lines, chars Counter(argv[1]).count()except ValueError as err:print(f错误{err})return 1print(f行数 {lines}字符数 {chars})return 0if __name__ __main__:sys.exit(main(sys.argv))这段代码体现了全部五种结构模块导入、类与__init__、异常捕获、Path对象承载状态、main函数配合入口守卫。常见坑点函数里给全局变量赋值忘了global❌ 直接counter 1报UnboundLocalError✅ 先global counter再赋值或干脆把状态放进类/参数传递用可变对象作默认参数❌def f(x, bucket[]):导致多次调用共享同一个列表 ✅def f(x, bucketNone):里判None再新建闭包在循环里的延迟绑定❌funcs [lambda: i for i in range(3)]调用时全部返回2✅ 用默认参数固化当时的值[lambda ii: i for i in range(3)]把可变类属性当实例属性❌ 在类体里写items []所有实例共享同一个列表 ✅ 在__init__里self.items []用裸except:吞掉一切❌try: ... except: pass连KeyboardInterrupt都吞了按 Ctrl-C 停不下来 ✅ 只捕获预期的具体异常如except ValueError:循环导入❌a.py导入bb.py又导入a运行时报ImportError或拿到半成品模块 ✅ 拆分公共依赖到第三个模块或把导入移进函数体延迟执行以为list.sort()返回新列表❌sorted_list nums.sort()得到None✅sort()是原地排序并返回None要新列表用sorted(nums)以为dict.keys()返回列表❌ 对返回值做下标访问d.keys()[0]✅ 它返回视图对象可迭代、可做集合运算要列表写list(d.keys())总结结构核心规则变量名字绑定到对象类型属于对象可变性list/dict/set可变str/tuple/frozenset不可变控制流缩进定块break只跳一层作用域LEGB重新绑定全局需global默认参数定义时求值一次勿用可变对象模块import顶层代码只执行一次异常try/except/else/finallyelse在无异常时执行返回None的陷阱sort()、reverse()都是原地操作把程序拆成「数据在类里、逻辑在函数里、错误在try里、入口在main里」这几块代码就从「能跑」变成「敢改」。上面八个坑点全部属于不报错但行为错误的类型比SyntaxError危险得多——记住它们的正确形态是基础结构这一课真正的产出。