前言
「快速入门」最大的陷阱,是把「语法速查」当成「上手」。查一次sorted的签名不难,难的是拿到一份陌生代码时知道从哪看起、写一个小工具时知道骨架怎么搭。这篇指南就按这两条线组织:上半篇给你能直接套用的骨架,下半篇给你一眼看懂别人代码的阅读顺序。
提前说明版本问题:Python 2.7 已于 2020 年 1 月 1 日停止维护。老资料里的print "x"、raw_input()、except X, e、d.iteritems()在 Python 3 里全部不能用。本文只写 Python 3,示例适用于 3.8 及以上。
另外纠正一个常见说法:「Python 是解释型语言,不需要编译」。CPython 是先把源码编译成字节码再由虚拟机执行的,dis.dis()能看到指令。知道你写的东西会先变成字节码,有助于理解为什么模块会被缓存成__pycache__里的.pyc。
一、五分钟核心语法速查
| 赋值 | a = 1;a, b = 1, 2 | 是名字绑定,不是复制 |
| 输出 | print(x, sep=..., end=...) | print是函数,不是语句 |
| 条件 | if / elif / else | 冒号 + 缩进,无switch |
| 循环 | for x in iterable:/while cond: | range左闭右开 |
| 函数 | def f(a, b=1, *args, **kw): | 默认参数只求值一次 |
| 类 | class C:/def __init__(self): | 首参是self |
| 异常 | try / except X as e / finally | 不写裸except: |
| 导入 | import x/from x import y | import会执行顶层代码 |
| 推导式 | [f(x) for x in xs if p(x)] | 有自己的作用域 |
二、五套实战骨架(可直接运行)
骨架一:脚本
最小可直接运行的脚本长这样:
# 适用于 Python 3.8+
"""一句话说明这个脚本做什么。"""
import sys
def main(argv):
if len(argv) != 2:
print(f"用法:python {argv[0]} 目标参数", file=sys.stderr)
return 2
print("收到:", argv[1])
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))
三个约定:main()接收参数列表并返回退出码;错误信息写到sys.stderr而不是stdout;raise SystemExit(main(...))把返回值当退出码。sys.argv[0]是脚本自身的名字。
骨架二:命令行工具
参数超过两个就别手撸sys.argv了:
# 适用于 Python 3.8+
import argparse
def build_parser():
parser = argparse.ArgumentParser(description="演示用命令行工具")
parser.add_argument("src", help="输入路径")
parser.add_argument("-o", "--output", default="-", help="输出路径,默认标准输出")
parser.add_argument("-n", "--count", type=int, default=10, help="处理条数")
parser.add_argument("-v", "--verbose", action="store_true", help="输出详细日志")
return parser
if __name__ == "__main__":
args = build_parser().parse_args()
print(args.src, args.output, args.count, args.verbose)
位置参数不带前缀;可选参数带-/--;type=int负责转换和报错;action="store_true"把开关变成布尔值。argparse还会自动生成-h帮助。
骨架三:文件处理
# 适用于 Python 3.8+
from pathlib import Path
def process(src: Path, dst: Path) -> int:
"""逐行处理,返回处理的行数。"""
count = 0
with src.open(encoding="utf-8") as fin, dst.open("w", encoding="utf-8") as fout:
for line in fin:
stripped = line.rstrip("\n")
if not stripped:
continue
fout.write(stripped.upper() + "\n")
count += 1
return count
if __name__ == "__main__":
print(process(Path("in.txt"), Path("out.txt")))
要点:with支持一行开两个文件(3.10 起还能写成带括号的多行形式);encoding必须显式写;大文件用for line in fin:惰性读,不要readlines()。
骨架四:异常与日志
# 适用于 Python 3.8+
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
try:
result = divide(1, 0)
except ValueError as exc:
logger.warning("计算失败:%s", exc)
else:
print(result)
finally:
logger.info("结束")
用logger.warning("模板 %s", 值)而不是 f-string 拼好再传,这样日志级别被过滤时不会白白做字符串格式化。异常只捕获你真正能处理的那一层,捕获不了就让它往上抛。
骨架五:正则与 JSON
# 适用于 Python 3.8+
import json
import re
KV = re.compile(r"(\w+)=(\d+)")
text = "set width=20 and height=10"
print(KV.findall(text)) # [('width', '20'), ('height', '10')]
print(re.sub(r"\d+", "#", text)) # set width=# and height=#
print(re.search(r"\d+", text).group())
payload = json.dumps({"名字": "小明"}, ensure_ascii=False, indent=2)
print(payload)
print(json.loads(payload)["名字"])
re.compile后复用模式对象,比每次调用re.findall传字符串更清晰。re.findall的签名是re.findall(pattern, string, flags=0),有捕获组时返回元组列表;re.sub是re.sub(pattern, repl, string, count=0, flags=0)。json.dumps的ensure_ascii默认是True,中文会变成\uXXXX。
三、几个必须先分清的点
re.match与re.search不一样
# 适用于 Python 3.8+
import re
print(bool(re.match(r"\d+", "abc123"))) # False,开头不是数字
print(bool(re.search(r"\d+", "abc123"))) # True
print(bool(re.fullmatch(r"\d+", "123"))) # True
print(bool(re.fullmatch(r"\d+", "123abc"))) # False
用re.match(pattern, s)却期望「整串是数字」是高频错误,正确工具是re.fullmatch。另外不要用正则解析 HTML 或 JSON——那是另一类工具的事。
四、八步读懂一份陌生的 Python 代码
拿到别人写的脚本,按这个顺序看,比从第一行读起快得多:
- 看有没有
if __name__ == "__main__":,找到真正的入口。
- 看 import:标准库 / 第三方 / 本地三组,能大致判断它在干什么。
- 看顶层常量与配置:大写名字通常是常量。
- 看
def的签名:参数名和默认值比函数体更能说明接口。
- 看类型标注(如果有):
def f(x: int) -> str:直接告诉你不该传什么。
- 看
class的__init__:实例属性全在那里。
- 看异常处理:
except捕获什么,说明作者预见什么失败。
- 最后才看循环与分支的细节。
# 适用于 Python 3.8+
from dataclasses import dataclass
@dataclass
class Job:
name: str
retries: int = 3
tags: tuple = ()
def label(self) -> str:
return f"{self.name}({self.retries})"
print(Job("backup", tags=("nightly",)))
类型标注和dataclasses是阅读代码时最省力的线索:@dataclass会自动生成__init__、__repr__、__eq__,所以类体里看不到构造逻辑是正常的,不必到处找。
常见坑点
- ❌ 用
re.match(r"\d+", s)判断「整个字符串是数字」 ✅re.match只要求开头匹配,"123abc"也会通过;要整串匹配用re.fullmatch。
- ❌ 以为
input()返回数字 ✅ 它永远返回str,input() + 1会抛TypeError;先转换并处理ValueError。
- ❌ 用
logging.warning(f"值 {v}")图省事 ✅ 写logging.warning("值 %s", v),让格式化在级别过滤之后才发生。
- ❌ 在脚本里直接用
sys.argv[1]✅ 参数缺失会抛IndexError;用argparse或先检查len(sys.argv)。
- ❌ 打开文件不写
encoding✅ 默认编码是平台相关的,跨平台必须写encoding="utf-8"。
- ❌ 照着老资料写
except ValueError, e:✅ 这是 Python 2 语法,早已移除;Python 3 写except ValueError as e:。
- ❌ 用裸
except:让脚本「不崩」 ✅ 它会吞掉KeyboardInterrupt和SystemExit,脚本会变成 Ctrl+C 都杀不掉的僵尸;至少写except Exception:。
- ❌ 认为
import不会执行任何代码 ✅import会执行模块顶层语句,有副作用的代码要放进main()并用if __name__ == "__main__":保护。
总结
| 写脚本 | main(argv)+raise SystemExit(...) | 返回退出码,错误走 stderr |
| 收参数 | argparse.ArgumentParser | type=负责转换与报错 |
| 处理文件 | with src.open(encoding="utf-8") as fin: | 显式编码,惰性逐行 |
| 打日志 | logging.getLogger(__name__) | 用%s占位,不用 f-string |
| 匹配文本 | re.compile+fullmatch/search | 分清 match 与 fullmatch |
| 序列化 | json.dumps(..., ensure_ascii=False) | 中文要关掉 ASCII 转义 |
| 读代码 | 入口 → import → 签名 → 类 → 异常 | 别从第一行硬读 |
快速入门的正确姿势,不是把语法表背下来,而是记住几个能改就用的骨架,再带着「名字绑定不是复制」「input()返回字符串」「re.match不要求整串匹配」这三类高频陷阱去读别人的代码。骨架省下的是敲键盘的时间,陷阱省下的是调试的时间。