FPS --
1938 字
10 分钟
Python 基础笔记:从语法到实用脚本

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"] = 60
print(a[0]["minutes"]) # 60

3. 控制流与推导式#

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:

study.csv
date,subject,minutes
2026-10-07,Python,45
2026-10-08,英语,30
2026-10-08,Python,25

再创建 study.py。读取函数负责把外部文本变成经过校验的记录,统计函数只做计算,main() 负责输出和退出状态:

study.py
import csv
import sys
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from datetime import date
from 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:

test_study.py
from datetime import date
from 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() 不变,只改变统计键,再比较两份汇总代码有哪些可以复用。这样练习比单纯背诵更多语法更接近实际写脚本的过程。

Python 基础笔记:从语法到实用脚本
https://www.pardofelis.top/posts/02b8403c/
作者
所愿Syuan
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...

最后更新于 ,距今已过 2 天

部分内容可能已过时