第 1 章 语法与执行模型¶
配套例题:PIO1 来源:菜鸟教程 Python3 基础语法、解释器、注释
这一章讲 Python 程序「长什么样、怎么被执行」。内容不多,但每一条都会在后面反复用到。
先约定几个判题术语¶
后面各章会反复用到这几个缩写,它们来自在线判题系统(OJ,Online Judge)的返回结果:
| 缩写 | 全称 | 含义 |
|---|---|---|
| AC | Accepted | 通过,所有测试点的输出都对 |
| WA | Wrong Answer | 答案错误,程序正常结束但输出和标准答案不符 |
| TLE | Time Limit Exceeded | 超时,运行时间超过题目给的限制 |
| MLE | Memory Limit Exceeded | 超内存 |
| RE | Runtime Error | 运行时错误,本质是有异常没被捕获(见 16-模块-异常-文件) |
| CE | Compile Error | 编译错误,Python 里就是语法错误 |
还有一个不是判题结果、但后面会反复提到的词:对拍—— 写一份朴素但一定正确的暴力程序,再写一个随机数据生成器, 让两份代码跑同一批随机数据并比较输出,用来在本地定位 WA 的成因。
1.1 一个程序的骨架¶
算法题里的 Python 程序几乎都长这样:
import sys
def main(): # 主逻辑包进函数,局部变量访问更快
data = sys.stdin.buffer.read().split() # 一次性读完整个输入,切成 token 列表
...
sys.stdout.write(...) # 结果攒够了一次写出,避免反复 print
if __name__ == "__main__": # 直接运行时才执行 main
main()
四个部分:导入、函数定义、主逻辑、入口守卫。竞赛里也可以完全不写函数直接平铺,
但把逻辑放进函数里会更快——CPython 访问函数局部变量用的是数组下标(LOAD_FAST),
访问模块级全局变量要查字典(LOAD_GLOBAL)。循环密集的题目,这一条能带来 20%–30% 的提速。
if __name__ == "__main__": 的作用是:当文件被直接运行时 __name__ 等于 "__main__",
被 import 时等于模块名。竞赛中不 import 自己的文件,写不写都行,但它是良好习惯。
1.2 缩进就是语法¶
Python 用缩进表示代码块,这是它和 C++/Java 最大的形式差异。
规则:
- 同一个代码块的所有语句必须缩进量完全一致,多一个空格都是
IndentationError。 - 官方推荐 4 个空格,不要用 Tab。混用 Tab 和空格在 Python 3 里直接报错。
- 冒号
:后面必须换行并缩进(单行写法见 1.5)。
从 C++ 转过来最容易犯的错:忘记冒号,或者复制代码后缩进层级错乱。 Python 不会因为缩进错误给出「语义错误」,它只会报语法错,所以反而好排查。
空行¶
函数之间空两行、方法之间空一行是 PEP 8 规范。空行不影响语义, 但在交互式解释器里,空行表示「这个代码块输入完了」。
1.3 标识符与关键字¶
标识符(变量名、函数名)规则:
- 首字符必须是字母或下划线
_,其余可以是字母、数字、下划线。 - 区分大小写,
Sum和sum是两个东西。 - Python 3 允许中文标识符(
变量 = 1合法),但不要在竞赛里用。
命名约定:
| 形式 | 含义 |
|---|---|
_name |
内部使用,from module import * 不会导入 |
__name |
类中触发名称改写(name mangling) |
__name__ |
系统定义的特殊名字,不要自己造 |
35 个保留关键字,不能用作标识符:
False None True and as assert async await
break class continue def del elif else except
finally for from global if import in is
lambda nonlocal not or pass raise return try
while with yield
实战坑:
sum、max、min、list、dict、set、id、input、str、type这些不是关键字(所以能赋值),但它们是内置函数。 写sum = 0之后再调用sum(a)就会TypeError: 'int' object is not callable。 这是竞赛里非常高频的自坑,尤其sum、max、min。
1.4 注释¶
放在模块、函数、类第一行的字符串是 docstring,可通过 __doc__ 访问:
def gcd(a, b):
"""求 a 和 b 的最大公约数。"""
while b:
a, b = b, a % b
return a
print(gcd.__doc__) # 求 a 和 b 的最大公约数。
本教程所有题解都用 docstring 写明「这题考什么、坑在哪」。
1.5 语句的分行与合并¶
多行语句¶
一条语句太长时,有两种续行方式:
[]、{}、() 内部的换行不需要任何续行符:
同一行多条语句¶
用分号分隔:
PEP 8 不推荐,但竞赛中用于压缩「读一个数并推进游标」这类高度模式化的代码是可以接受的, 本教程的题解里会这样用。
单行代码组¶
冒号后如果只有一条简单语句,可以写在同一行:
嵌套的复合语句不能这样写。
1.6 pass 与省略¶
pass 是空语句,用于语法上需要一条语句但逻辑上什么都不做的位置:
...(Ellipsis)也可以当占位符,但语义上它是一个对象,竞赛中用 pass 即可。
1.7 print 的完整用法¶
| 参数 | 作用 | 竞赛用法 |
|---|---|---|
*objects |
任意多个对象 | print(a, b, c) 自动用空格分隔 |
sep |
对象之间的分隔符 | print(*a) 输出列表元素,空格分隔 |
end |
结尾字符 | end="" 不换行 |
flush |
是否立即刷新 | 交互题必须 flush=True |
几个高频写法:
print(*a) # 列表按空格分隔输出:等价于 " ".join(map(str, a))
print(*a, sep="\n") # 每个元素一行
print(a, b, sep="") # 紧挨着输出
print("no newline", end="") # 不换行
print(*a)要逐个把元素转成字符串、再走一遍sys.stdout.write慢;元素上万时差距就很明显。 输出量大时用sys.stdout.write("\n".join(map(str, a)) + "\n")。
1.8 等待用户输入¶
竞赛中绝对不要给 input() 传提示字符串——提示会被打进标准输出,直接 WA。
input() 遇到 EOF 抛 EOFError,这是 EOF 模式题的常见处理点(见
20-输入输出处理,
那里也说明了为什么更推荐迭代 sys.stdin)。
1.9 import 的四种形式¶
import sys # 导入模块,用 sys.stdin
import sys as s # 起别名
from math import gcd # 只导入某个名字,直接用 gcd
from math import gcd, lcm # 导入多个
from math import * # 导入全部(不推荐,会污染命名空间)
竞赛常用导入清单:
import sys
from collections import deque, defaultdict, Counter
from heapq import heappush, heappop, heapify
from bisect import bisect_left, bisect_right, insort
from math import gcd, isqrt, inf
from itertools import permutations, combinations, accumulate
from functools import lru_cache, cmp_to_key
from math import *会引入pow(覆盖内置的三参数pow)、e、gamma等一堆名字, 其中math.pow只返回 float,会让pow(a, b, mod)这种模幂写法直接报错。 这是个隐蔽且致命的坑,所以永远显式导入。
1.10 命令行参数¶
sys.argv[0] 是脚本名本身。OJ 不会传参数,但本地对拍时很有用:
# gen.py —— 随机数据生成器,用法 python gen.py <seed>
import random
import sys
# 用命令行传进来的 seed 固定随机序列:同一个 seed 永远生成同一份数据。
# 发现某个 seed 让两份程序输出不一致时,就能原样复现出那组数据
random.seed(int(sys.argv[1]))
n = random.randint(1, 10) # 规模故意取小,方便肉眼比对
print(n)
print(*[random.randint(1, 100) for _ in range(n)])
1.11 例题:PIO1 只有输出¶
本题无输入,输出字符串
Hello Nowcoder!。
一行结束。但它验证了本章几个点:程序不需要 main 函数也能跑、print 自动换行、
无输入题不要去读 stdin(有些评测机不会关闭 stdin,input() 会阻塞到 TLE)。
完整题解:solutions/PIO1.py
1.12 本章速查¶
| 要点 | 结论 |
|---|---|
| 缩进 | 4 空格,不混 Tab,块内必须一致 |
| 变量名 | 别用 sum/max/min/list/id/input 等内置名 |
| 续行 | 优先靠括号,别用反斜杠 |
print |
大量输出改用 sys.stdout.write |
input() |
竞赛中不要传提示串 |
import |
永远显式导入,别用 from math import * |
| 性能 | 把主逻辑包进函数,比模块级平铺快 20%–30% |