
Python 基础笔记:从语法到实用脚本
这份笔记适合已经写过几行 Python、想把零散语法串成一个可用脚本的人。前半部分是可以回查的语法要点与易错点,后半部分完成一个“读取 CSV 并统计学习时间”的命令行脚本。示例使用 Python 3.10 及以上版本,仅依赖标准库。
1. 值、类型与表达式
| 写法 | 含义 | 容易忽略的地方 |
|---|---|---|
name = "Python" | 把值绑定到变量名 | = 是赋值,== 才是比较值是否相等 |
42、3.14、"42" | 整数、浮点数、字符串 | "42" + 1 会报错;先用 int("42") 转换 |
True、False、None | 布尔值与空值 | 判断是否为 None,通常写 value is None |
a and b、a or b、not a | 逻辑运算 | and、or 返回操作数,不保证返回布尔值 |
f"学了 {minutes} 分钟" | 格式化字符串 | 花括号里可以写表达式,如 {minutes / 60:.1f} |
input() 的结果始终是字符串;要计算分钟数,必须先转换,并考虑非法输入:
raw = "45" # 实际交互时可换成 input("学习了多少分钟?")minutes = int(raw)print(f"学习了 {minutes} 分钟")bool("") 是 False,但 bool("False") 是 True:非空字符串为真,字符串内容不会自动解析成布尔值。
2. 容器:选对数据形状
| 类型 | 常用场景 | 常用操作 |
|---|---|---|
list 列表 | 有顺序、可增删的一组值 | append()、索引、切片 |
tuple 元组 | 固定顺序、不打算增删的一组值 | 解包、索引 |
dict 字典 | 用键查找字段或累计结果 | data[key]、get()、items() |
set 集合 | 去重、判断成员是否存在 | add()、in |
records = [ {"subject": "Python", "minutes": 45}, {"subject": "英语", "minutes": 30},]
print(records[0]["subject"]) # Python;列表索引从 0 开始print(records[:1]) # 返回包含第一条记录的新列表print({row["subject"] for row in records}) # 去重后的科目集合record["minutes"] 在键不存在时抛出 KeyError;record.get("minutes", 0) 则返回默认值 0。后者适合累计计数,但不能用默认值掩盖必须存在的输入字段。
变量存放的是对对象的引用。b = a 不会复制列表;a.copy() 只复制最外层,嵌套的字典仍被共享:
a = [{"minutes": 45}]b = a.copy()b[0]["minutes"] = 60print(a[0]["minutes"]) # 603. 控制流与推导式
if 选择分支,for 遍历数据,continue 跳过当前轮。Python 用缩进表示代码块:
for index, record in enumerate(records, start=1): if record["minutes"] <= 0: continue print(f'{index}. {record["subject"]}: {record["minutes"]} 分钟')enumerate() 同时给出序号和元素;zip(a, b) 可以并行遍历两组数据,但默认会在较短的一组结束时停止。
推导式适合简单的“筛选 + 转换”,不要把复杂的多层判断挤进一行:
long_sessions = [row for row in records if row["minutes"] >= 40]total = sum(row["minutes"] for row in records)第一行创建列表;第二行传给 sum() 的是生成器表达式,按需提供值,不先构造另一份列表。
4. 函数、类型标注与异常
函数用参数接收输入,用 return 交还结果。print() 只负责显示,不等于返回值。类型标注能帮助阅读和静态检查,不会自动校验运行时输入。
def total_minutes(records: list[dict[str, object]]) -> int: return sum(int(row["minutes"]) for row in records)这段写法假设每条记录都有可转换的 minutes。当数据来自文件、网络或用户输入时,应先校验,再交给计算函数。也要避开可变默认参数:
def add_subject(subject: str, subjects: list[str] | None = None) -> list[str]: if subjects is None: subjects = [] subjects.append(subject) return subjects如果把默认值直接写成 [],多次调用会共享同一个列表。上面每次未传入列表时都会新建一个。
异常处理只包住可能失败的操作,并捕获预期类型:
try: minutes = int("四十五")except ValueError as exc: print(f"分钟数无效:{exc}")ValueError 表示值的格式不合要求;文件打不开通常表现为 OSError。不要用宽泛的 except Exception 把自己写错的代码也当成普通输入错误。
5. 文件、模块与脚本入口
pathlib.Path 处理路径,with 管理文件的打开和关闭。Path(__file__).with_name("study.csv") 指向脚本旁边的 CSV,运行命令时所在目录改变也不影响它。
from pathlib import Path
source = Path(__file__).with_name("study.csv")with source.open("r", encoding="utf-8") as file: text = file.read()if __name__ == "__main__": 用来区分“直接运行脚本”和“被其他文件导入”。把主要工作写进 main(),导入模块时就不会意外开始读文件、打印或退出。
6. 完整脚本:统计 CSV 中的学习时间
在同一个目录中创建 study.csv:
date,subject,minutes2026-10-07,Python,452026-10-08,英语,302026-10-08,Python,25再创建 study.py。读取函数负责把外部文本变成经过校验的记录,统计函数只做计算,main() 负责输出和退出状态:
import csvimport sysfrom collections.abc import Iterable, Iteratorfrom dataclasses import dataclassfrom datetime import datefrom pathlib import Path
@dataclass(frozen=True)class StudyRecord: day: date subject: str minutes: int
def read_records(path: Path) -> Iterator[StudyRecord]: with path.open("r", encoding="utf-8-sig", newline="") as file: reader = csv.DictReader(file) if reader.fieldnames != ["date", "subject", "minutes"]: raise ValueError("表头必须是 date,subject,minutes")
for row in reader: line_no = reader.line_num if None in row: raise ValueError(f"第 {line_no} 行:字段数量过多")
day_text = (row.get("date") or "").strip() subject = (row.get("subject") or "").strip() minutes_text = (row.get("minutes") or "").strip()
try: day = date.fromisoformat(day_text) except ValueError as exc: raise ValueError(f"第 {line_no} 行:无效日期 {day_text!r}") from exc
if not subject: raise ValueError(f"第 {line_no} 行:科目不能为空")
try: minutes = int(minutes_text) except ValueError as exc: raise ValueError(f"第 {line_no} 行:分钟数不是整数 {minutes_text!r}") from exc
if minutes <= 0: raise ValueError(f"第 {line_no} 行:分钟数必须大于 0")
yield StudyRecord(day, subject, minutes)
def summarize(records: Iterable[StudyRecord]) -> dict[str, int]: totals: dict[str, int] = {} for record in records: totals[record.subject] = totals.get(record.subject, 0) + record.minutes return totals
def main() -> int: source = Path(__file__).with_name("study.csv")
try: totals = summarize(read_records(source)) except (OSError, ValueError) as exc: print(f"读取失败:{exc}", file=sys.stderr) return 1
for subject, minutes in sorted( totals.items(), key=lambda item: (-item[1], item[0]) ): print(f"{subject}:{minutes} 分钟") print(f"总计:{sum(totals.values())} 分钟") return 0
if __name__ == "__main__": raise SystemExit(main())运行 python study.py,Windows 上也可以使用 py study.py。预期输出:
Python:70 分钟英语:30 分钟总计:100 分钟脚本里的几个关键连接点
- 文本到对象:
csv.DictReader读到的正常单元格是字符串,缺失字段可能得到None;date.fromisoformat()和int()在入口处转换并校验。StudyRecord的标注不会代替这一步。 - 生成器到统计函数:
read_records()中的yield使它按需读取。调用函数本身还没有打开文件;summarize()遍历它时才开始读取。因此try要包住整个统计调用。 - 文件生命周期:完整遍历或读取函数抛错时,
with会关闭文件。如果只取生成器的前几条就停止,文件可能仍保持打开;需要显式关闭生成器,或为小文件改用列表。 - 错误到退出码:读取层知道出错行号,就抛带行号的
ValueError;main()决定把信息写到标准错误输出并返回1。正常完成返回0。 - 内存使用:读取阶段不保存全部行;统计字典的大小主要由不同科目的数量决定。
7. 自测与扩展
先手动把第三行改成 2026-10-08,英语,-5:脚本应报告“第 3 行:分钟数必须大于 0”。把日期改成 2026-02-30、删除文件、清空文件内容,也能检查对应的错误分支。CSV 为空时表头缺失,脚本会报告表头错误;只有表头而没有数据时,总计是 0。
统计函数可以脱离文件单独验证。把下面的代码另存为同目录的 test_study.py,运行 python test_study.py:
from datetime import datefrom study import StudyRecord, summarize
records = [ StudyRecord(date(2026, 10, 7), "Python", 45), StudyRecord(date(2026, 10, 8), "Python", 25),]assert summarize(records) == {"Python": 70}assert summarize([]) == {}下一步可以写一个按日期汇总的函数。保持 read_records() 不变,只改变统计键,再比较两份汇总代码有哪些可以复用。这样练习比单纯背诵更多语法更接近实际写脚本的过程。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 2 天
部分内容可能已过时
评论