尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

单元 07 · 零基础通道 · 语法再走一遍

单元 07 · 零基础通道 · 语法再走一遍 阅读时间约 35 分钟 · 本篇任务读一份名单文件算出去重人数与前五名吐出能直接画图的数据一、周日下午教练发来一份名单你被拉去当校游泳队的记分员。教练在手机上记签到导出一份names.txt一行一个名字。他给你三个问题外加一个要求这次一共签到多少次一个人来几次就算几次实际来了多少个人谁来得最勤前五名是谁他要在队会上投影一张图。你手上已经有 02–05 的语法。卡住的地方多半是「第一步该写什么」。先看这份文件长什么样。它一共 35 行其中 5 行是空行落在第 5、8、13、17、24 行。注意第 13 行里其实是三个空格它只是看着像空行$ wc -l u07_data/names.txt 35 u07_data/names.txt再跑脚本。这条命令的完整路径写全为的是你能直接粘cd/home/lijiong/obsidian-vault/assets/Python学习/Python入门/Zero to Python/code./.venv/bin/python u07_warmup.py u07_data/names.txt实测输出如下一个字没改名单u07_data/names.txt 解释器3.14.4 有效行数含重复签到30 跳过的空行5 去重后人数10 去重后名单字典序Alex、Ali、Darius、Hannah、Joe、Mara、Ruth、Sam、李雷、韩梅梅 出现次数前 5 名 Darius | ████████████████████ 7 Ruth | ██████████████ 5 Ali | ███████████ 4 Hannah | ███████████ 4 Sam | █████████ 3 可直接画图的两列数据CSV全部名字按次数降序 name,count Darius,7 Ruth,5 Ali,4 Hannah,4 Sam,3 李雷,2 Alex,2 韩梅梅,1 Mara,1 Joe,1教练的四个问题在这里都有答案了。有效签到 30 次加上跳过的 5 行正好对上wc -l的 35 行一笔账不差。实际来了 10 个人。前五名是 Darius 7 次、Ruth 5 次、Ali 4 次、Hannah 4 次、Sam 3 次。有三处细节值得你停下来看它们各自对应一个语法点。第一处Darius和Sam后面那五个名字。CSV 一共 11 行第一行是表头后面 10 行是全部名字不是只有前五。图要的是全量数据前五名只是打印给人看的那一屏。这两列是画图工具之间的通用入口——Excel 里选中这两列就能插柱状图gnuplot 认这个格式单元 12 要用的 matplotlib 也认它。第二处条形图那五行的名字列对齐了。Darius六个字母、Ruth四个、Ali三个右边的竖线却落在同一列上。用len()算出来的长度做不到这件事因为名单里还有「李雷」和「韩梅梅」——len(李雷)是 2可它在屏幕上占 4 列。第三处第 33 行写的是「Ruth 」末尾带一个空格。脚本把这一行算成了 Ruth所以 Ruth 那 5 次里有它一份。这是单元 02 讲过的strip()在这里干活。同样地第 13 行那三个空格落进了「跳过的空行5」这个计数里——对统计来说「真空行」和「只有空格的行」一样都是没有数据。到这里你已经看到这一篇的终点一份能被下一个环节直接用的输出。图怎么画是单元 12 的事这一篇只保证喂过去的数据是对的。二、五条准则准则 1 · 一次只加几行加完立刻跑一次Head First Python3e ch 1「diving in」整章都在教一件事把代码敲进单元格按 ShiftEnter 看结果。原话是「When you press ShiftEnter, the code in the currently selected cell runs.」同章解释为什么可以这么干「Python is an interpreter that is designed to run each line of code as and when it sees it.」判据你写下的每一段代码都要能在 30 秒内跑出一次看得见的结果。做不到就说明这一步加得太大了拆小。u07_warmup.py的 12 个函数就是按这个顺序长出来的先让read_roster()能读文件、把空行数出来再让count_names()能数次数然后text_bars()拼图最后main()把它们串起来。每个函数都能单独跑一次。比如你只关心计数这一段不用跑整个脚本$ ./.venv/bin/python -c import u07_warmup as w; print(w.count_names([Ali,Ali,Ruth])) Counter({Ali: 2, Ruth: 1})这条命令能成立本身就是准则 3 的证据count_names有名字、有参数、有返回值所以它能被单独拎出来问一次。准则 2 · 数据先进列表再谈怎么处理ch 2「lists of numbers」的开场白原话是「sucking its lines into a list, one of Python’s most-powerful built-in data structures」——从文件里把行吸进列表这是它选定的第一个动作。同章另一句更像是写给入门者的「The more code you write, the better you get. It’s that simple.」判据在写任何统计之前先把输入落成一个list。理由是迭代器只能走一次。summarize()里那句materialized list(names)就是这条准则的物证——下面要数两遍先把数据落成列表第二遍才拿得到。这条准则带来的一个副作用是好的列表是你能print()出来看的东西。写统计代码卡住的时候先把列表打出来比盯着函数猜快得多。准则 3 · 同一段逻辑要跑第二遍就给它一个名字ch 3「list of files」的开场原话是「Your code can’t live in a notebook forever. It wants to be free.」从笔记本单元格到.py文件、从一段死代码到一个函数、从一个函数到一个模块这一章走的就是这条路。判据你第二次复制同一段代码的时候停手改成函数。u07_warmup.py里 12 个函数每个名字都是一句判据读函数名就知道它答应给你什么函数它答应的事read_roster(path)读名单交出「有效名字列表」和「跳过了几行空行」count_names(names)交出「名字 → 次数」的计数unique_names(names)交出「去重且有序」的名字列表top_names(counter, limit)交出前limit名按次数降序summarize(names, ...)把上面几步压成一份RosterStatsdisplay_width(text)/pad(text, width)按屏幕列数算宽度、按宽度补空格text_bars(rows, slots)交出那张文本条形图to_csv(rows)/write_csv(path, rows)交出两列 CSV 的文本、以及写文件parse_args(argv)/main(argv)解析参数、跑完整流程并交出退出码code/tests/test_u07_warmup.py里 36 条断言就是按这些名字组织的——函数切得清楚测试才写得下去。准则 4 · 要出图不一定要先学画图库字符串就是最便宜的输出格式ch 4「formatted string literals」的副标题叫「Make Charts from Data」开场原话是「In this chapter you finally get around to producing bar charts for the Coach. You’ll do this using nothing but strings.」判据对方只需要「看见」数据时先给文本对方要把数据拿去自己画时给两列 CSV。本篇两个都给了代码量加起来不到 20 行没引入任何第三方库。这不是让你以后都不学画图库。单元 12 会正经用 matplotlib。这条准则管的是顺序先用最便宜的方式把数据拿出来看一眼确认数据本身没问题再去调样式。准则 5 · 去重靠 set计数靠 dict保序靠 listch 5「getting organized」开场原话「your choice of which data structure to use can be critical, and is often the difference between a messy solution that works and an elegant solution that works well.」ch 0 讲集合那一页写得更直接「Sets in Python are likes sets from math class: they contain a collection of unique values where duplicates are not allowed.」原文如此likes是书里的笔误当作like读。判据选容器之前先问三句话——要不要唯一要不要计数要不要顺序把这三句套到教练的名单上「实际来了多少个人」要的是唯一性交给setunique_names()干这个。「每人来了几次」要的是计数交给dictcount_names()用Counter干这个。「前五名」要的是顺序次数降序并列时按先来后到交给listtop_names()干这个。三句话问完容器就定了不用试错。清单体、报告体里那些「一个数据该放哪种结构」的争论八成是这三句话里有哪句没说清。三、七条配方配方都能直接抄。每条末尾写清代价与边界——什么时候不该这么写。配方 1 · 读文件with path.open(encodingutf-8-sig)# code/u07_warmup.py 第 79–88 行names:list[str][]blank0withpath.open(encodingutf-8-sig)ashandle:forlineinhandle:nameline.strip()ifnotname:blank1continuenames.append(name)returnnames,blank想先看个最短版本这一行就够$ ./.venv/bin/python -c from pathlib import Path path Path(u07_data/names.txt) names [line.strip() for line in path.open(encodingutf-8-sig) if line.strip()] print(len(names)) 30为什么 encoding 要写死。不写encoding的时候默认编码跟着平台和 locale 走。本机 3.14.4 的sys.flags.utf8_mode是 0见单元 01 的自检报告也就是说「默认用 UTF-8」这件事在 3.14 上还没成立——PEP 686 把 UTF-8 模式定为 3.15 的默认行为。同一份文件换台机器可能报UnicodeDecodeError。选utf-8-sig而不是utf-8是为了顺手丢掉文件开头可能存在的 BOMWindows 记事本另存 UTF-8 会加没有 BOM 时这两者行为完全相同。为什么用with。教材 ch 3 用一整页拆解with open(...) as ...这个结构其中一句是「The with statement opens the file before its code block runs.」with的价值在出口就算循环里抛出异常文件句柄也会被关掉不用你手写close()。代价与边界。逐行迭代不把整个文件读进内存这是本篇选它的原因没有用readlines()反过来说如果你确实需要全部内容反复随机访问那还是得落成列表内存就得付。blank把「真空行」和「只有空格的行」算作一类两者不区分——对统计没影响但如果你的下游要区分就得在这里分开计数。utf-8-sig只处理开头那一个 BOM文件中间混进的零宽字符它不管单元 02 讲过这类字符strip()也删不掉。配方 2 · 去重set之后再sorted把顺序钉住defunique_names(names:Iterable[str])-list[str]:returnsorted(set(names))教材 ch 5 把这个动作叫「The list-set-list duplicate removing trick」原话是「a useful trick is to quickly convert the list to a set, which, rather conveniently, removes all duplicate or repeated entries.」书里给的是list(set(names))。本篇在中间多插了一步sorted()理由用实测说话——三次运行同一个表达式三次顺序都不同$ ./.venv/bin/python -c names[Darius,Ruth,Ali,Hannah,Sam,李雷,Alex,韩梅梅,Mara,Joe]; print(list(set(names))) [Hannah, Alex, Joe, 李雷, Ali, 韩梅梅, Darius, Mara, Ruth, Sam] $ 同上第二次运行 [Ruth, 李雷, Sam, Ali, Joe, Darius, 韩梅梅, Hannah, Mara, Alex] $ 同上第三次运行 [韩梅梅, Mara, Darius, Sam, Joe, Ali, 李雷, Hannah, Alex, Ruth]原因是字符串的哈希值受PYTHONHASHSEED影响每个新进程重新随机而set的迭代顺序跟着哈希走。在教材那份跑一次就完事的名单上list(set(...))没问题脚本要被反复运行、数字要写进正文和测试就得多一步sorted()。代价与边界。sorted()是 O(n log n)set是 O(n)这一步是额外开销。十个人的名单可以忽略百万行时先想清楚「我要的是顺序还是只要唯一」只要唯一就别排。set要求元素可哈希字符串没问题如果你把「名字 次数」这样的元组塞进set得确认元组里每一项都可哈希。配方 3 · 计数Counter不手写字典fromcollectionsimportCounter counterCounter(names)counter[Ali]# 2counter[查无此人]# 0不是 KeyError实测这两行$ ./.venv/bin/python -c from collections import Counter c Counter([Ali,Ali,Ruth]) print(c[Ali] , c[Ali]) print(c[查无此人] , c[查无此人]) c[Ali] 2 c[查无此人] 0Counter就是一个「取值默认是 0 的字典」。教材 ch 5 花了一整章讲「要让按键取值变快就用字典」Counter是这个思路的特化版本省掉了「先判断键在不在、不在就赋 0」这一步。代价与边界。Counter是dict的子类counter[新名字] 1会把这一项写进去——这一步已经算创建而查询只该读不该写。只想问次数、不想改变计数器用counter.get(name, 0)。键必须可哈希理由同配方 2。配方 4 · 取前五most_common(5)并列顺序由排序稳定性决定counter.most_common(5)# [(Darius, 7), (Ruth, 5), (Ali, 4), (Hannah, 4), (Sam, 3)]Ali 和 Hannah 都是 4 次输出里 Ali 在前。依据在源码里不在记忆里。本机执行inspect.getsource(collections.Counter.most_common)拿到两个分支n是None时走sorted(self.items(), keyitemgetter(1), reverseTrue)给了n时走heapq.nlargest(n, self.items(), keyitemgetter(1))。而heapq.nlargest的文档字符串本机heapq.nlargest.__doc__实测是Find the n largest elements in a dataset. Equivalent to: sorted(iterable, keykey, reverseTrue)[:n]sorted是稳定排序次数相同的两项保持它们进入Counter时的先后而Counter(names)的键顺序就是名字第一次出现的先后。Ali 在 names.txt 第 3 行Hannah 在第 4 行所以 Ali 排前面。代价与边界。这条稳定性依赖输入顺序。你换了输入文件或者中间先把名字塞进set再计数并列项的先后就会变——测试文件里那条test_tied_counts_keep_first_seen_order断的就是这个顺序它只有在输入顺序固定时才成立。另外most_common()不带参数会返回全部项并按次数降序本篇的 CSV 用的就是它。一处没核实到的地方。官方Counter文档页里有一句关于并列顺序的话本篇抓取失败docs.python.org连不上我没有逐字核实到官方页面。上面关于稳定性的结论来自本机源码与三次实测不是转述。配方 5 · 出图用 f-string 拼一张文本条形图# code/u07_warmup.py 第 172–178 行longestrows[0][1]name_widthmax(display_width(name)forname,_inrows)lines[]forname,countinrows:filledmax(1,round(count/longest*slots))lines.append(f{pad(name,name_width)}|{█*filled}{count})return\n.join(lines)BAR_SLOTS是 20次数最多的那根占满 20 格其余按比例四舍五入。实测那五行Darius | ████████████████████ 7 Ruth | ██████████████ 5 Ali | ███████████ 4 Hannah | ███████████ 4 Sam | █████████ 3round()外面套的那层max(1, ...)是必要的次数为 1 的名字按比例算出来不到一格round()给他 0 格图上就和「一次都没来」分不出来了。只要次数 ≥ 1 就至少给一格这是最低表示。名字那一列的对齐用len()做不到。实测$ ./.venv/bin/python -c import unicodedata for t in [李雷,Darius,韩梅梅]: w sum(2 if unicodedata.east_asian_width(c) in (W,F) else 1 for c in t) print(f{t}: len{len(t)} 显示宽度{w}) 李雷: len2 显示宽度4 Darius: len6 显示宽度6 韩梅梅: len3 显示宽度6display_width()用unicodedata.east_asian_width()逐字符判断返回W宽或F全角时按 2 列算其余按 1 列。A歧义宽度例如±和一部分希腊字母按 1 列算。代价与边界。这套对齐只对等宽字体成立。正文、网页、聊天窗口里字体不等宽空格补出来的对齐会散。歧义宽度字符在不同终端和不同字体下宽度不一致脚本里按 1 列算是一个约定不是规范保证。真要在任何字体下都对齐得换表格渲染或绘图库单元 12。配方 6 · 交付两列 CSV表头固定# code/u07_warmup.py 第 181–193 行CSV_HEADERname,countlines[CSV_HEADER]forname,countinrows:if,innameor\ninname:raiseValueError(f名字{name!r}里有逗号或换行写不进两列 CSV)lines.append(f{name},{count})return\n.join(lines)\n写文件同样写明编码理由和配方 1 一样不写的话在非 UTF-8 的 locale 下中文名字会写成别的编码别人打开就是乱码。path.write_text(to_csv(rows),encodingutf-8)实测把结果落到/tmp$ ./.venv/bin/python u07_warmup.py u07_data/names.txt --csv /tmp/u07_counts.csv 已写入 /tmp/u07_counts.csv10 行数据 1 行表头/tmp/u07_counts.csv一共 11 行表头加 10 个名字全部按次数降序。代价与边界。这里没有实现 CSV 转义。名字里出现逗号、引号或换行标准 CSV 要加引号并转义本篇直接抛ValueError让上游数据的问题当场暴露——这份名单是教练手输的宁可在读的时候红也好过下游 Excel 打开发现列错位。换一份别人给的名单这条边界可能就不够用了那时该上csv模块那是后面单元的事。空输入返回「只有表头」的文本它仍然是合法 CSV下游读到 0 行数据不会炸。配方 7 · 出错时用退出码说话别只打印一行字# code/u07_warmup.py 第 231 行的约定# 0 正常、1 名单里没有有效数据、2 找不到文件或参数不对、3 编码读不动空名单的实测标准输出与标准错误分开看$ ./.venv/bin/python u07_warmup.py /tmp/u07_empty.txt 标准输出 名单/tmp/u07_empty.txt 解释器3.14.4 name,count 标准错误 名单里一个有效名字都没有跳过空行 0 行—— 没有数据就没法画图 下面是只有表头的两列 CSV 退出码 1文件不存在的实测$ ./.venv/bin/python u07_warmup.py /tmp/nope.txt 找不到名单文件/tmp/nope.txt 退出码 2为什么值得给退出码。人看屏幕脚本看退出码。你以后把这个脚本挂进cron或者别的地方单元 10 开始会用到对方判断成败靠的是退出码不是那行中文。0 与非 0 是唯一稳定的约定。代价与边界。这套退出码是本篇自己定的不是行业规范——sys.exit只承诺 0 表示成功。别人写脚本调用你的程序时得看你的说明才知道 1、2、3 各代表什么。错误信息往标准错误走filesys.stderr数据往标准输出走两者分开管道才不会被错误提示污染。四、印证条款下面这张表把本篇的五条准则钉回教材原文。引文都是英文原句逐字抄自Head First Python3e 的电子文本用字符串检索定位教材里的弯引号’按直引号转写其余字符逐字未改。出处只标到章不标页码这份 txt 是从两栏 PDF 抽出来的同一页的内容在文本里会交错实测页脚标记与正文行的相邻关系不可靠标页码反而会写错。有一条我核不到官方页面在表里单独标了出来没有拿它当定论用。主张出处原话要点四种内置容器是 Python 最常被称道的地方Big 4HFP 3e ch 0「why Python?」「Python’s excellent built-in support for data structures is legendary, and is often cited as the main reason most Python programmers love Python.」set天然去重HFP 3e ch 0「why Python?」「Sets in Python are likes sets from math class: they contain a collection of unique values where duplicates are not allowed.」likes为书中原文笔误小步跑起来每步都看结果HFP 3e ch 1「diving in」「When you press ShiftEnter, the code in the currently selected cell runs.」「Python is an interpreter that is designed to run each line of code as and when it sees it.」数据先落成列表再谈处理HFP 3e ch 2「lists of numbers」「sucking its lines into a list, one of Python’s most-powerful built-in data structures」同章「The more code you write, the better you get. It’s that simple.」函数与模块是代码离开笔记本的第一步HFP 3e ch 3「list of files」「Your code can’t live in a notebook forever. It wants to be free.」with open(...)在代码块运行之前就把文件打开了HFP 3e ch 3「list of files」「The with statement opens the file before its code block runs.」条形图可以只用字符串拼出来HFP 3e ch 4「formatted string literals」「In this chapter you finally get around to producing bar charts for the Coach. You’ll do this using nothing but strings.」容器的选择决定方案是否干净HFP 3e ch 5「getting organized」「your choice of which data structure to use can be critical, and is often the difference between a messy solution that works and an elegant solution that works well.」list-set-list 三连去重HFP 3e ch 5「getting organized」「a useful trick is to quickly convert the list to a set, which, rather conveniently, removes all duplicate or repeated entries.」次数并列时按首次出现的先后排本机实测inspect.getsource(collections.Counter.most_common)与heapq.nlargest.__doc__「Equivalent to: sorted(iterable, keykey, reverseTrue)[:n]」官方Counter文档页本篇未核实docs.python.org抓取失败该句不作引文使用教材要改的地方这几条已收进教材纠正表书里这么写现在要这么写依据前言「You need at least Python 3.6」「at the time of going to press, Python 3.12 is just around the corner」本专栏声明范围≥3.13全部实测在3.14.4现行稳定版 3.143.15 定于 2026-10-01 转正。书里「3.6 起就能跑」的说法请按 ≥3.13 读本机sys.version实测Python 发布日程官方发布日程本专栏已核对并标注〔官方〕ch 0 的交互流程基于 Jupyter 笔记本加 ShiftEnter本专栏的工作方式是一个.py文件加终端里一条命令./.venv/bin/python u07_warmup.py笔记本不是必需环节本篇 §一 §三 的全部实测命令ch 5 的list(set(names))假设「跑一次就完事」要被反复运行的脚本里写成sorted(set(names))set的迭代顺序不保证实测三次运行三个顺序§三 配方 2本机三次运行实测PYTHONHASHSEED每次启动随机本篇的全部命令与输出在本机跑过可复跑2026-09-21 20:55 CSTcd/home/lijiong/obsidian-vault/assets/Python学习/Python入门/Zero to Python/code./.venv/bin/python u07_warmup.py u07_data/names.txt ./.venv/bin/python-mpytest tests/test_u07_warmup.py-pno:cacheprovider第一条的输出见 §一原文粘贴。第二条的完整输出 test session starts platform linux -- Python 3.14.4, pytest-9.1.1, pluggy-1.6.0 rootdir: /home/lijiong/obsidian-vault/assets/Python学习/Python入门/Zero to Python/code configfile: pyproject.toml plugins: timeout-2.4.0 timeout: 120.0s timeout method: signal timeout func_only: False collected 36 items tests/test_u07_warmup.py .................................... [100%] 36 passed in 0.04s 退出码 0。这里不带-q是为了让36 passed in 0.04s这行摘要露出来——单个-q照样打印它本机对照过三次-q得到点行加36 passed in 0.03s-qq才只剩点行。pyproject.toml里现在只有addopts -m \not broken\没有-q命令行给一个-q不会叠成-qq。0.04s是单次运行的实测值没有做多次取样所以不给区间。五、练习三级梯度做完一级再看下一级。不要在code/里改任何文件——那是全书示例共用的目录动手的实验放到/tmp。E07.1〔照着做〕把写死的名单换成一份文件。先照着配方 1 那个最短版本把写死在命令行里的列表换成一份文件。自己造/tmp/my_names.txt里面至少要有3 行空行其中一行只放空格或制表符、一个重复出现三次的名字、一行末尾带空格、一个中文名字。然后跑cd/home/lijiong/obsidian-vault/assets/Python学习/Python入门/Zero to Python/code./.venv/bin/python u07_warmup.py /tmp/my_names.txt验收先手算「有效行数 / 跳过空行 / 去重人数 / 第一名」再和脚本输出逐个对有效行数 跳过空行必须等于wc -l /tmp/my_names.txt。对不上就说明空行的判据和你以为的不一样——去read_roster()里找原因不要改数字迁就结果。E07.2〔改一改〕把sorted拿掉看哪几条断言会红。在/tmp/order_probe.py里并排跑两种写法不要动仓库里的文件names[Darius,Ruth,Ali,Hannah,Ruth,Ali,Ruth]print(list(set(...)) :,list(set(names)))print(sorted(set(...)) :,sorted(set(names)))连着跑三次把三次输出都留下。然后回到code/跑一次./.venv/bin/python -m pytest tests/test_u07_warmup.py -p no:cacheprovider在tests/test_u07_warmup.py里找到test_unique_names_is_sorted_and_deduplicated和test_tied_counts_keep_first_seen_order说明这两条各自在钉什么契约。验收三次list(set(...))的输出贴在回复里能说出它们为什么不同能指出如果unique_names()退回list(set(...))上面哪一条断言会红、红在哪一行。E07.3〔深〕让--csv那条路在三种极端输入下都说得清。目标--csv在「空文件」「全是同一个人」「名字里带逗号」三种输入下各自给出可解释的行为。做法造三份输入放/tmp逐个跑./.venv/bin/python u07_warmup.py 输入 --csv /tmp/out.csv每次都记四栏退出码、标准输出、标准错误、/tmp/out.csv的行数与内容wc -l加head。验收三组原始输出都要贴上包括「名字里带逗号」那条抛出的ValueError完整消息。再回答两个问题空文件时写出来的 CSV 有几行、为什么它仍然是合法 CSV如果要求「名字带逗号也能写进去」改动会落在哪个函数上、会影响code/tests/test_u07_warmup.py里的哪一条断言六、验收自测先自己答再展开对答案。§一 输出里的30、5、10三个数字分别由哪个函数算出来的「跳过的空行」这一项为什么值得单独报出来而不是直接丢掉unique_names()为什么写成sorted(set(names))而不是教材 ch 5 的list(set(names))给出可复现的证据并说清这一步付了什么代价。喂一个空文件给脚本会发生什么给出退出码、标准输出、标准错误各是什么并说明错误信息为什么要走标准错误而不是标准输出。答案30是RosterStats.entries等于读进来的有效名字个数来源是read_roster()返回的第一个值再在summarize()里取len()5是read_roster()返回的第二个值blank只数被continue跳过的行10是summarize()里的len(counter)——Counter的键本身就是去重后的名字集合不必再len(set(names))数一遍。「跳过空行」值得单独报是因为它是一份输入质量的读数空行占 5/35说明教练的导出格式里有 14% 是噪声这个数字突然变成 20你会在统计结果出错之前先看出输入变了。直接丢掉它你就少了一条能提前看出输入变了的线索。因为set的迭代顺序不保证。三次运行同一个list(set(names))得到三个不同顺序§三 配方 2 有原文输出原因是字符串哈希受PYTHONHASHSEED影响每个新进程重新随机。教材 ch 5 那份名单跑一次就完事所以list(set(names))够用脚本要被反复运行、数字要写进讲义和测试顺序必须可复现。代价是sorted()的 O(n log n)——十个人的名单可以忽略百万行时先想清楚是不是需要顺序。另一条边界set要求元素可哈希。退出码 1标准输出只有三行名单、解释器、以及只有表头的name,count标准错误有两行「名单里一个有效名字都没有跳过空行 0 行—— 没有数据就没法画图」和「下面是只有表头的两列 CSV」。错误信息走标准错误是为了让「数据」和「出事了」这两类文字分开别人把标准输出重定向成文件、接进管道的时候... out.csv错误提示不会混进数据里把文件污染掉。这也是单元 10 之后把脚本挂进别的流程时要用的判据——对方看退出码不看中文。七、附录 · 术语表只收这一篇用到、且还没在前六个单元术语表里出现过的词。术语一句话解释首次出现Big 4Head First Python对list/tuple/dict/set四种内置容器的叫法本篇用「要不要唯一、要不要计数、要不要顺序」三句判据在它们之间选§二 准则 5集合set只存不重复元素的容器迭代顺序不保证转换一次就把重复项去掉§二 准则 5list-set-list 三连教材 ch 5 的叫法set去重、再转回list本篇在中间补一步sorted()让顺序可复现§三 配方 2Countercollections里的计数字典取值默认是 0给它赋一个新键会真的写进去那不是查询§三 配方 3most_common(n)Counter的方法按次数降序取前 n 项次数相同的按首次出现的先后排列§三 配方 4排序稳定性stable sort键值相同的元素保持原有先后sorted是稳定的reverseTrue不会把并列项倒过来§三 配方 4显示宽度East Asian Width一个字符在等宽终端里占几列汉字占 2 列len()数的是字符个数不是列数§三 配方 5文本条形图text bar chart用 f-string 加重复字符拼出的条形图不依赖绘图库只在等宽字体下对齐§三 配方 5两列 CSV可画图数据name,count这种两列表格文本Excel、gnuplot、matplotlib 都认这个形状§三 配方 6utf-8-sig读文件时用的编码名与utf-8行为相同另外顺手丢掉文件开头的 BOM§三 配方 1→ 全书术语总表见术语总表
返回列表