<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>赵喧典 | iamkissg</title><description>文章、笔记、TILs、摘录与酒馆来信：长期写作，公开学习，也保存生活。</description><link>https://lifelonglove.cn/</link><language>zh-CN</language><item><title>摘录自 meng_shao</title><link>https://lifelonglove.cn/quotes/meng-shao-63b989/</link><guid isPermaLink="true">https://lifelonglove.cn/quotes/meng-shao-63b989/</guid><description>对新一代旗舰模型行为倾向的实测笔记，指出多数保守表现源自安全取舍，主张用时序化解自主与风险的冲突：可逆操作先执行、不可逆动作留给审批，同时把上下文文件纳入审计与归因配套。</description><pubDate>Sun, 13 Sep 2026 08:01:20 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;整体对 GPT-6 Astra 的感受 · 五个倾向中至少三个（爱提问、对指令敏感、测试彻底）是「更谨慎」的副作用。默认值偏保守，需要开发者主动放开。 · 「先做完再问」是方法论核心：把自主性与安全性的矛盾转化为时序问题，可逆的先做，不可逆的最后审批。 · 指令遵循越强，上下文文件越成为风险面。审计文件与要求归因是配套措施。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;— meng_shao · &lt;a href=&quot;https://x.com/shao__meng/status/2096386797945360737/?rw_tt_thread=True&quot;&gt;RT @shao__meng: GPT-6 Astra 提示词最佳实践&lt;/a&gt; &lt;span&gt;offset 3951&lt;/span&gt;&lt;/p&gt;</content:encoded><category>摘录</category><category>提示工程</category><category>Agent</category><category>上下文管理</category></item><item><title>摘录自 Kyle</title><link>https://lifelonglove.cn/quotes/kyle-10eb69/</link><guid isPermaLink="true">https://lifelonglove.cn/quotes/kyle-10eb69/</guid><description>Learn how model size and reasoning capabilities affect the number of instructions LLMs can follow reliably.</description><pubDate>Sat, 29 Aug 2026 04:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;Frontier thinking LLMs can follow ~ 150-200 instructions with reasonable consistency. Smaller models can attend to fewer instructions than larger models, and non-thinking models can attend to fewer instructions than thinking models.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;— Kyle · &lt;a href=&quot;https://www.aihero.dev/a-complete-guide-to-agents-md&quot;&gt;A Complete Guide To AGENTS.md&lt;/a&gt;&lt;/p&gt;</content:encoded><category>摘录</category></item><item><title>智能体平台的合理边界与可靠集成</title><link>https://lifelonglove.cn/tils/agent-platform-boundaries-and-reliable-integration/</link><guid isPermaLink="true">https://lifelonglove.cn/tils/agent-platform-boundaries-and-reliable-integration/</guid><description>业务应用应掌控界面、上下文、数据、权限与审批，Harness 负责会话、工具协调、执行、流式事件和恢复，MCP 或业务工具负责受控访问与操作。针对外部副作用，仅靠人工审批不足，还需持久化幂等操作号、外部回执和最终对账状态，避免恢复或重试导致重复执行。</description><pubDate>Wed, 26 Aug 2026 20:00:59 GMT</pubDate><content:encoded>&lt;p&gt;智能体平台的合理边界是：业务应用掌控界面、业务上下文、数据记录、权限与审批；Harness 负责会话状态、上下文延续、工具协调、沙箱执行、流式事件、失败恢复和人工授权；MCP 或业务工具负责访问内部数据与执行受控操作。核心原则不是把业务搬进 AI，而是把 AI 嵌入现有业务系统。
集成可按复杂度分为三种：&lt;code&gt;codex exec&lt;/code&gt; 适合脚本、CI 和一次性后台任务；Codex SDK 适合在应用代码中启动、恢复和流式接收智能体任务；&lt;code&gt;app-server&lt;/code&gt; 适合长期会话、事件处理、任务中断、工具暴露及审批交互。
对于会产生外部副作用的操作，仅有人工审批仍然不够。宿主应用还必须保存幂等操作号、外部系统回执和最终对账状态，以区分“操作尚未执行”“正在执行”和“已执行但响应丢失”，避免会话恢复或重试时重复执行。&lt;/p&gt;
</content:encoded><category>TIL</category><category>智能体平台</category><category>Harness</category><category>MCP</category><category>codex</category><category>系统集成</category><category>幂等性</category><category>人工审批</category><category>可靠性</category></item><item><title>高可靠 Agent Skill 的设计原则</title><link>https://lifelonglove.cn/tils/high-reliability-agent-skill-design/</link><guid isPermaLink="true">https://lifelonglove.cn/tils/high-reliability-agent-skill-design/</guid><description>高可靠的 Agent Skill 应采用“最小必需、按需扩展”的结构，并通过明确的触发条件、可执行流程、反向排除、验证标准和渐进披露提升执行可靠性。脚本应分离标准输出与状态信息、快速失败，同时控制上下文与 Token 成本。</description><pubDate>Wed, 26 Aug 2026 20:00:59 GMT</pubDate><content:encoded>&lt;p&gt;高可靠的 Agent Skill 应采用“最小必需、按需扩展”的结构：仅保留必需的 &lt;code&gt;SKILL.md&lt;/code&gt;，根据实际需要添加 &lt;code&gt;scripts/&lt;/code&gt; 和 &lt;code&gt;references/&lt;/code&gt;，避免为形式完整而创建空目录。&lt;code&gt;description&lt;/code&gt; 是唯一常驻系统提示的部分，必须同时说明技能做什么以及何时触发，但不应包含流程摘要，以免 Agent 跳过正文。
Skill 的正文应围绕可执行行为组织：用具体命令替代笼统要求，用反向排除条件辅助激活判断，用 &lt;code&gt;Common Rationalizations&lt;/code&gt; 预先反驳 Agent 可能跳过步骤的借口，并通过 &lt;code&gt;Red Flags&lt;/code&gt; 和 &lt;code&gt;Verification&lt;/code&gt; 定义可观察的违规信号与基于证据的退出标准。
脚本应将结构化 JSON 输出到 stdout、状态信息输出到 stderr，并使用 &lt;code&gt;set -e&lt;/code&gt; 快速失败。由于执行脚本本身不消耗上下文，只有输出会消耗上下文，因此应优先把重复或复杂逻辑放入脚本，并将 &lt;code&gt;SKILL.md&lt;/code&gt; 控制在约 500 行以内、引用层级限制为一层。核心原则是：流程优于知识、具体优于笼统、证据优于假设、预防合理化、渐进披露和持续关注 Token 成本。&lt;/p&gt;
</content:encoded><category>TIL</category><category>Agent</category><category>Skill设计</category><category>提示工程</category><category>自动化脚本</category><category>可靠性</category><category>上下文管理</category></item><item><title>摘录自 マリウス</title><link>https://lifelonglove.cn/quotes/quote-fb0e20/</link><guid isPermaLink="true">https://lifelonglove.cn/quotes/quote-fb0e20/</guid><description>マリウス借低价跨境电商的兴起提出，数字内容正被大模型以同样的低成本、高产量方式“TEMU 化”，成本外部化最终或催生低端量产与人类精品并存的双层内容市场，对理解 AI 时代内容生态有参考价值。</description><pubDate>Tue, 25 Aug 2026 08:02:45 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;几年前，如果有人告诉我，从地球另一端的仓库发货，十美元的电钻、两美元的连衣裙和一美元的鞋子竟然有市场，我肯定会嗤之以鼻。然而如今，这样的市场不仅存在，而且有了名字，甚至还公开交易。

TEMU 、Shein 和其他一些公司围绕着这样一个理念打造了令人瞠目结舌的商业帝国：只要生产成本足够低、速度足够快，质量勉强够好，能在手机屏幕上看起来没问题，那么即使产品品质较差、即使它的原材料可能有问题，即使一次配送的碳排放量比同等本地购买高出几个数量级，也会有相当一部分人购买。

这种商业模式的关键不在于创新，而在于成本的外部化和压缩。

我觉得，软件、书籍、音乐、电影、以及我们消费的大多数数字商品和服务，都面临着同样的困境。它们都在 TEMU 化。

大模型正在源源不断生成这种成本足够低的内容，由于它们庞大的数量、快速的生成速度、极低的成本，很多用户愿意消费这些内容。

就像实体商品一样，我们最终可能会看到一个双层市场：一个规模庞大、利润丰厚的低端市场充斥着大量粗制滥造的产品，而一个规模较小、价格更高的高端市场则提供人类劳动成果的产品。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;— マリウス · &lt;a href=&quot;http://www.ruanyifeng.com/blog/2026/08/weekly-issue-409.html&quot;&gt;科技爱好者周刊（第 409 期）：程序员的职业未来&lt;/a&gt; &lt;span&gt;offset 5804&lt;/span&gt;&lt;/p&gt;</content:encoded><category>摘录</category><category>经济学</category><category>有感</category><category>人工智能</category></item><item><title>摘录自 张一鸣</title><link>https://lifelonglove.cn/quotes/quote-e2b923/</link><guid isPermaLink="true">https://lifelonglove.cn/quotes/quote-e2b923/</guid><description>知乎问答回应成长之问：唯有做事才能成长，牵头扛事收效最大——成败无处分摊，逼人全力以赴；日日直面人事钱财与节奏的难题，能力遂在实战中沉淀。</description><pubDate>Mon, 24 Aug 2026 16:24:59 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;空想是不可能成长的，人要成长就得做事。

做事也分很多种，有时候是配合做事，有时候是居中协调，有时候是牵头负责。

其中只有**牵头负责最锻炼人**，人的成长最快。

**牵头对成长的好处，首先是强度和态度。**

牵头负责一件事，结果成败、毁誉得失系于自己一身，躲都躲不掉。

所以唯一的选择就是竭尽全力。

**牵头的第二个锻炼，是解决问题的能力。**

牵头负责一件事时，人会发现处处是问题，人是问题，钱是问题，上面是问题，下面是问题，节奏是问题，协调是问题，自己也是问题……出了问题就得解决，每天都在解决问题，有时候得哄着，有时候得吓着，有时候还得瞒着……有时候得抓紧，有时候得放放，有时候彻底没办法……

自己也不是省油的灯，有时候亢奋，有时候失落，有时候机智无比，有时候目瞪口呆，有时候还想散摊子回高老庄……

总之到了最后，结果也许好，也许不好，但这个过程中的人，就如同八卦炉里的孙猴子，固然伤病一身，但本事也是切切实实增长了。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;— 张一鸣 · &lt;a href=&quot;https://www.zhihu.com/question/490344475/answer/1947032530827077508&quot;&gt;人在什么情况下成长最快？&lt;/a&gt; &lt;span&gt;offset 51&lt;/span&gt;&lt;/p&gt;</content:encoded><category>摘录</category><category>职慧</category><category>生活</category></item><item><title>酒馆来信 009</title><link>https://lifelonglove.cn/letters/009/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/009/</guid><description>从《文明之旅》里五十三年十四位皇帝的历史密度，到 Nat Friedman 对小团队与速度的信条，两条线索共同追问有限时间里什么值得更快行动。</description><pubDate>Fri, 14 Aug 2026 06:28:33 GMT</pubDate><content:encoded>&lt;h1 id=&quot;酒馆来信-009&quot;&gt;酒馆来信 009&lt;/h1&gt;
&lt;p&gt;本周在尝试优化个人编辑系统, 减少创作的阻力, 恢复写作, 恢复分享~&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;文明之旅-第一期&quot;&gt;文明之旅 第一期&lt;/h2&gt;
&lt;p&gt;这周看了《文明之旅》第一期。有一个细节，我记了一笔：53年，就有14个皇帝。&lt;/p&gt;
&lt;p&gt;平均不到4年换一位皇帝——这个密度，也许值得停下来想一想。&lt;/p&gt;
&lt;p&gt;真乱啊&lt;/p&gt;
&lt;h2 id=&quot;书摘nat-friedman-的信条&quot;&gt;书摘：Nat Friedman 的信条&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] Nat Friedman 的信条&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Smaller teams are better 小团队更好&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster decisions, fewer meetings, more fun 更快的决策，更少的会议，更多的乐趣&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s important to do things fast 做事要快，这很重要&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You learn more per unit time because you make contact with reality more frequently 你在单位时间内学到的东西更多，因为你更频繁地与现实接触&lt;/li&gt;
&lt;li&gt;Going fast makes you focus on what’s important; there’s no time for bullshit 走得快让你专注于重要的事情；没有时间废话&lt;/li&gt;
&lt;li&gt;「Slow is fake」「慢是假的」&lt;/li&gt;
&lt;li&gt;A week is 2% of the year 一周是一年的2%&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;「Slow is fake」大概是整段信条里最干脆的一句。而「一周是一年的2%」这个换算，让时间的流逝忽然变得具体。&lt;/p&gt;
&lt;p&gt;AI 时代, 一切都变了.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果你喜欢酒馆来信，欢迎转发给更多的朋友。&lt;/p&gt;
&lt;p&gt;祝各位阅读愉快&lt;br&gt;
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)&lt;/p&gt;
</content:encoded><category>酒馆来信 009</category></item><item><title>有启发</title><link>https://lifelonglove.cn/letters/008/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/008/</guid><description>断更一年多后重新来信，回顾成家、换工作与走向三十岁的生活变化，再收集近期真正有启发的阅读和观察，把恢复表达本身当作新的开始。</description><pubDate>Sun, 10 Mar 2024 12:54:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello~&lt;/p&gt;
&lt;p&gt;好久不见, 朋友们! 欢迎收看酒馆来信第 八 期.&lt;/p&gt;
&lt;p&gt;自从 2022 年 8 月 6 日&lt;a href=&quot;/letters/007/&quot;&gt;第七期的酒馆来信&lt;/a&gt;之后, 断更已有 1 年 07 个月 4 天.&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;/letters/002/&quot;&gt;酒馆来信 002&lt;/a&gt;期中, 有一小节叫&lt;a href=&quot;/letters/002/#%E6%95%88%E7%8E%87%E4%B8%8E-%E5%8D%95%E8%BA%AB%E9%BD%90%E9%A3%9E&quot;&gt;⚙️效率与🐕单身齐飞&lt;/a&gt;, 断更证明是真的: 那段时间, 我开始恋爱了 🫣.&lt;/p&gt;
&lt;p&gt;过去的一年半时间, 我度过了 30 虚岁, 成家了, 换了工作, 目前努力立业中~ Fighting 🆙&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;有启发&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;有启发&quot;&gt;有启发&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.dedao.cn/share/course/article?id=Lpy0edZAG5mnK0wlo1XzD9BkoajY4x&amp;amp;trace=eyJzX3BpZCI6IjEwODE0NyIsInNfcHR5cGUiOiI2NSIsInNfdWlkIjozMzI1Njc4NH0%3D&quot;&gt;公元1000年：宋代如何走出五代阴影？&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;文明之旅 视频封面.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/a1d467bf7769-文明之旅 视频封面.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;这是罗老师&amp;lt;文明之旅&amp;gt;的第一期. 从宰相吕端之死说起, 讲到“宋太宗驾崩, 吕端如何阻止太后另立新君”, 讲到五代宋初的皇帝继承问题.&lt;/p&gt;
&lt;p&gt;视频中提到: 五代历53年，有过14位皇帝. &lt;strong&gt;政权和皇位更替太频繁了&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;至于大宋, 太宗赵匡义是太祖赵匡胤的弟弟, 皇位是&lt;strong&gt;兄终弟及&lt;/strong&gt;的. 太宗在位时, 后来的真宗已经被立为太子了. 然而, 当太宗驾崩 (公元 997 年), 在有储君的情况下, 太后欲&lt;strong&gt;废幼立长, 另立新君&lt;/strong&gt;. 这, 就是五代的阴影.&lt;/p&gt;
&lt;p&gt;从后世看, 宋是五代之后的统一王朝, 北宋历 167 年 (公元 960-1127年), 南宋历 152 年 (公元1127-1129 年), 两宋共 319 年. 但是, &lt;strong&gt;从当世的视角看, 997 年, 赵宋才历两任皇帝, 承平才 38 年, 之前是近百年的唐末乱世&lt;/strong&gt;. 凭什么他赵家不会重蹈五代的覆辙, 成为“梁唐晋汉周宋”中的第六代呢?&lt;/p&gt;
&lt;p&gt;所以太宗太后要立更年长的皇子为皇帝, 为了皇权与国家的稳定. 这符合乱世的皇权交接逻辑, 不无道理. 吕端保太子登基, 则是因为太子地位的合法性: 要保证国家的&lt;strong&gt;长期稳定&lt;/strong&gt;, 太子地位不可动摇.&lt;/p&gt;
&lt;p&gt;以上, 比起吕端的“大事不糊涂”, 带给我更多启发的, 反而是太宗太后为什么糊涂了. 从后世的命名看, 五代十国与大宋有着明显的分别; 但是作为当世人, 尤其是亲历者, 情况是完全不一样的, 大宋会是梁唐晋汉周之后的第六代吗?&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;读书摘记&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;读书摘记&quot;&gt;读书摘记&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note Leandro von Werra 从事 BigCodeProject 一年多的经验之谈]
&lt;strong&gt;Small, aligned teams can build great things fast. 小而团结的团队可以快速构建伟大的东西&lt;/strong&gt;。(&lt;a href=&quot;https://twitter.com/lvwerra/status/1764710833626436017&quot;&gt;Leandro von Werra 从事 BigCodeProject 的经验之谈&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note &amp;lt;繁花&amp;gt;]
康总说，名堂不少。梅瑞说，也许，这朋友，全部是乱讲。康总不响。梅瑞说，&lt;em&gt;人讲的故事，往往是表面文章&lt;/em&gt;，懂了吧。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note &amp;lt;营销笔记&amp;gt;]
有一次我去重庆见一个客户，客户问了我一个问题：“为什么重庆的火锅这么好吃，却没有特别火的全国性品牌？”我说，&lt;em&gt;其实只是重庆人觉得重庆火锅好吃&lt;/em&gt;，作为一个山东人，我觉得重庆火锅又油又腻、又麻又辣，我真的没办法接受这种重口味。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a id=&quot;好物推荐&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;好物推荐&quot;&gt;好物推荐&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://metaso.cn/search/8455144233900060672&quot;&gt;秘塔AI搜索&lt;/a&gt;: AI 新时代的搜索引擎, 能够根据用户输入的问题, 检索相关网页, 自动组织内容, 并由 AI 生成答案.
&lt;ul&gt;
&lt;li&gt;比起谷歌百度类搜索引擎, 可直达答案, 省去了人工再通过浏览网页寻找答案的过程; 因为是 AI 自动生成的答案, 需要辨别.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://metaso.cn/s/MV9PRg&quot;&gt;秘塔AI搜索- 为什么说吕端大事不糊涂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://metaso.cn/s/a4BHGF&quot;&gt;秘塔AI搜索- 鸟山明的生平&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;Pasted image 20240310203938.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/4829fde23394-Pasted image 20240310203938.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;秘塔搜索-漫画家鸟山鸣的生平.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/35c1aa912550-秘塔搜索-漫画家鸟山鸣的生平.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果你喜欢酒馆来信，欢迎转发给更多的朋友。&lt;/p&gt;
&lt;p&gt;祝各位阅读愉快&lt;/p&gt;
&lt;p&gt;赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)&lt;/p&gt;
&lt;p&gt;Mar 10, 2024 at 20:54&lt;/p&gt;
</content:encoded><category>酒馆来信 008</category></item><item><title>酒馆来信 007</title><link>https://lifelonglove.cn/letters/007/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/007/</guid><description>借《张艺谋的作业》回看创作者如何观察、积累和完成作品，并延伸到“留下”的意义：分享经得起回看、能让新老读者继续考古的内容。</description><pubDate>Sat, 06 Aug 2022 01:13:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello~，&lt;/p&gt;
&lt;p&gt;周末好呀，朋友们！欢迎收看酒馆来信第 七 期：张艺谋的作业与留下的哲学。&lt;/p&gt;
&lt;p&gt;酒馆来信写得漫无目的，但其实有一个隐性的核心：&lt;strong&gt;我希望分享更恒久的内容&lt;/strong&gt;。一来，看过的朋友能够有所收获，留下点什么（留下的哲学😄）；二来，新老朋友都可以不断往前考古，而往期分享也不负所望。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;张艺谋的作业&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;张艺谋的作业&quot;&gt;张艺谋的作业&lt;/h2&gt;
&lt;p&gt;偶然又看到了这本书&lt;a href=&quot;https://book.douban.com/subject/7064581/&quot;&gt;《张艺谋的作业》&lt;/a&gt;，想推荐给各位。&lt;/p&gt;
&lt;p&gt;想来大家都知道&lt;a href=&quot;https://baike.baidu.com/item/%E5%BC%A0%E8%89%BA%E8%B0%8B/147018&quot;&gt;张艺谋&lt;/a&gt;、喜欢他的作品，但是可能并不了解老谋子其人。&lt;/p&gt;
&lt;p&gt;这本书，让我更了解了老谋子，为他所折服，从此路转粉。它出版于 2012 年，是一本 10 年前的老书了，基于访谈写成的“张艺谋的作业”。老谋子在访谈中表现出的气质，作者都写在文字中了。读此书，我感受到老谋子的那种：&lt;em&gt;&lt;strong&gt;勤奋、刻苦、自律、诚恳、清醒、坦荡&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;assets/836dbc952e12-%E5%BC%A0%E8%89%BA%E8%B0%8B%E7%9A%84%E4%BD%9C%E4%B8%9A_%E8%B5%B5%E5%96%A7%E5%85%B8.pdf&quot;&gt;这张思维导图&lt;/a&gt;是我个人总结的老谋子的哲学与做法。以老谋子为榜样，与君共勉！（文字链接是 PDF 版）&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;张艺谋的作业_赵喧典.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/3472fb18c4d8-张艺谋的作业_赵喧典.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;引用两段很触动我的话，让不方便打开思维导图的朋友也领略一下老谋子的魅力。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!quote] 张艺谋有意识让自己工具化
“我一开始就有这个意识，让自己迅速工具化。”张艺谋说。工具化这个词他反复说了几遍。&lt;em&gt;工具化你就会对别人有用，人有了用，有些东西就不会找到你身上，你就会有空隙生存&lt;/em&gt;。“工具不是个坏词儿，有用也是我们这一代人深入骨髓的价值感。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这段话让我对“工具人”有所改观：从有用的角度来看，何妨让自己工具化？谁又不是呢？&lt;/p&gt;
&lt;p&gt;补充分享：动画短片&lt;a href=&quot;https://www.bilibili.com/video/BV15s411k7Jx/&quot;&gt;雇佣人生 - 哔哩哔哩&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!quote] 梦想是入世的、具体的、现实的
张艺谋说：“人是要有梦想的，不过我也在想，什么是梦想。我总觉得，梦想是很入世、很具体、很现实的，&lt;em&gt;梦想是你在某一个生存阶段，可以做点什么改善自己的状况，梦想是最切实的想法，它不能在天边，因为那无法作用和影响你的行为&lt;/em&gt;。”他从不承认自己有机心。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;留下的哲学&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;留下的哲学&quot;&gt;留下的哲学&lt;/h2&gt;
&lt;p&gt;周四，🚴骑车上班路上，等红绿灯，突然想起“&lt;em&gt;教育是离开学校之后留下的&lt;/em&gt;”这个说法。&lt;/p&gt;
&lt;p&gt;去检索出处，发现原来是爱因斯坦说的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Education is what remains after one has forgotten everything he learned in school. — &lt;strong&gt;Albert Einstein&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;罗老师在他的新书&lt;a href=&quot;https://book.douban.com/subject/35870746/&quot;&gt;《阅读的方法》&lt;/a&gt; 中把对象换成了读书，表达了相似的观点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们读过的书，大部分都会遗忘。但如果我们在书中遇到喜欢的人、尊敬的人、有趣的人，他们做事的方式就会留下来，变成清晰的烙印，深刻地影响着我们。——《阅读的方法》 罗振宇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;让万物穿过自己 20220715225727.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/dc6b4ee976c4-让万物穿过自己 20220715225727.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;我又双叒叕想起了孟岩老师的这一期播客：&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/61ee26c84675a08411f51570&quot;&gt;让万物穿过自己&lt;/a&gt;。在&lt;a href=&quot;/letters/004/#%E5%91%A8%E5%B2%81%E4%BA%BA%E7%94%9F&quot;&gt;酒馆来信第四期&lt;/a&gt;中，我分享了“让万物穿过自己”去经历的一面。&lt;/p&gt;
&lt;p&gt;现在看来，&lt;em&gt;&lt;strong&gt;经历是一方面，留下的经验是另一方面&lt;/strong&gt;&lt;/em&gt;。让我们变得更加丰盈的，归根结底是留下的。&lt;/p&gt;
&lt;p&gt;我还收藏了类似观点的一句话🧐： &lt;em&gt;旅行不是为了看到不同的事物，而是为了学会不同地看待事物 (You don’t travel to see different things, you travel to see things differently.)&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;我更愿意是这样的：&lt;em&gt;&lt;strong&gt;旅行是为了看到不同的事物，也是为了看过不同事物之后，学会不同地看待事物&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;人生像一只空杯，装进去一些又倒掉一下。这反复的过程，就是我们精彩的一生，而这只杯子以及它留下的、承载的就是我们自己。&lt;/p&gt;
&lt;p&gt;让万物穿过自己，让自己更加丰盈。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果你喜欢酒馆来信，欢迎转发给更多的朋友。&lt;/p&gt;
&lt;p&gt;祝各位更加丰盈
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-08-06 09:13&lt;/p&gt;
</content:encoded><category>酒馆来信 007</category></item><item><title>酒馆来信 006</title><link>https://lifelonglove.cn/letters/006/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/006/</guid><description>从《世说新语》里“我与我周旋久，宁作我”谈忠于自身选择，再借遗憾最小化框架思考职业与人生决定：承担代价，也不把判断交给别人。</description><pubDate>Sun, 31 Jul 2022 08:47:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello~，&lt;/p&gt;
&lt;p&gt;朋友们！欢迎收看酒馆来信第 &lt;strong&gt;六&lt;/strong&gt; 期：&lt;em&gt;宁作我，遗恨最小化&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;宁作我&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;宁作我&quot;&gt;宁作我&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;桓公少与殷侯齐名，常有竞心。桓问殷：“卿何如我？”殷云：“我与我周旋久，宁作我。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是出自&lt;a href=&quot;https://baike.baidu.com/item/%E4%B8%96%E8%AF%B4%E6%96%B0%E8%AF%AD%C2%B7%E5%93%81%E8%97%BB/14135561&quot;&gt;《世说新语·品藻》&lt;/a&gt;的一段对话。彼时，桓温（桓公）位极人臣，殷浩（殷侯）正值人生低谷，桓温问殷浩：&lt;em&gt;你和我相比，怎么样&lt;/em&gt;？可以想象，桓温问这话的时候，是多么得志得意满。&lt;/p&gt;
&lt;p&gt;可是，殷浩并没有遂了桓温的意，回答“&lt;em&gt;啊，我不如公多矣&lt;/em&gt;”，而是说“&lt;em&gt;既然我已经与自己周旋这么久了，还是做我自己吧&lt;/em&gt;”。没有谄媚、没有嫉妒、也没有立志、放狠话，很平淡却个性十足。&lt;/p&gt;
&lt;p&gt;如果殷浩回答“我不如公”，这段对话就无法成为一段佳话，后人也不会更高看他殷浩一眼。我们欣赏、羡慕殷浩什么呢？是&lt;em&gt;&lt;strong&gt;做自己的勇气&lt;/strong&gt;&lt;/em&gt;！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;怨恨最小化&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;怨恨最小化&quot;&gt;怨恨最小化&lt;/h2&gt;
&lt;p&gt;受&lt;a href=&quot;http://www.samvitjain.com/blog/regret/&quot;&gt;《Regret Minimization》&lt;/a&gt;一文启发，我写过一篇文章：遗憾最小化。“遗憾最小化”提供了一个决策框架，&lt;em&gt;它将我们发射到未来，让我们以未来的视角来看待现在的选择——以后是否会后悔或者遗憾，从而在现在做出更具有长期价值的决定&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;我想补充一点：&lt;em&gt;&lt;strong&gt;怨恨最小化&lt;/strong&gt;&lt;/em&gt;。出于本心的决定，最差的，也只是自己会遗憾；迫于他人意志的选择，万事胜意、皆大欢喜，事不如意，心怀怨恨。与遗憾相比，怨恨不只伤己、更伤人。&lt;/p&gt;
&lt;p&gt;$遗恨最小化=遗憾最小化+怨恨最小化$&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;相亲-是对父母意志的贯彻&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;相亲是对父母意志的贯彻&quot;&gt;相亲：是对父母意志的贯彻&lt;/h2&gt;
&lt;p&gt;从北京回到杭州，我开始怀念北京的一点好——&lt;em&gt;离家太远，妈不管&lt;/em&gt;。其实，妈妈也想管，只是&lt;em&gt;子在外、母命有所不受&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;我是浙江人，从老家到杭州，高铁只要 2.5 小时。物理距离太近了，以至于我明显地感受到身上承载的父母的意志。最明显的是相亲😳。&lt;/p&gt;
&lt;p&gt;我把相亲当作认识新朋友的渠道，所以我愿意接受我妈给我安排的相亲。（你知道相亲遇到同学是一种怎样的体验吗？我知道😂。）&lt;/p&gt;
&lt;p&gt;一开始，我都是抱着认识新朋友的心态去的。交个朋友，平常心态。&lt;/p&gt;
&lt;p&gt;但是，我没有意识到，相亲是一个特殊的交友渠道。父母是有多期待的！我妈会时不时地问我“聊得怎么样啊”，并总是督促我“男生要主动点”。我的行为开始变形。我开始硬着头皮给对方发微信，鲜有回复、或者尬聊；约对方吃饭，因为各种原因无法实现。有来无回的交往，让我极度难受。&lt;/p&gt;
&lt;p&gt;一件事情的发生，让我意识到：&lt;em&gt;&lt;strong&gt;相亲，更多的是在贯彻父母的意志&lt;/strong&gt;&lt;/em&gt;。如果自己也愿意，那是家和万事兴；如果自己不愿意，要么敷衍、要么矛盾。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我妈又给我介绍了一位对象。妈妈说，对方家长说得很好的，让我试试看。于是，我尝试邀请对方出来吃饭。她回复我说：&lt;em&gt;不用因为你妈妈的压力来约我&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我突然意识到，除了认识新朋友，相亲后来的故事，都是我非预期的。所以，我没了自知之明，我感到难受，我会抱怨“&lt;em&gt;我的脸皮也薄的呀&lt;/em&gt;”！&lt;/p&gt;
&lt;p&gt;本着怨恨最小化，在抱怨升级之前，我决计不再相亲。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;我的一周与推荐&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周与推荐&quot;&gt;我的一周与推荐&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;送了朋友一本《&lt;em&gt;男孩、鼹鼠、狐狸和马&lt;/em&gt;》，她的朋友看了之后很喜欢，也买了一本；&lt;/li&gt;
&lt;li&gt;基于&lt;a href=&quot;https://github.com/arampacha/CLIP-rsicd/&quot;&gt;arampacha/CLIP-rsicd&lt;/a&gt;，微调 OpenAI CLIP，以下是简单的炼丹记录
&lt;ul&gt;
&lt;li&gt;在 Apple Silicon 芯片的 Mac 上安装深度学习环境&lt;/li&gt;
&lt;li&gt;如何使用 transformers 转换模型&lt;/li&gt;
&lt;li&gt;开始学 JAX&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;如果你喜欢酒馆来信，欢迎转发给更多的朋友。&lt;/p&gt;
&lt;p&gt;祝夏凉，
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-07-31 16:47&lt;/p&gt;
</content:encoded><category>酒馆来信 006</category></item><item><title>酒馆来信 005</title><link>https://lifelonglove.cn/letters/005/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/005/</guid><description>围绕一次购买新电脑引发的玩笑，整理“花钱买时间”、重视使用价值和减少无效消费等朴素原则，说明节俭并不等于拒绝为真正重要的事付费。</description><pubDate>Sat, 23 Jul 2022 02:04:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello~，&lt;/p&gt;
&lt;p&gt;周末好呀，朋友们！欢迎收看酒馆来信第 5 期：一场消费观革命，我是吝啬鬼😳。&lt;/p&gt;
&lt;p&gt;上周，我在朋友圈分享了购买新电脑💻的喜悦。同学 XXJ 发来了灵魂拷问：&lt;em&gt;&lt;strong&gt;你给公司卖命，挣得钱还要买继续给公司卖命的装备，香吗&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;一周过去了，我想我能更好地回答这个问题了：香啊，太香了！&lt;/p&gt;
&lt;p&gt;本周还是偏个人向的内容，与各位分享几点朴素实用的消费观。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;花钱买时间&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;花钱买时间&quot;&gt;花钱买时间&lt;/h2&gt;
&lt;p&gt;正如我在&lt;a href=&quot;/letters/004/#zm10n6&quot;&gt;上一封酒馆来信&lt;/a&gt;中提到的，在新设备的加持下，&lt;em&gt;&lt;strong&gt;我的办公效率提高了&lt;/strong&gt;&lt;/em&gt;，体现在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我可以在 0.9 的时间内完成 1.0 的工作，&lt;strong&gt;时间盈余了&lt;/strong&gt;！&lt;/li&gt;
&lt;li&gt;也可以在 1.0 的时间内完成 1.1 的工作，&lt;strong&gt;可以做更多事情了&lt;/strong&gt;！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里的工作是指能够在这台机器💻上完成的所有。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Trade money for time, not time for money. You’re going to run out of time first.
——Naval Ravikant&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我记得一个说法：&lt;em&gt;&lt;strong&gt;花钱买时间是最幸福的&lt;/strong&gt;&lt;/em&gt;。确实如此。&lt;/p&gt;
&lt;p&gt;一个典型的场景是：住的离公司越近，房租越贵。我在北京的时候，步行时间减少 20 分钟，月租平均上涨 300-500 元/月。&lt;/p&gt;
&lt;p&gt;我没有很好地践行这一点，我愿意为更短的通勤时间买单，却忘了更新设备、提高办公效率以节省时间。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;我的屏幕时间 2022-07-23 06.24.56.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/eee938cc284d-我的屏幕时间 2022-07-23 06.24.56.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;职业原因与个人兴趣爱好原因，我每天有大量的时间在计算机前。日均 8 小时，工作日超过 10 小时。&lt;/p&gt;
&lt;p&gt;得益于新笔记本💻的高性能，预计每天能够省出 30 分钟，折算成金钱💰，$300\times20\times1.5=450$，接近月供的一半。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;花钱在高频-超长的使用场景上&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;花钱在高频超长的使用场景上&quot;&gt;花钱在高频、超长的使用场景上&lt;/h2&gt;
&lt;p&gt;现在，我不必等程序编译、不能等网页加载，不必为了打开新应用而关闭旧应用…一切都丝般顺滑。没有了时间焦虑⌛，办公体验得到了极大的提升⬆️。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!important] 斌哥的消费观——高频、超长的使用场景，要舍得花钱
我总是舍不得给自己花钱，这台 MBP 让我犹豫了小半年。在斌哥看来，简直荒谬。他以前劝我买机械键盘⌨️与妙控板🖱，是这样说的：&lt;em&gt;&lt;strong&gt;你每天摸键盘的时间，比摸女朋友手的时间都长&lt;/strong&gt;&lt;/em&gt;。然后，我都买了，用得很开心。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/features/copilot&quot;&gt;GitHub Copilot&lt;/a&gt; 开始收费了，100刀/年（675人民币/年）。我又跑去问斌哥。他说：&lt;em&gt;&lt;strong&gt;超值&lt;/strong&gt;&lt;/em&gt;。我理解斌哥的意思，他和陈皓老师是一样的看法，&lt;em&gt;程序员，省下写常规性代码的时间，去尝试更牛逼的算法、去做更有意思的项目&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;陈皓老师对 github copilot 的看法 2022-07-23 07.27.32.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/87a7a42a9af2-陈皓老师对 github copilot 的看法 2022-07-23 07.27.32.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;花钱在有用的-突破边界的场景上&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;花钱在有用的突破边界的场景上&quot;&gt;花钱在有用的、突破边界的场景上&lt;/h2&gt;
&lt;p&gt;现在，我可以开两个浏览器——&lt;a href=&quot;https://brave.com&quot;&gt;Brave&lt;/a&gt; 用于工作、&lt;a href=&quot;https://www.microsoft.com/en-us/edge?r=1&quot;&gt;Edge&lt;/a&gt; 用于个人用途，可以打开多个 IDE，可以一直开着&lt;a href=&quot;https://www.zotero.org/&quot;&gt;Zotero&lt;/a&gt; 随时保存论文，更重要的是，&lt;strong&gt;我可以在本地炼丹了&lt;/strong&gt;😄！&lt;/p&gt;
&lt;p&gt;当我意识到这一点，我才发现之前是多么地&lt;strong&gt;自囚于樊笼&lt;/strong&gt;。因为旧机器性能的缘故，除了常规性的开发，我把使用需求限制在了一个很小的范围内——机器的性能边界。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!important] 我妈的消费观——有用的，当花则花，不要心疼钱
当我和我妈说，我买了一台一万九的电脑的时候，她的回复让我很感动，大意是：&lt;em&gt;&lt;strong&gt;这钱，当花则花&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我的初中同学 ZYH 凭信息奥赛一等奖保送浙大，后来一路开挂，除了他个人的天赋与努力，中学时，他父母有能力、也舍得给他买3万的电脑，是一个很重要的起点。&lt;em&gt;雪球就这样滚起来了&lt;/em&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我妈说，她为当时无法给我提供相似的条件而感到遗憾，否则我也会更好的。我说，那时候，我是个网瘾少年，我只会用来打游戏的。我妈又说，&lt;em&gt;99% 的时间用来打游戏，1% 的时间用来学习，这 1% 的价值就是难以估量的&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;当幸福来敲门，无所限制 20220723095123.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/dfddd3792ef9-当幸福来敲门，无所限制 20220723095123.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在，我无所束缚了。海阔凭鱼跃，天高任鸟飞。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;我的一周与推荐&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周与推荐&quot;&gt;我的一周与推荐&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;上周日，我和朋友去看了电影&lt;a href=&quot;https://movie.douban.com/subject/35131346/&quot;&gt;《隐入尘烟》&lt;/a&gt;，是我喜欢的带点纪事性质的、萦绕着一股淡淡忧伤的、发人深省难以忘怀的电影，⭐️⭐️⭐️⭐️⭐️推荐&lt;/li&gt;
&lt;li&gt;史老师的这一期播客，我在朋友圈分享过，一位朋友听了觉得有帮助：&lt;a href=&quot;https://steveshuo.com/293&quot;&gt;独白：人生意义长什么样？为什么那么难找？ - Steve说&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;美团的这篇文章，我看了1/3，是一篇深度好文，建议算法同学收藏：&lt;a href=&quot;https://tech.meituan.com/2022/07/06/LargeScaleDeepLearningModel-EngineeringPractice-in-MTWaimaiad.html&quot;&gt;外卖广告大规模深度学习模型工程实践 - 美团技术团队&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;分享了一则小诗：问人也问心&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一些事
问别人
只是希望他们说出
自己心里的答案&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果你喜欢酒馆来信，欢迎转发给更多的朋友。&lt;/p&gt;
&lt;p&gt;祝夏安，
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-07-23 10:04&lt;/p&gt;
</content:encoded><category>酒馆来信 005</category></item><item><title>酒馆来信 004</title><link>https://lifelonglove.cn/letters/004/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/004/</guid><description>在忙碌到来不及提前规划的一周，借“让万物穿过自己”回看年龄、时间与生活感受；写作成为让经验停驻、重新看见自己的方式。</description><pubDate>Sat, 16 Jul 2022 02:07:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello，&lt;/p&gt;
&lt;p&gt;欢迎收看酒馆来信第四期：&lt;em&gt;&lt;strong&gt;让万物穿过自己，让时间停驻&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;这是我第一次没有在周四、周五提前整理想要与各位分享的，因为太忙了，忙到——两周半前约朋友吃饭，当他有事情改约的时候，我有一种庆幸😩&lt;/p&gt;
&lt;p&gt;所以，先祝各位周末愉快啦！&lt;/p&gt;
&lt;p&gt;本期，是一场没有计划的写作，受启发于孟岩老师的一期播客名称：&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/61ee26c84675a08411f51570?s=eyJ1IjoiNjA3NzZjMjFlMGY1ZTcyM2JiY2I1MjM0In0%3D&quot;&gt;&lt;strong&gt;让万物穿过自己&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;三十与十五六&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;三十与十五六&quot;&gt;三十与十五六&lt;/h2&gt;
&lt;p&gt;我在虚岁与周岁一文中分享了一个有趣的区分法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;虚岁&lt;/strong&gt;，记录的是生下来经历过的年数，它将人嵌入到了时间、历法之中；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;周岁&lt;/strong&gt;，则是把时间往人身上套，它是人经历过完整的一整年之后的累计的周年数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是两套完全不同的计数逻辑。按照虚岁计，明年我就三十了；按照周岁计，现在我才27！&lt;/p&gt;
&lt;p&gt;我下班的时间，恰好是附近一所中学晚自习下课的时间。许多学生骑自行车🚴回家，我也是。当我骑着车🚴，混迹在他们中间。晚风吹他们也吹过我，让我有点恍惚：&lt;em&gt;我与他们没有区别，也是十五六岁，时间并没有穿过我&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;周岁人生&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;周岁人生&quot;&gt;周岁人生&lt;/h2&gt;
&lt;p&gt;近年来，兴起了一股“&lt;strong&gt;从自己出发&lt;/strong&gt;”的世界观，比如项飙老师的这本书《把自己作为方法》、孟岩老师这期播客：&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/61ee26c84675a08411f51570?s=eyJ1IjoiNjA3NzZjMjFlMGY1ZTcyM2JiY2I1MjM0In0%3D&quot;&gt;&lt;strong&gt;让万物穿过自己&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;在播客的尾声，孟岩老师讲到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们每个人可能都不知道会发生什么。但是我愿意放手，我也愿意纵身一跃，我愿意去冒险，我愿意去相信我自己的身体，然后相信自己的心，然后我愿意去在场，我愿意去体验，我愿意去经历，我愿意去把自己变得更加的丰盈、专注、放松。最后就是允许万事万物穿过自己，然后穿过我内心的那些毁掉不断重建的那些房子，最终让他们都穿过自己。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;孟岩老师用了很多“我愿意”，他告诉我们：要&lt;em&gt;去经历、去体验、去置身事内、去获得&lt;a href=&quot;/letters/001/#%E4%B8%80%E6%9C%9F%E6%92%AD%E5%AE%A2-%E4%B8%80%E4%B8%AA%E6%99%AE%E9%80%9A%E4%BA%BA%E5%8F%AF%E4%BB%A5%E6%8B%A5%E6%9C%89%E6%80%8E%E6%A0%B7%E7%9A%84%E7%94%9F%E6%B4%BB-%E8%9E%BA%E4%B8%9D%E5%9C%A8%E6%8B%A7%E7%B4%A7&quot;&gt;身临其境的实感&lt;/a&gt;&lt;/em&gt;。这里的主体都是，我、 我、我。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;时间是万物的尺度&lt;/strong&gt;&lt;/em&gt;。从时间出发，它穿过我，无可阻挡地把我从27送往28。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;赵喧典的 27 28 2022-07-16 at 10.11.38.gif&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/8d726ef004a7-CleanShot 2022-07-16 at 10.11.38 1.gif&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;从我出发，&lt;em&gt;&lt;strong&gt;我是我所经历的万事万物的尺度&lt;/strong&gt;&lt;/em&gt;。我选择&lt;em&gt;让万物穿过自己，让时间停驻&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;对此，千年前，苏子有过更加精彩的议论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;逝者如斯，而未尝往也；盈虚者如彼，而卒莫消长也。盖将自其变者而观之，则天地曾不能以一瞬；自其不变者而观之，则物与我皆无尽也。——《前赤壁赋》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;自其变者，持“时间是万物的尺度”观点；自其不变者，则是“我是我所经历的万事万物的尺度”的另一种表达。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;前赤壁赋帖 20220716083858.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/0beb3c5ce9c2-前赤壁赋帖 20220716083858.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;物（时间）与我的两套世界观，恰如虚岁与周岁的逻辑。许多年后，我的墓碑🪦上写着“赵喧典，1994-YYYY”，但是这段时间真能够定义我平凡的一生吗？&lt;strong&gt;不能够啊&lt;/strong&gt;！相比之下，我每周与你分享的这一期期酒馆来信、我写下的这些文字，更能够表达我的一生。像&lt;a href=&quot;/letters/003/#%E5%86%99%E4%BD%9C&quot;&gt;上一期“诗酒趁年华”&lt;/a&gt;中，年轻作家说的，写作要趁早，因为这也是我今生今世的证据之一。&lt;/p&gt;
&lt;p&gt;与&lt;em&gt;&lt;strong&gt;虚岁人生&lt;/strong&gt;&lt;/em&gt;——人嵌入到时间之中，我更愿意去过一场&lt;em&gt;&lt;strong&gt;周岁人生&lt;/strong&gt;&lt;/em&gt;——让时间上身、让我所经历的万事万物定义我的一生。&lt;/p&gt;
&lt;p&gt;人生，有两个进度条。我们无可避免地同时在过虚岁人生与周岁人生，我们能做的就是尽量去提高周岁人生占虚岁人生的比例。&lt;/p&gt;
&lt;p&gt;我想到一个很好的比喻：&lt;em&gt;&lt;strong&gt;人生热力图&lt;/strong&gt;&lt;/em&gt;！人生本是一场灰白，我们每天的一个行动，都是在那一格灰白的格子上增添一抹亮色。我好喜欢这里 contribution 的定义：原语境（Github Heatmap）下，每一次 commit（提交）都是对项目的一次贡献；对人生，每一个行动，都是对我们自己人生的一次贡献——用行动创造人生。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;iamkissg github heatmap 2021-2022 2022-07-16 09.26.18.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/b2d217d20019-iamkissg github heatmap 2021-2022 2022-07-16 09.26.18.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;我的一周与推荐&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周与推荐&quot;&gt;我的一周与推荐&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;过去的一周很有压力，一直在忙着工作，从醒来到睡去&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个习惯不好，但是我还没找到解&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分享了 leader 的建议&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我原计划昨天（周五）去跑一次步，但是午休醒来，身体倍感不适，有一种强烈的恐惧——要是坚持跑步，可能就要上新闻了😂&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因此，对于“我们更加需要的是休息，而不是运动”有了直观的体会&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;买了一台 M1 Pro Macbook Pro，这封酒馆来信就是在新本本上开心地完成的
&lt;a id=&quot;zm10n6&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我想着，有了它以后，提高了办公效率之后，我要把更多的时间留给生活&lt;/li&gt;
&lt;li&gt;我也意识到，这可能是一个对自己美丽的谎言——我将用它来更愉快地干更多的活&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后分享一段很喜欢的话：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;我曾经问过自己一个问题：最羡慕谁的人生？最好的回答当然是“既然我已经和自己周旋很久了，那就还是做我自己吧”——贾行家&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;祝各位阅读愉快
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-07-16 10:07&lt;/p&gt;
</content:encoded><category>酒馆来信 004</category></item><item><title>酒馆来信 003</title><link>https://lifelonglove.cn/letters/003/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/003/</guid><description>受两期写作播客启发，讨论阅读如何输入世界、写作如何整理自己，以及文字为何仍是自由而强大的工具；也分享了值得继续使用的阅读产品。</description><pubDate>Sat, 09 Jul 2022 00:35:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello~，&lt;/p&gt;
&lt;p&gt;周末好呀，朋友们！欢迎收看酒馆来信第三期。&lt;/p&gt;
&lt;p&gt;相信许多朋友都喜欢这一句：&lt;em&gt;且将新火试新茶，诗酒趁年华&lt;/em&gt;。它出自苏子的《望江南》：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;春未老，风细柳斜斜。试上超然台上看，半壕春水一城花。烟雨暗千家。
寒食后，酒醒却咨嗟。休对故人思故国，&lt;em&gt;&lt;strong&gt;且将新火试新茶。诗酒趁年华&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本周的分享受启发于新闻实验室的两期播客“文字还是最自由最强大的工具” 和“写作是与自己、与世界的对话”，想要与各位聊一聊我了解到的读书写作观。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;读书&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;读书&quot;&gt;读书&lt;/h2&gt;
&lt;p&gt;在“文字还是最自由最强大的工具”中，方可成老师推荐了一个阅读 APP &lt;a href=&quot;https://apps.apple.com/us/app/matter-reader/id1501592184&quot;&gt;Matter Reader&lt;/a&gt;。我多次在多个渠道看到多位大 V 推荐过这个 APP，没有尝试过的朋友赶紧试试吧。&lt;/p&gt;
&lt;p&gt;Matter 不只推荐热点新闻，还会有编辑筛选和知名作家推荐的深度旧闻。&lt;/p&gt;
&lt;p&gt;方可成老师讲得特别好：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在社交媒体的年代里面，我们早就习惯不断地刷新，要看最新的内容，如果在时间线上刷不出来新的内容，我们就会感到焦虑，感到无聊。但是近来我已经越来越多地感受到了一种相反的趋势，那就是当人们对不断刷新的夺人眼球的内容感到厌倦，越来越多的人会发现不如回去读读旧文章，这些旧文章甚至都不需要是什么经典了。
……
很多人已经在反思互联网内容和时间之间的关系。比如说&lt;a href=&quot;https://pmthinking.com/&quot;&gt;产品沉思录&lt;/a&gt;，去年有一篇叫做人们都愿意跃入溪流，而建造花园的人将赢得未来。这篇文章就对比了两种不同的内容生产和展示的逻辑。一种逻辑是&lt;em&gt;像溪流一样不断地向前，但是留不下什么东西&lt;/em&gt;。另外一种逻辑就是&lt;em&gt;像花园一样不断地去修建和灌溉&lt;/em&gt;。溪流是社交媒体的时间线逻辑。而数字花园则是另外一种完全不同逻辑。他不追求海量，不追求时效，但是追求质量，追求培育和反思。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我找到了&lt;a href=&quot;/letters/002/&quot;&gt;上一期的酒馆来信&lt;/a&gt;介绍的塔老师的读书原则原话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在选择读什么书时，我以&lt;strong&gt;林迪效应&lt;/strong&gt;为指导：已经流传10年的书将再流传10年；流传了2000年的书籍还将流传更多时间，以此类推。——&lt;a href=&quot;https://d.dedao.cn/DL6zpTLzlnLPsUfM&quot;&gt;《反脆弱》&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a id=&quot;9w50uw&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E6%9E%97%E8%BF%AA%E6%95%88%E5%BA%94&quot;&gt;林迪效应（Lindy Effect）&lt;/a&gt;是这样说的：&lt;em&gt;对于一些不会自然消亡的东西，比如一种技术、一个想法，它们的预期寿命和它们目前已经存在的时间成正比&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;以上带给我的三点启发是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建造花园，而不是随波逐流，让分享能够穿越时间；&lt;/li&gt;
&lt;li&gt;多读经典，多读原文；&lt;/li&gt;
&lt;li&gt;改掉即时阅读的习惯，稍后阅读，也稍后不读。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;写作&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;写作&quot;&gt;写作&lt;/h2&gt;
&lt;p&gt;在“写作是与自己、与世界的对话”的节目尾声（1小时13分），听到一段很动人的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我听到那个作者，非常年轻的作家，一个小说家，非常年轻，27 岁。然后他写了一本新的小说。然后他在讲自己的写这个小说的历程的时候，有一个很有趣的说法。他说，他觉得自己30岁的时候会写的小说，或者会怎么处理这个小说题材，会跟27岁的、现在的自己已经肯定会很不一样，他甚至已经感觉到自己的很多观念跟态度开始转变了。&lt;em&gt;他觉得长大了的自己可能会更加就不会用现在的自己的方式去思考了。所以他的结论是，不是我不写了，而是我要赶快把今天此时此刻的27岁的自己，在想的这个方式以小说的方式呈现出来&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;听完的那一刻，我立马就确定了本期的分享主题：&lt;em&gt;&lt;strong&gt;诗酒趁年华&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;中学时，我们学过一篇文章：&lt;a href=&quot;https://baike.baidu.com/item/%E4%BB%8A%E7%94%9F%E4%BB%8A%E4%B8%96%E7%9A%84%E8%AF%81%E6%8D%AE/6426683&quot;&gt;今生今世的证据&lt;/a&gt;。文中，今生今世的证据是物理的故园。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://baike.baidu.com/item/%E6%9F%B3%E6%AF%94%E6%AD%87%E5%A4%AB/3389273#2&quot;&gt;柳比歇夫&lt;/a&gt;创造了时间统计法。他从20多岁开始，为每天所花的时间记账，从未间断！随便打开某一天的时间账本，他都能够复盘那一天。这里，今生今世的证据是记录的时间。&lt;/p&gt;
&lt;p&gt;写作，记录了我们的思考与情绪。这也是今生今世的证据。像前面那位作家讲的，我们要赶紧把此时此刻的自己记录下来：二十多岁的理想主义与中二气质，三十岁的我且等着。&lt;/p&gt;
&lt;p&gt;写作这个词可能有点大，让人望而生畏。我觉得英文的“write”会更加友好一些：&lt;em&gt;写就完事儿了&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://critter.blog/2020/10/02/write-5x-more-but-write-5x-less/&quot;&gt;Write 5x more but write 5x less&lt;/a&gt;，如这篇文章的题目所建议的：&lt;em&gt;&lt;strong&gt;写五倍多，但五倍少&lt;/strong&gt;&lt;/em&gt;。五倍多，是要写得更加频繁；五倍少，是要减少内容的长度。（这里是我的译文）&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;我的一周与推荐&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周与推荐&quot;&gt;我的一周与推荐&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在忙于学习炼丹，这是我看过的炼丹笔记
&lt;ol&gt;
&lt;li&gt;1250-Transformer 在美团搜索排序中的实践 - 美团技术团队@annote&lt;/li&gt;
&lt;li&gt;1251-腾讯钱丁丁：Yoo 视频底层页推荐系统从 0 到 1 的实践@annote&lt;/li&gt;
&lt;li&gt;1252-Tensorflow 技术概览——从建模到部署@annote&lt;/li&gt;
&lt;li&gt;1254-神器 CLIP：连接文本和图像，打造可迁移的视觉模型@annote&lt;/li&gt;
&lt;li&gt;1255-如何评价 OpenAI 最新的工作 CLIP@annote&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;这是过去一周 arxiv 上推荐系统相关的论文：&lt;a href=&quot;https://arxiv.org/search/advanced?advanced=&amp;amp;terms-0-operator=AND&amp;amp;terms-0-term=%E2%80%9Crecommender+system%E2%80%9D&amp;amp;terms-0-field=abstract&amp;amp;terms-1-operator=AND&amp;amp;terms-1-term=cs.IR&amp;amp;terms-1-field=all&amp;amp;classification-physics_archives=all&amp;amp;classification-include_cross_list=include&amp;amp;date-year=&amp;amp;date-filter_by=date_range&amp;amp;date-from_date=2022-07-02&amp;amp;date-to_date=2022-07-08&amp;amp;date-date_type=submitted_date_first&amp;amp;abstracts=show&amp;amp;size=50&amp;amp;order=&quot;&gt;Advanced Search | arXiv e-print repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;最后，推荐一个有趣又好看的&lt;a href=&quot;https://www.cikeee.com/&quot;&gt;此刻 电影日历-每天一部优秀电影&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;祝各位阅读愉快
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-07-09 08:35&lt;/p&gt;
</content:encoded><category>酒馆来信 003</category></item><item><title>酒馆来信 002</title><link>https://lifelonglove.cn/letters/002/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/002/</guid><description>从效率博主与单身时间的玩笑出发，串起一周里的工具、阅读和生活观察；既关注怎样更有效率，也提醒效率最终应服务于想过的日子。</description><pubDate>Sat, 02 Jul 2022 01:02:00 GMT</pubDate><content:encoded>&lt;p&gt;哈喽，&lt;/p&gt;
&lt;p&gt;见信好呀，各位！这是酒馆来信的第二期。&lt;/p&gt;
&lt;p&gt;本期，我们聊一聊：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了更好地阅读体验，欢迎来&lt;a href=&quot;/letters/002/&quot;&gt;小酒馆分站阅读&lt;/a&gt;~&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;效率与-单身齐飞&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;️效率与单身齐飞&quot;&gt;⚙️效率与🐕单身齐飞&lt;/h2&gt;
&lt;p&gt;周内，和一位朋友聊天。他问：&lt;em&gt;小赵，你平时是怎么学习的&lt;/em&gt;？我说：&lt;em&gt;没啥，就是单身，时间比较多&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;关于这一点，范冰老师做过更加精辟的总结：&lt;em&gt;效率博主不会告诉你，他们保持效率的终极诀窍是：至今仍然单身&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;效率的终极秘诀是单身 20220630082514.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/09c98fc1f6f2-效率的终极秘诀是单身 20220630082514.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;那么，学习有没有方法呢？答案绝对当然是肯定的，👇是我正在尝试的公开学习。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;公开学习与-个人学习&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;公开学习与个人学习&quot;&gt;🌞公开学习与🌛个人学习&lt;/h2&gt;
&lt;p&gt;公开学习（Learning in Public，或译“当众学习”）这个概念，我最早在这篇文章中了解到，作者罗列了几点公开学习的好处。而他对公开学习的了解来源以及更系统地介绍这一套理论的，应该是这一系列文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.swyx.io/learn-in-public/&quot;&gt;Learn In Public&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.swyx.io/learning-gears&quot;&gt;Learning Gears&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.swyx.io/puwtpd&quot;&gt;Pick Up What They Put Down&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.swyx.io/learn-in-private&quot;&gt;How To Learn In Private&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通读下来，第一篇文章反而最&lt;strong&gt;务虚&lt;/strong&gt;，没啥嚼头（在后面的文章里，作者也介绍了为什么要这样写：太具体、太务实，就不能因人制宜，而学习可能是很私人定制的事情）。&lt;/p&gt;
&lt;p&gt;总结一下，公开学习的概念很像&lt;strong&gt;以教为学&lt;/strong&gt;，但是它并不强调教，而是公开。公开的意义在于：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;化他律为自律&lt;/strong&gt;，以及监督对学习过程、结果的塑造；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续获得反馈&lt;/strong&gt;，以及反馈带来的反思与成长。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;写到此处，我意识到，就这两点而言，公开学习比以教为学更好。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以教为学，在个人学习完成之后再进行教学，完成一次学教；公开学习，公开“学习的过程”，“边学边教”，完成多轮学教；&lt;/li&gt;
&lt;li&gt;因为过程的公开，公开学习能够获得比以教为学更加高频、及时的反馈。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与公开学习相对的，是个人学习。对比第四篇文章介绍的 Learning in Private（我把它译作个人学习），个人学习与公开学习像是学习这件事的一体两面：个人化的，强调输入；公开化的，强调输出。&lt;em&gt;&lt;strong&gt;输入强化输出，输出反哺输入，构成一个完美的闭环&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://www.swyx.io/learn-in-private&quot;&gt;How To Learn In Private&lt;/a&gt;一文中，作者介绍了一些具体的方法论，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;个人胜于内容&lt;/strong&gt;：当你遇到高度认可的内容，关注它背后的创作者（比如我，赵喧典）。互联网时代，内容会快速过时，或者达到某些商业化目的之后迅速变质，而个人的耐久性更好，能够持续地生产好的内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!quote] 补充阅读
《反脆弱》、《黑天鹅》等书的作者&lt;a href=&quot;https://twitter.com/nntaleb&quot;&gt;纳西姆·塔勒布（塔老师）&lt;/a&gt;和纳老师都强调了阅读经典的重要性。塔老师说，经典之所谓经典，因为&lt;em&gt;时间越长，它们的生命力也越长&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;了解情境、关注细节&lt;/strong&gt;：原文如下，大意是&lt;em&gt;初学者需要快速入门，需要简明的教程；专家只需要跟进前沿，对知识体系修修补补、添砖加瓦；而学道中途，需要情境，需要对事物细节的理解。&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.swyx.io/learn-in-private#:~:text=Think%20about%20it%3A%20Beginners%20need%20tutorials%20as%20they%20give%20the%20shortest%20path%20to%20success%2C%20Experts%20just%20need%20to%20tinker%20and%20keep%20up%20to%20date%2C%20but%20Intermediates%20need%20context%20and%20clear%20explanation%20of%20details.&quot;&gt;This is especially relevant if you are Intermediate at whatever you’re doing. Think about it: Beginners need tutorials as they give the shortest path to success, Experts just need to tinker and keep up to date, but Intermediates need context and clear explanation of details.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!quote] 补充阅读
陈丹青老师对于一流、二流、三流的说法如下。结合此处的观点，“经由二流去一流的路”断了，可能是因为对情景、细节的忽略。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们遵从一流，忽略二流，最终恰好是困居三流，因为，经由二流去一流的路，断了。 ——陈丹青，&lt;a href=&quot;https://www.dedao.cn/ebook/detail?id=EJmMZXq1b8qOpBlD69XAdP7LEGaKJWEnDn3xRnme5vrVzo4QMZYgNyk2jNA5467K&quot;&gt;《局部》&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上四篇文章，最推荐关于个人学习的 &lt;a href=&quot;https://www.swyx.io/learn-in-private&quot;&gt;How To Learn In Private&lt;/a&gt;，或者你可以先看看我的阅读理解。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;今晚月色真美&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;今晚月色真美&quot;&gt;🎑今晚月色真美&lt;/h2&gt;
&lt;p&gt;周三，在&lt;a href=&quot;https://www.dedao.cn/course/article?id=aYB83z6N9dqxVyP3xjK7ZMvy0GQDO5&quot;&gt;《蔡钰·商业参考》&lt;/a&gt;听到日本人不会直接说“我爱你”，而是说成“&lt;em&gt;&lt;strong&gt;今晚月色真美&lt;/strong&gt;&lt;/em&gt;”，会心一笑（记下了😄）。&lt;/p&gt;
&lt;p&gt;传说，夏目漱石还是英语老师的时候，曾问学生，“I love you”该如何翻译。有学生翻译为直白的“愛しています”（我爱你）。夏目漱石说，日本人是不会把“我爱你”挂在嘴边的。日本人会说“月が綺麗ですね”（月色真美）。&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;网上还流传着来自《源氏物语》的这段话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;今晚的月亮真美，也曾和你如今日一般看过月亮，每每想起你我昔日共渡的悲欢时光，就会莫名为光阴的流逝而落泪感伤，从今而后，和你的一切，都会成为慰藉我一生的美好回忆，在我剩下的岁月里，我都会怀念今晚和你一起看过的月亮。&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我在《源氏物语》书中并没有检索到 ，却想起了&lt;a href=&quot;https://www.bilibili.com/bangumi/play/ss2546&quot;&gt;《言叶之庭》&lt;/a&gt;里的这两段应和：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;隐约雷鸣，阴霾天空，但盼风雨来，能留你在此。&lt;/p&gt;
&lt;p&gt;隐约雷鸣，阴霾天空，即使天无雨，我亦留此地。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当我和朋友说“单身，时间比较多”的时候，也盼风雨来。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;近者悦-远者来&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;近者悦远者来&quot;&gt;近者悦♥，远者来👋&lt;/h2&gt;
&lt;p&gt;本周听了两期孟岩老师的播客。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/62b804912af904c4f0031586?s=eyJ1IjoiNjA3NzZjMjFlMGY1ZTcyM2JiY2I1MjM0In0%3D&quot;&gt;E14 孟岩对话爱哲：我们都有自己的故事 - 无人知晓&lt;/a&gt;中，听到孟岩老师提到往期播客。“&lt;em&gt;让更多人知道你，还是让爱你的人更爱你&lt;/em&gt;”，这个问题深深打动了我，于是又去听了&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/61dbf5b519e8465da2f8f4fd?s=eyJ1IjoiNjA3NzZjMjFlMGY1ZTcyM2JiY2I1MjM0In0%3D&quot;&gt;E09 孟岩对话黄海：让更多人知道你，还是让爱你的人更爱你 - 无人知晓&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!question] 让更多人知道你，还是让爱你的人更爱你？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我看来，这是一个选择的问题，更是一个先后的问题。&lt;/p&gt;
&lt;p&gt;《论语》说&lt;em&gt;&lt;strong&gt;近者悦，远者来&lt;/strong&gt;&lt;/em&gt;，是对这个问题的完美回答。&lt;/p&gt;
&lt;p&gt;我最早在&lt;a href=&quot;https://www.dedao.cn/course/detail?id=z1DWOMARavZVxoMs3LKP2mlx7bjydL&quot;&gt;跟华杉学儒家思想&lt;/a&gt;中听到“悦近来远”。华杉老师如是说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;只在近处下功夫，只投入在近处的人身上&lt;/em&gt;，远方的人，让他自己来。&lt;strong&gt;悦近，是我的事；来远，来不来是他的事&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;要想实现近悦远来，你还需要拥有一种品质，就是&lt;strong&gt;不贪心&lt;/strong&gt;。儒家还有一个词叫“&lt;strong&gt;不外慕&lt;/strong&gt;”，&lt;em&gt;只珍惜自己已有的，绝不羡慕外面的&lt;/em&gt;。不管他多好，跟我没关系，他来了才跟我有关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，先后顺序应该是：&lt;em&gt;&lt;strong&gt;让爱你的人更爱你，然后更多的人会知道你&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;两位老师持同样的观点。他们的对话更生动、更具体，并列举了许多实例 ，推荐收听🎧&lt;/p&gt;
&lt;p&gt;&lt;a id=&quot;我的一周&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周&quot;&gt;我的一周&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将之前分享的论文笔记迁移到了小酒馆 0103 - 丹方，原来有接近 100 篇呢，读书时候的自己真高产呐😁&lt;/li&gt;
&lt;li&gt;和祝大哥（凯文老师）约好一起参加今年的杭马，开始了体能训练&lt;/li&gt;
&lt;li&gt;订阅了 #Λ-Reading 推荐的一个 #日历 &lt;em&gt;One Question A Day（每天一个问题）&lt;/em&gt;：&lt;a href=&quot;webcal://addevent.com/feed/euaiehiw.ics&quot;&gt;webcal://addevent.com/feed/euaiehiw.ics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天的问题是：What upsets you about other people’s actions — and what does that say about you? （是什么让你对别人的行为感到不满ーー这说明了你的什么问题？（由&lt;a href=&quot;https://fanyi.caiyunapp.com/#/&quot;&gt;彩云小译&lt;/a&gt;翻译））&lt;/p&gt;
&lt;p&gt;如果你觉得本期内容还不错，欢迎订阅、关注。&lt;/p&gt;
&lt;p&gt;与子同 Yue，关于我，也关于你。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;祝各位阅读愉快、周末愉快
赵喧典 于 🏮 观照酒馆(notes.lifelonglove.cn)
2022-07-02 09:02&lt;/p&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;今晚月色真美是什么意思？ - 小鸡词典 &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/142294219&quot;&gt;日语中“今晚的月色真美”是什么梗 - 知乎&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>酒馆来信 002</category></item><item><title>酒馆来信 001</title><link>https://lifelonglove.cn/letters/001/</link><guid isPermaLink="true">https://lifelonglove.cn/letters/001/</guid><description>第一封来信说明这份 newsletter 想轻松分享一周见闻与启发，并从《纳瓦尔宝典》和一期播客谈财富、幸福、选择与值得长期保存的观点。</description><pubDate>Sat, 25 Jun 2022 00:29:00 GMT</pubDate><content:encoded>&lt;p&gt;哈喽，&lt;/p&gt;
&lt;p&gt;我是赵喧典，欢迎收看第一期的酒馆来信。&lt;/p&gt;
&lt;p&gt;这份 newsletter 没有固定的主题，我对她的定位是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分享我一周的见闻与启发，希望对你也有所帮助；&lt;/li&gt;
&lt;li&gt;我可以轻松地写，你也可以愉快地读。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本期，主要分享一本书与一期播客。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;，而我推荐你阅读英文版&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;一本书-纳瓦尔宝典-财富与幸福指南&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;一本书纳瓦尔宝典财富与幸福指南&quot;&gt;一本书：《纳瓦尔宝典：财富与幸福指南》&lt;/h2&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;纳瓦尔宝典 封面 20220624212152.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/e9f794fbd90e-纳瓦尔宝典 封面 20220624212152.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;
这本书的中文版刚出版两个月，是最近顶热门的图书。&lt;/p&gt;
&lt;p&gt;如书名所示，这本书记录了 Naval Ravikant（我习惯成为“纳老师”）的智慧。但是，它并不是纳老师本人写的，而是由纳老师的信徒对他的言论收集而成。说到弟子对老师言语的记录，各位是否立马想到了《论语》呢？ 没错，本书就是以同样的形式出版成册的。&lt;/p&gt;
&lt;p&gt;让我们先来领略一下纳老师关于智慧的智慧吧：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My definition of wisdom is knowing the long-term consequences of your actions. Wisdom applied to external problems is judgment. They’re highly linked; knowing the long-term consequences of your actions and then making the right decision to capitalize on that.&lt;/p&gt;
&lt;p&gt;我对智慧的定义是&lt;em&gt;&lt;strong&gt;知道你的行动的长期后果&lt;/strong&gt;&lt;/em&gt;。应用于外部问题的智慧就是判断力。它们（智慧与判断力）是高度相关的；知道你的行动的长期后果，然后利用它来做出正确的决策。（我自己翻译的，非中文版原文） #智慧&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a id=&quot;yfgrpw&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;看到这段话，我有一种开悟的感觉。&lt;/p&gt;
&lt;p&gt;智慧，它是不可名状的。就像老子说：道可道，非常道。关于智慧，如果说我们有哪些共识的话，其中一点会是：长者拥有更多的智慧。&lt;/p&gt;
&lt;p&gt;为什么长者更加智慧？纳老师的定义做出了回答：因为他们活得足够久，小辈面对的决策，他们都面对过，他们知道后果 。反过来看，恰恰是长者用他们的生命长度定义了智慧。&lt;/p&gt;
&lt;p&gt;高中的时候，我哥哥（当时他是数学博士，现在他是教授、博导）教我数学，他一再强调定义的重要性——&lt;em&gt;只有知道了定义和性质，我们才能利用好它&lt;/em&gt;。这一点同样适用于此处的智慧：在纳老师下定义之前，智慧是模糊的，培养智慧更加无从谈起；在纳老师下定义之后，智慧一下清晰起来，&lt;em&gt;&lt;strong&gt;通过练习预判、通过用更长远的眼光来看待事物，智慧是可以锻炼的&lt;/strong&gt;&lt;/em&gt;！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;+补充阅读&lt;/strong&gt;：&lt;a href=&quot;https://www.dedao.cn/share/course/article?id=g258WANERjwQJDzpmeKbOMG1rZqkPl&quot;&gt;启发俱乐部第 57 期：什么是“躬身入局”&lt;/a&gt;中，罗老师讲到他对智慧的理解：人生智慧是，能够从多重身份、多个视角看待问题。&lt;/p&gt;
&lt;p&gt;你可以看到，我读的是英文版，这是因为在中文版出版之前，我了解到这本书，就迫不及待地翻阅了。后来，因为沉默成本，也就一直读英文版。我也向你推荐英文版，有以下几点原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;英文本电子书可以免费下载 （&lt;a href=&quot;https://www.navalmanack.com/&quot;&gt;Almanack of Naval Ravikant&lt;/a&gt;），先下载了翻一翻，不会有买书不看的压力&lt;/li&gt;
&lt;li&gt;因为是纳老师言论的合集，包括他的推文，用词大多是常见的单词，直接读英文也不会有压力
&lt;ul&gt;
&lt;li&gt;推荐下载 epub 格式的电子书，再传输到微信读书，遇到生僻词可以在微信读书内方便查询&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;直接阅读英文，不会因为翻译而破坏纳老师的原意&lt;/li&gt;
&lt;li&gt;因为中英文思维与语言的差异，许多道理用英文再讲一遍，起到中英文相互映照、注释的作用，有一种新鲜感与恍然感
&lt;ul&gt;
&lt;li&gt;比如，当纳老师劝我们珍惜时间，他说：&lt;em&gt;Trade money for time, not time for money. You’re going to run out of time first&lt;/em&gt;. “寸金难买寸光阴”啊，因为常常是钱没花完，时间没了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;再次推荐《纳瓦尔宝典》，并且推荐英文原版的 &lt;a href=&quot;https://www.navalmanack.com/&quot;&gt;Almanack of Naval Ravikant&lt;/a&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;一期播客-一个普通人可以拥有怎样的生活-螺丝在拧紧&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;一期播客一个普通人可以拥有怎样的生活--螺丝在拧紧&quot;&gt;一期播客：一个普通人可以拥有怎样的生活 | 螺丝在拧紧&lt;/h2&gt;
&lt;p&gt;周内，看到 happy 对于十年后生活的预测。他说，我们&lt;strong&gt;真正需要的是更多的阳光、汗水、阅读，更好的饮食和睡眠&lt;/strong&gt;，而不是屏幕、沙发、印象、视频等。&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;quot;alt&amp;quot;:&amp;quot;Happy 对未来生活的预判 20220623224300.png&amp;quot;,&amp;quot;src&amp;quot;:&amp;quot;assets/f396682f0a00-Happy 对未来生活的预判 20220623224300.png&amp;quot;,&amp;quot;index&amp;quot;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;这让我想起了更早之前听过这期播客：&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/628e5d666c7eec4d0113c98b?s=eyJ1IjoiNjA3NzZjMjFlMGY1ZTcyM2JiY2I1MjM0In0%3D&quot;&gt;Vol.34 对话王梆：一个普通人可以拥有怎样的生活 - 螺丝在拧紧&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;标题中，我剔除了两个字，因为这期节目真正令我念念不忘和深深向往的，是王梆女士描绘的生活而不是其他。&lt;/p&gt;
&lt;p&gt;在节目中，她提到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我觉得关键还是能够跟人，就是&lt;strong&gt;物理性的相处&lt;/strong&gt;，就是&lt;strong&gt;面对面的相处&lt;/strong&gt;。我觉得这个很重要，因为我们的生活实在是太虚幻了，我们生活在云间，就是什么都是云上的。&lt;/p&gt;
&lt;p&gt;像我们今天这样的。你记得当时你来我们这个小村，就是我们一起去散步的时候，当时已经很晚了，然后我们走在，就是空无一人的街道的小巷上，然后就是朝这个教堂走去。这种体验跟你现在坐在这个你自己的书房，然后我坐在我的书房，我们两个对着一个麦克风在说话，这个体验是很不一样的，是很不一样的。&lt;/p&gt;
&lt;p&gt;因为你会闻到那个一个村庄的味道，这个气味是从它的这个田野里传来的，你还能听到牛的这个叫声，然后这些都是非常具体的，是很 physical 的。然后你去教堂里面，你会听到他的钟声，他的这个钟声也是很 physical 的，是能够直接在你的听觉上起到一种反应的，就是通过你的听觉，然后传感到你的整个这个身体，一下就会给你一种非常幽静的感受。这个幽静的感受是你坐在家里面打开录音机，然后听一段这个教堂的录音是不一样的。&lt;strong&gt;就是身临其境，跟人交往的时候，这种身临其境的感觉是很美好的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然后民间机构它最大的一个功能，它其实就是这种身临其境的一个感觉，它给了你一个这样的空间，它让你跟人物理性地聚集在一起。然后比如说我刚才说到的那个 day center，你去到那个地方，进入到厨房里面，这种烟火蒸腾，这种这个刚刚烘焙出来的蛋糕的香气，然后其他的这个志愿者聊的天气的话题，聊的自己家里面的家长里短，这种感受，你是在电脑面前没有办法体验到的，你是绝对体验不到的。你必须走到这个地方，在厨房里面，一边洗碗一边跟你身边的人说话，然后说的都是什么，我们家那个媳妇，她最近这段时间在看一个什么病，然后那个医生怎么建议。然后这种家长里短的话题，这些话题你在“中会”里面你是不会谈的。比如说我们坐在这里的话，我们都不会具体地去聊这种琐碎的话题，&lt;strong&gt;就是这种琐碎就是这种家长里短这种物理之间的这种接触，physical 的这样的一种接触，它会让你整个人身心愉悦。我从来都觉得这种愉悦感是我们生存在这个灰暗世界的唯一的一个动力&lt;/strong&gt;。（这段描述开始于0时57分；文字由飞书妙记转录）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;抱歉，引用了这么一大段文字。不过，王梆女士描述的&lt;em&gt;&lt;strong&gt;人与人之间物理性的接触、身临其境的感觉&lt;/strong&gt;&lt;/em&gt;，难道不是很美好吗？当我再次去听这期播客，还是会被它所打动。&lt;/p&gt;
&lt;p&gt;这一点，我深有体会。三个月之前，我结束了一年零八个月的北飘，回到了杭州。&lt;/p&gt;
&lt;p&gt;我只身一人来到北京，除了同事和一位因为疫情总是被关在大学校园里的同学，几乎没有朋友。因此，工作之余，我也不愿意出门，总是宅着，而且是宅在公司里面，为了周末食堂提供的一顿午饭。我这完全属于“只有工作，没有生活”的一条腿走路。当工作顺利的时候，工作上的成就感能够弥补生活的缺失；当工作不顺利的时候，生活的缺失是一场灾难。2021年，一整年的时间，我都感觉自己像一条孤魂野鬼柜👻，在北京飘荡着。想找个人倾诉一下都很难，以致于我最常念叨的一句词是“&lt;em&gt;千里孤坟，无处话凄凉&lt;/em&gt;”。北京很大，对于当时的我而言，却是一方囚笼。（以上，都是自找的）&lt;/p&gt;
&lt;p&gt;回到杭州之后，我找了一个生活气息特别浓重的老小区，小区门口就是菜市场。每天早上出门上班，看到涌动的买菜的人群、讨价还价的大爷大妈们，听到“XXX便宜卖”的吆喝声，特别亲切。周末，我会买菜、做饭。水果店混杂的果香🍎，蔬菜店新鲜时蔬🥦带着一些泥土的气息，水产店活蹦乱跳的大虾🦐，身临其境的亲切感加倍。此外，我会尽量每个周末约一位在杭的朋友吃饭。线下的、面对面的交流，侃一侃生活的琐事，亲切感三倍了。&lt;/p&gt;
&lt;p&gt;这是我现在的感受：&lt;em&gt;&lt;strong&gt;要有生活&lt;/strong&gt;&lt;/em&gt;。你可以选择宅着，只要这是你喜欢的方式；也可以选择走出去，去更多的地方（不用太远，家门之外就行），去见更多的人，去发现生活的美好。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a id=&quot;我的一周&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;我的一周&quot;&gt;我的一周&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;发布了🏮 观照酒馆，迁移了部分之前的文章到小酒馆。希望能够持续地开张&lt;/li&gt;
&lt;li&gt;在准备新的 newsletter，就是现在的酒馆来信，同样希望可以坚持下去&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你觉得本期内容还不错，欢迎订阅、关注。&lt;/p&gt;
&lt;p&gt;与子同 Yue，关于我，也关于你。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;抓住六月的尾巴，祝周末愉快&lt;/p&gt;
&lt;p&gt;赵喧典&lt;/p&gt;
&lt;p&gt;观照酒馆：notes.lifelonglove.cn&lt;/p&gt;
&lt;p&gt;2022-06-25 08:29&lt;/p&gt;
</content:encoded><category>酒馆来信 001</category></item><item><title>存量阅读+增量阅读，助你在学术的海洋上乘风破浪</title><link>https://lifelonglove.cn/articles/do_not_be_afraid_of_the_overwhelming_papers/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/do_not_be_afraid_of_the_overwhelming_papers/</guid><description>面对持续增长的学术论文，可先用综述和经典文献建立领域存量，再借助订阅、筛选和固定节奏处理增量，让阅读服务于研究问题而不是堆积收藏。</description><pubDate>Sun, 21 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;这是一个信息爆炸的时代，连学术论文也概莫能外。当代研究生如何才能不被泛滥的论文所淹没，以至于在学术研究的海洋上乘风破浪？本文将介绍一套简单实用的&lt;strong&gt;存量阅读+增量阅读&lt;/strong&gt;的阅读方法论。&lt;/p&gt;
&lt;h2 id=&quot;学海无涯&quot;&gt;学海无涯&lt;/h2&gt;
&lt;p&gt;开始之前，先来看一组数据。&lt;/p&gt;
&lt;p&gt;这是一个公认的事实，这是一个信息爆炸的时代。信息每天都在增长，并且增长得越来越快。但是你知道，学术论文的数量，每月/每年以怎样的速度在增长吗？&lt;/p&gt;
&lt;p&gt;微软学术的统计&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;显示，自 2013 年以来，所有学科合起来，年新增出版物（包括图书、期刊/会议论文、专利等）每年都超过了一千万。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-21-do_not_be_afraid_of_the_overwhelming_papers/ms_academic_new_publications_per_year.png&quot; alt=&quot;Microsoft Academic New Publications per Year&quot;&gt;&lt;/p&gt;
&lt;p&gt;arXiv，一个著名的论文预印本发布平台，它的统计显示：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;2019 年的年新增论文数已经超过了 15 万，具体为 155,866 篇，月均 12,989 篇（左图&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;）；&lt;/li&gt;
&lt;li&gt;2020 年 4 月，月新增论文数已经超过了 15,000 篇，日均 500 篇（右图&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-21-do_not_be_afraid_of_the_overwhelming_papers/arxiv_new_submissions_year_month.jpg&quot; alt=&quot;arXiv New Submissions per Year&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果所有学科的出版物与多学科论文的激增并没有让你产生被淹没的感觉，那么你可以留意下自己所在领域的顶级期刊或者会议的投稿量与录用论文数的逐年变化。&lt;/p&gt;
&lt;p&gt;事实上，让我产生“论文读不完了”感觉的正是，近年来，人工智能领域的顶会论文几年翻一番的投稿量与录用论文数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;许多会议 2019 年的投稿量相对于 2018 年都出现了激增，相对于 2017 年都翻了一倍有余（左图&lt;sup&gt;&lt;a href=&quot;#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;）；&lt;/li&gt;
&lt;li&gt;无论录用率是保持不变还是下调了，随着投稿量的激增，会议录用的论文数同样在激增。对于一些会议，录用论文数也是两年翻一倍（右图）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-21-do_not_be_afraid_of_the_overwhelming_papers/14-19_top_conferences_received_accepted.jpg&quot; alt=&quot;2014-2019 Top Conferences Received and Accepted Papers&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我不清楚其他学科或者计算机的其他领域是什么情况。但是 AI 是一个重会议轻期刊的领域，相对而言的轻和重。它近年来得以快速发展，其中一个原因就是会议论文的短平快，极大地促进了学术交流。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从所有学科的出版物、到多学科的论文、再到具体领域的会议论文，无一不在证明，在这个科学技术是第一生产力的时代，学术论文与研究报告的激增是大势所趋，是全面的、加速的。&lt;/p&gt;
&lt;h2 id=&quot;读作舟&quot;&gt;读作舟&lt;/h2&gt;
&lt;p&gt;面对研究性工作的知识爆炸现状，我们如何通过阅读获得有价值的内容，构建自己的知识体系呢？&lt;/p&gt;
&lt;p&gt;我来抛砖引玉。以下是我践行过的一些方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;看教科书/专业书籍；&lt;/li&gt;
&lt;li&gt;看论文综述；&lt;/li&gt;
&lt;li&gt;看经典论文；&lt;/li&gt;
&lt;li&gt;订阅经典论文的施引论文；&lt;/li&gt;
&lt;li&gt;订阅学术大牛的论文；&lt;/li&gt;
&lt;li&gt;关注大牛的动态；&lt;/li&gt;
&lt;li&gt;关注顶级会议或者期刊的动态。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;稍微扯远一点讲下，就我个人以及身边的研究生同学的实际情况来看，我国的研究生教育存在这样的问题（如果只是个别现象，那就太好了）：研一的课安排得很满，但多是于研究无益的专业课，感觉像在读大五。真正培养学生的基础研究能力的课程，要么变成了选修课，要么安排得学生和老师都不乐意上。这样的课，我上过两门。一门其他学院开设的《英文科技论文写作与投稿》的选修课，结果整片校区就 3 个人选了，老师不开课了，我又太想上了，于是每周乘 3 小时的车去另一个校区上 1.5 小时的课；一门本学院开设的《科技论文写作》的必修课，第一位授课教师全程在抱怨和吐槽，导致同学们再也不正视这门课了。&lt;/p&gt;
&lt;p&gt;也许有同学认为培养学生的研究能力是导师的责任。我同意这一点，如果能遇上好导师，你的研究生活一定会很开心，养成的习惯甚至会让你终身受益（这一点，我很幸运，感谢我的导师）。但是来了，导师的能力各不相同，有些导师的个人能力超强但不一定擅长指导学生，有些导师自己就是混子，等等。基于此，我认为培养研究生基础的研究能力的重担应该落在校方或者院方身上，导师则是来进一步拔高你的。&lt;/p&gt;
&lt;p&gt;我的导师给了我做研究的自由。他的原意是这样的：&lt;strong&gt;做科研是枯燥的，做自己不喜欢的科研是令人绝望的枯燥&lt;/strong&gt;。愿你能做自己喜欢的研究~&lt;/p&gt;
&lt;p&gt;自由的结果是，我最终选择了和导师相去较远的研究方向；而代价是，导师不能授我以鱼了，我无法从他那里获得具体看什么的建议。这三年跌跌撞撞，看过不少论文，一开始是瞎看什么都看，慢慢地有了明确的阅读目标，知道要缺啥补啥；读到精彩处会拍案叫绝，看到水文也会嫌弃得要死，慢慢地有了自己的品位。&lt;/p&gt;
&lt;p&gt;总而言之，这篇文章的目标是，希望我个人的这一套阅读方法论（参考了许多人的建议，在此对 Ta 们表示感谢）能够帮助在研究的路上同样缺少指引的同学少走弯路，把更多的时间投入到更有价值的研究上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;存量阅读&quot;&gt;存量阅读&lt;/h3&gt;
&lt;p&gt;我不知道你是否把前面三步都做好了，是的话最好，还没有的话，有时间最好看一看这些内容。&lt;/p&gt;
&lt;p&gt;基本上，从事任何研究，你都可以从前面三步开始，它们几乎是研究入门的必由之路。现在，让我们来看看，为什么你一定要先从教科书看起，再看论文综述和经典论文。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;凡战者，以正合，以奇（jī，多出来的意思）胜。——《孙子兵法・兵势篇》&lt;/p&gt;
&lt;p&gt;先出合战为正，后出为奇。——曹操&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;学术研究，99% 是在做 N+1 的工作，在于一个“奇”字。但是，就入门而言，没有什么比打好基础更重要的了，也就是要先有 N，即曹公所谓的“先出合战为正”。&lt;/p&gt;
&lt;p&gt;在所有资料中，教科书最&lt;strong&gt;全面&lt;/strong&gt;也最&lt;strong&gt;系统&lt;/strong&gt;地梳理了一个领域的知识，在各个领域中都是被主流认可的“正统”。日后的阅读，都是在此时打下的基础上做知识的更新。因此，面对多达几百页的书籍，你要耐下心来啃。磨刀不误砍柴工。啃完了教科书，你对所在的领域会有高屋建瓴的理解，之后的阅读与理解会更加事半功倍。&lt;/p&gt;
&lt;p&gt;第二步你要看一些近期的论文综述。与教科书相比，论文综述的内容更加具体，与你希望从事的具体研究也更加相关。阅读论文综述，弥补了书本知识的滞后性，方便你快速地跟进研究前沿。&lt;/p&gt;
&lt;p&gt;在这之后，你要看所在领域以及相关领域中的经典论文，甚至要反复地看。经典论文的价值自不必说，经过时间的大浪淘沙，留下的都是金子。它们或解决了研究领域中的关键问题，或破而后立，引发了范式转移，总之都是日后诸多研究的基石。&lt;/p&gt;
&lt;p&gt;通过教科书和综述，你很可能已经了解了经典论文的思想或者方法。不过我还是建议你至少看一遍这些论文，原因有二：&lt;/p&gt;
&lt;p&gt;一方面，就像历史研究一样，你只有深入到当时的时代背景，知道那时的关键问题是什么、局限有哪些、资源又有哪些，才能更深刻地理解作者的思考逻辑，把握论文的精髓，明白经典之所以被奉为经典的根本原因。相信我，这样的训练对你今后的研究会很有帮助。&lt;/p&gt;
&lt;p&gt;另一方面，经典论文通常写得极好，无论是研究方法的呈现，还是比较工作与结果的分析，都让人赏心悦目。多看经典，有助于培养对论文的品位和写作能力。&lt;/p&gt;
&lt;p&gt;看过了经典论文，如果感觉还有欠缺，建议补充阅读一些与你的研究相关的最新的论文。&lt;/p&gt;
&lt;p&gt;再补充一点，对于大部头的教科书，你可以看中文教材，这样看起来更顺畅，更容易坚持把它啃完。对于论文，如果你所在的研究领域是用英文交流的、国内外顶尖的研究都是以英文发表的，那么请尽量阅读英文文献。一来内容的时效性更强，二来同样有助于培养学术品位。&lt;/p&gt;
&lt;p&gt;完成了前面三步，大体上就完成了&lt;strong&gt;存量知识&lt;/strong&gt;的储备，也就是有了 N，你可以开始做一些 +1 的尝试了。&lt;/p&gt;
&lt;h3 id=&quot;增量阅读&quot;&gt;增量阅读&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-21-do_not_be_afraid_of_the_overwhelming_papers/papers_blowing_in_the_wind.jpg&quot; alt=&quot;Papers blowing in the wind&quot;&gt;&lt;/p&gt;
&lt;p&gt;你开始做研究了。然而论文们不会因此停止产生，它还会源源不断、源源不断地产生，直到把你淹没。&lt;/p&gt;
&lt;p&gt;为了对抗论文的泛滥，你必须对新的论文进行“过滤”。为了区别于上述做存量知识储备所做的阅读，将此时的阅读称为&lt;strong&gt;增量阅读&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我所列举的后面四步正是为增量阅读准备的。&lt;/p&gt;
&lt;p&gt;学术研究是在前人工作的基础上进行改进，自然要引用前人的工作。而&lt;em&gt;天下论文共一石（dàn，等于十斗），经典论文独得八斗&lt;/em&gt;，对经典论文的引用通常是免不了的。因此，订阅经典论文的施引论文，通常就能够跟进你所在领域的最新研究了。&lt;/p&gt;
&lt;p&gt;当读到与你当前的想法或者所做的研究很相似的论文，一个好习惯是，再订阅它的施引论文。如此，你就不会错过这一条线上的研究，还能避免重复他人的工作。&lt;/p&gt;
&lt;p&gt;除了施引论文，我建议你再订阅大牛的新作。青年学者通常会在一个领域内持续地发光发热，订阅 Ta 们新出的论文，你能够持续地跟进 Ta 们的研究。而且，从 Ta 们的一系列论文中，你没准能够发现一条主线脉络，见贤思齐，你可以考虑将自己的研究往这个方向靠。&lt;/p&gt;
&lt;p&gt;对于成名已久的泰山北斗，订阅一个人很可能就是订阅一个机构。有利有弊，利在你获得了一大群人的智慧，弊在研究不那么聚焦，与你的研究无关的论文会变多。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我推荐一个提供论文订阅的平台——Semantic Scholar&lt;sup&gt;&lt;a href=&quot;#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;。它首先是一个论文搜索引擎，不过提供了很方便的论文订阅功能，同时支持施引论文和新作。就订阅功能而言，比谷歌学术更好用。更重要的是，国内可直接访问。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;除了订阅大牛的新作，我还建议你在社交媒体上关注 Ta 们。这一方法实际上是吴恩达老师&lt;sup&gt;&lt;a href=&quot;#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;推荐的。&lt;/p&gt;
&lt;p&gt;在互联网时代，我们主要有两种获取信息的渠道：搜索和推荐。关注大牛，你将获得 Ta 们替你筛选、推荐的有价值的内容。当然，你还能实时地获悉 Ta 们的研究近况。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;国外的社交媒体首推推特，这也是吴恩达老师推荐的。就 AI 而言，推特上有大量活跃的学术界、工业界的大牛以及机构，比如吴恩达老师本人、谷歌的 Jeff Dean、OpenAI、DeepMind、斯坦福大学的以人为本 AI 研究院 HAI 等等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;增量阅读的第四步是关注顶级会议和期刊的动态。如果你想更全面地了解最前沿的研究，这应该是最好的方式。&lt;/p&gt;
&lt;p&gt;顶会和顶刊收录的论文代表了最新的被学术界认可的研究成果。&lt;/p&gt;
&lt;p&gt;诚然，arXiv 等预印本发布平台的流行，加速了思想的交流。但是任何人都可以在其上发布论文，加之缺少同行评议，许多预印本论文的质量是无法保证的。除非是大牛的新作或者有大牛的背书，又或者你有足够的眼力，否则，你应该尽可能少看预印本论文。因为捡到沙子的概率比金子大得多得多。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于从事 AI 研究的同学，我再推荐一个网站 AI Conference Deadlines&lt;sup&gt;&lt;a href=&quot;#user-content-fn-7&quot; id=&quot;user-content-fnref-7&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;。站如其名，它汇总了许多人工智能领域顶会的举办日期，并且以倒数计时地方式显示了投稿截止时间。此外，可以订阅日程，再也不用担心错过投稿时间了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;本文介绍了一套易于执行、人人都能上手的阅读方法。清晰起见，我按照新知产生的时间，将阅读分成了存量阅读和增量阅读。存量阅读主要负责打好基础、储备知识，增量阅读则对知识进行补充和更新。不过要知道，存量知识最初都是增量知识，增量知识最终都会转化为存量知识，两者相互交织，构建起你的知识体系。&lt;/p&gt;
&lt;p&gt;我写这篇文章的初衷是：希望在研究生毕业之前，送学弟学妹以及未来的学弟学妹们一份礼物。思来想去，也许这个最合适。如果你觉得这篇文章还不错，希望你能转发给你的同学和学弟学妹们。谢谢~&lt;/p&gt;
&lt;h2 id=&quot;参考资料&quot;&gt;参考资料&lt;/h2&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://academic.microsoft.com/publications&quot;&gt;https://academic.microsoft.com/publications&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/help/stats/2019_by_area/index&quot;&gt;https://arxiv.org/help/stats/2019_by_area/index&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/stats/monthly_submissions&quot;&gt;https://arxiv.org/stats/monthly_submissions&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;数据整理自 &lt;a href=&quot;https://github.com/lixin4ever/Conference-Acceptance-Rate%E3%80%82%E6%B2%A1%E6%9C%89%E4%BD%BF%E7%94%A8%E5%BE%AE%E8%BD%AF%E5%AD%A6%E6%9C%AF%E7%9A%84%E5%8E%9F%E5%9B%A0%E6%98%AF%EF%BC%8C%E5%B0%B1%E6%88%91%E5%85%B3%E6%B3%A8%E7%9A%84%E4%B8%80%E4%B8%AA%E4%BC%9A%E8%AE%AE%EF%BC%88EMNLP%EF%BC%89%E8%80%8C%E8%A8%80%EF%BC%8C%E5%AE%83%E6%95%B4%E7%90%86%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%9C%A8%E9%94%99%E6%BC%8F%E3%80%82&quot;&gt;https://github.com/lixin4ever/Conference-Acceptance-Rate。没有使用微软学术的原因是，就我关注的一个会议（EMNLP）而言，它整理的数据存在错漏。&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.semanticscholar.org/&quot;&gt;https://www.semanticscholar.org/&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://baike.baidu.com/item/%25E5%2590%25B4%25E6%2581%25A9%25E8%25BE%25BE&quot;&gt;https://baike.baidu.com/item/%25E5%2590%25B4%25E6%2581%25A9%25E8%25BE%25BE&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-7&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://aideadlin.es/&quot;&gt;https://aideadlin.es/&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-7&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>文章</category><category>阅读</category><category>学术研究</category></item><item><title>【Papernotes】Applying Deep Learning to Airbnb Search</title><link>https://lifelonglove.cn/notes/papernotes_applying_deep_learning_to_airbnb_search/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/papernotes_applying_deep_learning_to_airbnb_search/</guid><description>记录 Airbnb 将深度学习引入搜索排序时的数据、特征、模型迭代与线上经验。</description><pubDate>Sun, 07 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;论文地址: &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3292500.3330658&quot;&gt;https://dl.acm.org/doi/10.1145/3292500.3330658&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文是 Airbnb 被 KDD 2019 录用的论文，非常详细地记录了他们将深度学习引入搜索排名（Search Ranking）的探索过程，写得相当好，字字珠玑，干货满满。&lt;/p&gt;
&lt;p&gt;在拿了 KDD 2018 的最佳论文之后，这一次，他们想必是冲着 KDD 2019 的最佳论文来的。只是可惜了。&lt;/p&gt;
&lt;p&gt;好玉不雕。在这篇笔记中，我将按照原文的顺序，尽可能原原本本地记录本文的重要内容。（笔记挺长的，4800+字，建议收藏阅读。这也证明了论文确实是好论文，真・字字珠玑。）&lt;/p&gt;
&lt;p&gt;首先需要注意的是，本文的讨论仅限于&lt;strong&gt;搜索排名模型&lt;/strong&gt;。这个模型是 Airbnb 整个模型生态中的一环，其作用是根据房客（Guest）预订的可能性，对房源（listing，参考官方的翻译）进行排序。&lt;/p&gt;
&lt;h2 id=&quot;模型的演化&quot;&gt;模型的演化&lt;/h2&gt;
&lt;p&gt;在尝试深度模型之前，他们使用 GBDT（Gradient Boosted Decision Tree）做搜索排名。&lt;/p&gt;
&lt;p&gt;按照他们的说法，将模型迁移到深度模型并非一蹴而就的，而是一系列迭代优化的最终结果。截止 18 年 6 月，主要有四个阶段性的成果，模型性能的提升如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_performance_gains.png&quot; alt=&quot;Airbnb Performance Gains&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;simple-nn&quot;&gt;Simple NN&lt;/h3&gt;
&lt;p&gt;Andrej Karpathy（特斯拉的AI主管）曾经发表过关于深度模型结构的看法：Don’t be a hero。不要（妄想）成为一个英雄。大意是劝人不要一开始就上复杂的模型。&lt;/p&gt;
&lt;p&gt;不听老人言，吃亏在眼前。Airbnb 的众人不听劝，上来就搞了一个相当复杂的模型，结果被其复杂性所压垮，最后不了了之。&lt;/p&gt;
&lt;p&gt;不再妄想成为一个英雄，Airbnb 的深度学习之旅才正式拉开了序幕。&lt;/p&gt;
&lt;p&gt;他们回过头来从一个简单的单隐层神经网络入手。这个 NN 只有 32 个神经元，直接使用了原来 GBDT 的输入特征作为输入。&lt;/p&gt;
&lt;p&gt;上线之后，NN 的性能与 GBDT 的相当。这说明 NN 是有效的，能够服务于他们的真实流量。因此，他们有了继续探索的动力和勇气。&lt;/p&gt;
&lt;h3 id=&quot;lambdarank-nn&quot;&gt;Lambdarank NN&lt;/h3&gt;
&lt;p&gt;简单的神经网络并没有带他们走得很远。在实践中，他们对 Karpathy 的建议有了新的认识：don’t be a here, in the beginning。不要在一开始就想做大英雄。隐含的意思是，英雄还是要当的，不过要慢慢来。&lt;/p&gt;
&lt;p&gt;第二次进化的契机是，引入了 Lambdarank 的思想&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。这使得他们能够直接针对 NDCG（Normalized Discounted Cumulative Gain，一个线下评价指标）进行模型优化，从而同时提高了模型在线下和线上的性能。&lt;/p&gt;
&lt;p&gt;引入 Lmabdarank 思想，对简单神经网络的主要改进有二：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将数据整理成&lt;code&gt;{预订的房源，展示但没预订的房源}&lt;/code&gt;的形式，采用 pairwise 的训练方式代替了原来 pointwise 的方式；&lt;/li&gt;
&lt;li&gt;对 pairwise loss 进行加权。具体地，交换构成训练数据的两个房源在搜索排名中的位置，根据交换引起的 NDCG 的差异来设置加权系数。按照文章的说法，这还有助于提高预订的房源在搜索结果中的排名。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;decision-treefactorization-machine-nn&quot;&gt;Decision Tree/Factorization Machine NN&lt;/h3&gt;
&lt;p&gt;NN 逐渐成为了 Airbnb 提供搜索排名的主要模型。不过依然有其他的模型在研，其中最值得关注的两个分别是：GBDT 和 FM（Fatorization Machine）。这些模型在测试集上的性能与 NN 具有可比性，但是它们给出的搜索排名与 NN 的差异巨大。&lt;/p&gt;
&lt;p&gt;受混合模型的启发，他们将 FM 输出的最终预测作为 NN 的一项输入，并将 GBDT 中每棵决策树中最终被激活的叶节点的索引视为类别特征输入 NN，构造了一个搜索排名的混合模型。模型结构如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_nn_with_gbdt_and_fm.png&quot; alt=&quot;NN with GBDT tree nodes and FM prediction as features&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;deep-nn&quot;&gt;Deep NN&lt;/h2&gt;
&lt;p&gt;Airbnb 的简单神经网络经过 3 次进化之后，在2018年6月，终于脱胎成了深度神经网络。说它深，其实也不深，只是从单隐层变成了双隐层。&lt;/p&gt;
&lt;p&gt;一个典型的模型设置是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;195 维的输入层，这还是算上类别特征嵌入的 embedding size 的结果；&lt;/li&gt;
&lt;li&gt;127 维 + 83 维的隐藏层，使用 ReLU 作为激活函数；&lt;/li&gt;
&lt;li&gt;输入的特征多是房源的基本特征，如价格、历史预订数等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这里，作者们多提了一句，说，DNN 在 CV 等领域取得了人类水平的性能，但是“我们”并不清楚自己现在处于什么位置。这个问题的部分原因是，在搜索排名或者推荐系统中，难以定义人类的水平。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;借着作者开的话头，我也来多说几句。&lt;/p&gt;
&lt;p&gt;我是研究 NLP 的，在我的认知中，NLP 的研究是落后于 CV 的。2018 年，预训练语言模型爆火，Sebastian Ruder 将之称为 “NLP’s ImageNet moment”&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;。如果按 ImageNet 诞生的 2009 年算起，NLP 落后 CV 9年；如果按 AlexNet 横空出世的 2012 年算起，NLP 落后 CV 6 年。&lt;/p&gt;
&lt;p&gt;NLP 落后 CV 的一个重要原因是：CV 处理的对象以及评价标准，相对更加客观。相比之下，NLP 受限于人的主观感受，对于一个任务，很难给出客观的评价标准，比如如何评价“主观”和“客观”？这样的莫衷一是，加上语言本身的模糊性，使得 NLP 比 CV 更“难”一些。&lt;/p&gt;
&lt;p&gt;从处理对象的复杂性来看，假设 CV 处理的图片和视频是简单对象，NLP 处理的语言文字是复杂对象，那么，搜索排名或者推荐系统处理对象——人，就是超级复杂的对象。尽管一千个人有一千个哈姆雷特，但是语言文字一旦说出口写下来，它作为客观存在的那部分就固定了。人，却无时无刻不在变化之中。&lt;/p&gt;
&lt;p&gt;我甚至怀疑，Resys’s ImageNet moment 会不会到来。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;失败的模型&quot;&gt;失败的模型&lt;/h2&gt;
&lt;p&gt;看上面的四个例子，似乎 Airbnb 对深度学习的探索，只是在起步的时候走了弯路，后面就顺风顺水了。其实不然。&lt;/p&gt;
&lt;p&gt;正所谓，一将功成万骨枯。每一个成功的模型背后，都是数不清的失败的尝试。文中举了两个失败的案例。&lt;/p&gt;
&lt;h3 id=&quot;listing-id&quot;&gt;Listing ID&lt;/h3&gt;
&lt;p&gt;在这个例子中，他们尝试使用房源 ID 来学习房源嵌入，就像词嵌入一样。但是在尝试过各种方法之后，发现房源嵌入总会导致过拟合，以致于不可用。&lt;/p&gt;
&lt;p&gt;公认有用的 Embedding 技术，为什么不适用了？对此，Airbnb 的思考是：Embedding，需要一个 item 出现的次数足够多，才能收敛到一个合理有效的值，反之则不行。比如词嵌入对于生僻词的表示就是欠佳的。反观房源，最火爆的房源一年的预订数也不过 365 次，一般的房源的预订数就更加远远不如了。这使得每个房源 ID，都像是生僻词，难以学到高质量的房源嵌入。&lt;/p&gt;
&lt;h3 id=&quot;multi-task-learning&quot;&gt;Multi-Task Learning&lt;/h3&gt;
&lt;p&gt;观察到“房源详情页的浏览时长与预订之间具有相关性”，他们提出了同时预测预订概率和浏览时长的多任务学习方式，模型结构如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_multi_task_learning.png&quot; alt=&quot;Multi-Task Learning&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在这个模型中，他们依然使用了房源 ID 嵌入的方式。实际上，使用多任务学习框架的目的之一就是为了处理房源 ID 过拟合的问题。&lt;/p&gt;
&lt;p&gt;线上测试的结果表明，该模型确实大幅提高了房源详情的浏览时长，但是并没有改善预订率。于是，他们对长浏览低预订的房源进行了人工检查，找到了以下几点可能导致长浏览的原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;推荐了高端高价的房源；&lt;/li&gt;
&lt;li&gt;推荐了描述很长的房源；&lt;/li&gt;
&lt;li&gt;推荐了一些特别的，甚至是搞笑的房源。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;幸福的家庭都是相似的，不幸的家庭各有各的不幸。——《安娜·卡列尼娜》&lt;/p&gt;
&lt;p&gt;套用在这里的话，失败的模型各有各的原因。像 Airbnb 这样，去挖掘失败背后的原因，去深刻地理解失败。在我看来就特别好，可能是自己尚缺这样的能力吧。&lt;/p&gt;
&lt;p&gt;失败和成功，到底谁才是成功之母？有说失败的，也有说成功的。就失败而言，如果不能从中吸取教训，失败就不是好的失败，失败是失败之母。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;特征工程&quot;&gt;特征工程&lt;/h2&gt;
&lt;p&gt;在 NN 之前，Airbnb 使用 GBDT 做搜索排名，并且有很多年的特征工程经验。NN 的特征自动化很有吸引力，但是他们并没有放弃特征工程，因为他们发现，输入处理过的数据而不是原始数据能让 NN 的计算效率更高，性能可能也更强。&lt;/p&gt;
&lt;p&gt;需要注意的是，他们理解中的面向 NN 的特征工程，不同于传统的特征工程，不在于使用数学推导出某种特征，而是&lt;strong&gt;确保特征具有某些特性，使得 NN 本身能够进行高效地数学计算&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;feature-normalization&quot;&gt;Feature Normalization&lt;/h3&gt;
&lt;p&gt;一开始，他们将 GBDT 的输入特征不做任何修改地输入 NN。结果非常糟糕。&lt;/p&gt;
&lt;p&gt;对于决策树而言，只要数值特征的相对顺序有意义就行，它们确切的值反而不重要。NN 则对特征的数值很敏感。比如，异常值在反向传播时会带来很大的梯度。对于像 ReLU 这样的激活函数，由于梯度消失问题，可能会因此永久地被关停。&lt;/p&gt;
&lt;p&gt;对此，他们希望通过特征工程，确保所有的特征都被限制在一个小范围内：分布中的大多数值落在&lt;code&gt;{-1, 1}&lt;/code&gt;的区间内，分布的中位数为 0。具体地，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于特征分布类似正态分布的，使用$\frac{val-\mu}{\sigma}$做归一化，其中$\mu$和$\sigma$分别是特征均值和标准差&lt;/li&gt;
&lt;li&gt;对于特征分布类似幂律分布的，使用$log(\frac{1+val}{1+median})$做归一化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;feature-distribution&quot;&gt;Feature Distribution&lt;/h3&gt;
&lt;p&gt;除了将特征限制在一个小范围内，他们还希望通过特征工程，确保特征具有平滑的分布。这主要有以下三方面的原因。&lt;/p&gt;
&lt;p&gt;第一，分布的平滑性能用于识别 bug。这是因为，错误的分布通常不是典型的分布（正态分布、幂律分布等）。&lt;/p&gt;
&lt;p&gt;第二，平滑的分布能增强模型的泛化能力。DNN 的泛化能力为什么这么强，一直是学界研究的一个热点。对此，Airbnb 也有自己的发现与思考。他们通过绘制 DNN 每一层输出的分布（如下所示），发现这些分布在逐层变得更加平滑。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_distributions.png&quot; alt=&quot;Distributions from disfferent layers&quot;&gt;&lt;/p&gt;
&lt;p&gt;据此，他们给出了对 DNN 泛化能力的一个解释：当特征很多时，特征值的组合空间几乎无限大，而训练只能覆盖到一部分的特征组合。此时，低层输出的平滑分布确保了高层能够为未见过的数据正确地插值。&lt;/p&gt;
&lt;p&gt;第三，分布的平滑性能用于检查特征的完整性。&lt;/p&gt;
&lt;p&gt;对此，他们举了一个遇到的实例。房源的&lt;em&gt;未来可预订天数&lt;/em&gt;是房源质量的一个强信号，因为高质量的房源倾向于提前售完。但是他们发现房源的占用分布是不平滑的。进一步调研发现了影响房源占用的另一个因素：最少住宿天数，而这一因素依赖于日历，根据预订的可能性是动态变化的。于是，他们补充了&lt;em&gt;房源的平均住宿时长&lt;/em&gt;作为特征。&lt;/p&gt;
&lt;h3 id=&quot;high-cardinality-categorical-features&quot;&gt;High Cardinality Categorical Features&lt;/h3&gt;
&lt;p&gt;尽管房源 ID 容易导致过拟合，Airbnb 发现其他的高基数&lt;sup&gt;&lt;a href=&quot;#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;特征确实能够提升 NN 的性能。更重要的是，与 GBDT 不同，将高基数特征用于 NN，几乎不需要特征工程。NN 能够自动地学习此类特征的 embedding。&lt;/p&gt;
&lt;p&gt;此处，他们举了将&lt;em&gt;城市的社区&lt;/em&gt;作为高基数特征的例子。通过可视化，可以看到 embedding 捕捉到了城市的热点区域信息。&lt;/p&gt;
&lt;h2 id=&quot;系统工程&quot;&gt;系统工程&lt;/h2&gt;
&lt;p&gt;这一章主要介绍了 Airbnb 在深度学习模型之外下的功夫。&lt;/p&gt;
&lt;p&gt;第一，纠正了喂数据的方式。他们使用 CSV 文件作为旧 GBDT 的数据来源，并在迁移到 TensorFlow 之初沿用了该形式。但是 GPU 的利用率只有 25% 左右，大部分的训练时间都消耗在了解析 CSV 和读取数据上。按照作者们的说法，这是在用骡子拉法拉利啊！痛定思痛，转而使用 TensorFlow 推荐的 pipeline，训练速度提高了 17 倍，GPU 的利用率也达到了 90%。&lt;/p&gt;
&lt;p&gt;第二，鉴于大量特征几乎是不变的，他们将所有的准静态特征打包成了一个向量，存储在显存中，通过房源 ID 来索引，就像一个不可训练的 embedding 一样。由于避免了频繁地从磁盘读取数据，训练效率进一步提高了。不过，因为将训练周期内可能会变化的准静态数据也固定住了，相当于引入了一些噪声，算是为训练速度做出的取舍。&lt;/p&gt;
&lt;p&gt;第三，由于 Airbnb 的应用服务是基于 Java 的，为了满足搜索业务的低延迟硬性要求，开发了 Java NN library 用于对模型进行评分。&lt;/p&gt;
&lt;h2 id=&quot;超参数&quot;&gt;超参数&lt;/h2&gt;
&lt;p&gt;在“努力成为英雄”的阶段，Airbnb 对超参数做过详尽的调研与实验。尽管这一阶段最终以不了了之告终，但是他们也因此对超参数有了更深刻的认识。&lt;/p&gt;
&lt;h3 id=&quot;dropout&quot;&gt;Dropout&lt;/h3&gt;
&lt;p&gt;他们关于 Dropout 的最初印象是起正则化作用。但是在所有的尝试中，Dropout 无一例外都导致了线下指标的轻微下降。于是，他们重新理解了 Dropout。它更接近于一种数据增广（Data Augmentation）技术，当 Dropout 引入的随机噪声有效地模拟了训练数据中缺失的噪声时，它才有效。&lt;/p&gt;
&lt;p&gt;在 Airbnb 的业务场景下，随机噪声是无效的。作为替代，他们根据特定特征的分布人工模拟了噪声。这将线下 NDCG 提升了约 1%，但是线上性能并没有统计学意义上显著的提升。&lt;/p&gt;
&lt;h3 id=&quot;initialization&quot;&gt;Initialization&lt;/h3&gt;
&lt;p&gt;经过调研，他们使用 Xavier 初始化来初始化 NN 的参数，使用&lt;code&gt;{-1, 1}&lt;/code&gt;之间的随机均匀采样来初始化 embeddings。&lt;/p&gt;
&lt;h3 id=&quot;learning-rate&quot;&gt;Learning Rate&lt;/h3&gt;
&lt;p&gt;在 Airbnb 的搜索排名下，使用 Adam 难以提升模型性能。于是他们采用了&lt;code&gt;LazyAdamOptimizer&lt;/code&gt;的一个改版作为优化器。&lt;/p&gt;
&lt;h3 id=&quot;batch-size&quot;&gt;Batch Size&lt;/h3&gt;
&lt;p&gt;对于 Batch size 的选择，他们并没有采纳 Don’t Decay the Learning Rate, Increase the Batch Size&lt;sup&gt;&lt;a href=&quot;#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; 一文的结论，而是“可行就好”，选择了 200 的固定大小。&lt;/p&gt;
&lt;h2 id=&quot;特征重要性&quot;&gt;特征重要性&lt;/h2&gt;
&lt;p&gt;对于 NN，可谓成也萧何，败也萧何。一方面，自动地发现特征之间的非线性交互，正是 NN 的优势所在；另一方面，错综复杂的非线性交互使得考察单一特征的作用变得几乎不可能。&lt;/p&gt;
&lt;p&gt;Airbnb 通过以下方式尝试去理解单一特征对于 NN 的重要性。&lt;/p&gt;
&lt;h3 id=&quot;score-decomposition&quot;&gt;Score Decomposition&lt;/h3&gt;
&lt;p&gt;对于 GBDT，可以通过绘制偏依赖图（Partial Dependence Plots）&lt;sup&gt;&lt;a href=&quot;#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;的方式来考察结果对于单一特征的偏依赖，从而进行特征分析。&lt;/p&gt;
&lt;p&gt;对于 NN，尝试去理解单一特征的重要性是徒劳的。这甚至是一个概念上的错误：绘制偏依赖图，是基于特征能够独立地影响模型性能的假设的。然而使用非线性激活函数之后，特征之间相互纠缠，几乎不能再分离出单一特征对模型性能的影响。&lt;/p&gt;
&lt;h3 id=&quot;ablation-test&quot;&gt;Ablation Test&lt;/h3&gt;
&lt;p&gt;另一种思路是：每次去掉一个输入特征，观察模型性能下降的程度。特征的缺失导致模型性能下降越厉害的，说明该特征越重要。&lt;/p&gt;
&lt;p&gt;然而现实的情况是，去掉单一特征对线下指标几乎不影响，线下指标的轻微起伏更像是噪声。这可能是因为特征集的高度冗余，模型能够从剩余特征中自行推导出缺失特征携带的信息。&lt;/p&gt;
&lt;h3 id=&quot;permutation-test&quot;&gt;Permutation Test&lt;/h3&gt;
&lt;p&gt;第三种思路受启发于随机森林对于特征重要性的度量。具体地，每次将一个特征值用随机值替换掉，通过考察替换前后，模型性能的下降来衡量特征重要性。特征越重要，替换后的性能下降将越明显。&lt;/p&gt;
&lt;p&gt;然而，这依然落入了特征之间相互独立的思维误区。&lt;/p&gt;
&lt;p&gt;此外，随机替换特征值可能会生成现实中并不存在样本，在不合理的特征空间中考察特征重要性更加没有意义。&lt;/p&gt;
&lt;p&gt;反过来看，如果对一个特征值的随机替换并不影响模型性能，这说明模型可能并不依赖该特征。&lt;/p&gt;
&lt;h3 id=&quot;topbot-analysis&quot;&gt;Topbot Analysis&lt;/h3&gt;
&lt;p&gt;第四种思路是：给定查询，生成排好序的房源列表，考察列表顶部的房源的各特征分布与列表底部的差异。特征分布的差异指示了模型利用该特征的有效性。&lt;/p&gt;
&lt;p&gt;如下图所示，使用的模型对价格敏感，但对于评论数不太敏感。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_topbot.png&quot; alt=&quot;Airbnb Topbot Analysis&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;最后，作者们用下图描述了他们引入深度学习的心路历程：从准备大干一场的极度乐观，下降到失望的谷底，再到度过最黑的夜重新踏上征途。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/airbnb_journey.png&quot; alt=&quot;Anatomy of a journey&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这一过程，同样符合 Gartner 曲线&lt;sup&gt;&lt;a href=&quot;#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2020-06-07-papernotes_applying_deep_learning_to_airbnb_search/gartner_curve.png&quot; alt=&quot;Gartner Curve&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;参考资料&quot;&gt;参考资料&lt;/h2&gt;
&lt;section data-footnotes class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/research/publication/from-ranknet-to-lambdarank-to-lambdamart-an-overview/&quot;&gt;https://www.microsoft.com/en-us/research/publication/from-ranknet-to-lambdarank-to-lambdamart-an-overview/&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://ruder.io/nlp-imagenet/&quot;&gt;https://ruder.io/nlp-imagenet/&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cardinality_(SQL_statements)&quot;&gt;https://en.wikipedia.org/wiki/Cardinality_(SQL_statements)&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://openreview.net/forum?id=B1Yy1BxCZ&quot;&gt;https://openreview.net/forum?id=B1Yy1BxCZ&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://scikit-learn.org/stable/auto_examples/inspection/plot_partial_dependence.html&quot;&gt;https://scikit-learn.org/stable/auto_examples/inspection/plot_partial_dependence.html&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Hype_cycle&quot;&gt;https://en.wikipedia.org/wiki/Hype_cycle&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content:encoded><category>笔记</category><category>搜索排名</category><category>深度学习</category><category>推荐系统</category></item><item><title>【读书笔记】小岛经济学</title><link>https://lifelonglove.cn/notes/how_an_economy_grows_and_why_it_crashes/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/how_an_economy_grows_and_why_it_crashes/</guid><description>借小岛捕鱼故事理解储蓄、资本、信贷、通胀与经济危机如何彼此作用。</description><pubDate>Fri, 22 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在微信读书上看到我妹在读&lt;a href=&quot;https://book.douban.com/subject/26897464/&quot;&gt;《小岛经济学》&lt;/a&gt;，并且难得地给出了五星好评。好奇地点开来看，然后就被深深地吸引住了，太过瘾了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;《小岛经济学》，一本经济学入门读物，讲述了人类与鱼的故事：怎么高效地捕鱼，一条鱼引发的革命，大鱼为何变成小鱼，如何空手套白鱼……&lt;/p&gt;
&lt;p&gt;噢，故事中的鱼就是现实中的货币。&lt;/p&gt;
&lt;p&gt;推荐给所有对经济学感兴趣的朋友。&lt;/p&gt;
&lt;p&gt;推荐指数：★★★★☆&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;这是一本怎样的书&quot;&gt;这是一本怎样的书？&lt;/h2&gt;
&lt;p&gt;首先，必须吐槽下中译名《小岛经济学：鱼、美元和经济的故事》。取这样的主标题，应该是为了向“魔鬼经济学”等前辈看齐，冲下卖点，但是完全不点题了。副标题也不咋滴。小岛还是小岛，经济学也是经济学，但就是没内味儿了。人家原作名叫“How an Economy Grows and Why It Crashes”，直译应该是《经济如何增长又为何崩溃》。&lt;/p&gt;
&lt;p&gt;如原作名所示，本书讲述的是经济的缘起与坏灭的故事。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;眼看他起朱楼，眼看他宴宾客，眼看他楼塌了。——《桃花扇》&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多时候，我们对一个事物不了解，或者一知半解，是因为我们只看到了结果，而没有看到原因，不知道其中的脉络，所以不解其中意。&lt;/p&gt;
&lt;p&gt;最近有点迷《柯南》。探案破案，是要去案发现场的，甚至是找出第一案发现场，还要了解作案动机。要一直往前溯源，才能理清案件的来龙去脉。&lt;/p&gt;
&lt;p&gt;要了解经济学的原理，最好的方式也是去它的源头看一眼。然后顺流而下，把握其脉络。那么，最近发生的一切，合理抑或不合理，心中就有答案了。&lt;/p&gt;
&lt;p&gt;本书就提供了这样的机会。小岛经济学的故事，要从艾伯厌倦了每天捕鱼，而且只能捕一条鱼的那天说起……关于故事的内容，就不多说了，看就对了。&lt;/p&gt;
&lt;p&gt;总得来说，文字浅显易懂，加上丰富的插图，看起来非常轻松惬意，对小白很友好。每段故事之后，作者都会把视角从故事拉回现实，浅析故事背后的逻辑，帮助读者更好的理解。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;且看且想&quot;&gt;且看且想&lt;/h2&gt;
&lt;p&gt;以下分享几段我看书时随手记的感想。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;许多政客还喜欢自我标榜，认为自己促成了公平公正的工作环境，因为他们通过各类法规，要求雇主为员工提供强制休假、医疗保健、请病假以及休产假等各类权益，于是便居功自傲。但是在最低薪资制度下，使这些权益得以实现的是员工的生产力，而非那些法规。然而，这些要求却剥夺了员工的选择权：他们本可以在这些权益和更高的薪资之间做出选择，如果那些政客不强制雇主为员工提供上述待遇的话，他们是有可能拿到更高薪资的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;《黑天鹅》中关于“沉默的证据”的一个例子：政府的税收是固定的。灾难发生时，在救灾方面投入更多了，相应地，就会削减其他方面的开销，比如对高校里药物研发的科研赞助。救灾是必须的，但是投入多少是一个问题。救灾投入的1000万能救10万人，但是同样多的钱投入药物研发，也许能救活100万甚至更多的人。应该如何分配呢？政客会公布救灾的投入，百姓会拍手叫好，只是不知道哪里的开支要被削减了。&lt;/p&gt;
&lt;p&gt;回到此处，企业本来可以为员工提供更高的薪资，但是因为福利制度，加上企业家不是慈善家，福利实际仍由员工买单。这里有一个价值的转移。高福利+低薪资也许等价于高薪资，但是如文章所说，员工丧失了选择权：员工本可以选择高薪资，在此基础上自由地支配那部分被用作福利的薪资，但是现在没有了。&lt;/p&gt;
&lt;p&gt;这是个见仁见智的问题。但是有必要了解一下：生活中处处有“守恒”。收之桑榆，失之东隅。朝三暮四，暮四朝三。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参议员们都无法相信自己的好运。现在，他们可以随意做出任何竞选承诺了，再也不必维持收支平衡，也不需要通过提高税率为支出筹钱了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不要看对方承诺了什么，而要看他兑现了什么。毕竟，吹牛是不用纳税的，承诺是很廉价的，说的要比唱的好听。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;任何头脑清醒的人都能看出，2006年的房价已经高得离谱了。人们对房价的估计与对自身负担能力的估计脱节了。所有的数字都不对劲。然而，经济学家却想出了一些莫名其妙的理论，似乎能解释房价上涨的原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大道至简。当不能简单地解释一个现象时，任何复杂的理论都是危险的。理论越复杂，它成立的条件就越苛刻，适用的范围就越狭窄，它就越脆弱，也就越危险。&lt;/p&gt;
&lt;p&gt;一方面，常在河边走，哪有不湿鞋？对于脆弱，可远观而不可亵玩。花瓶用来看就好，不要捧来捧去，会碎的。&lt;/p&gt;
&lt;p&gt;另一方面，哪个骗局不是为了让人看不懂而精心设计的。&lt;/p&gt;
&lt;p&gt;巴菲特的智慧：不要做自己不懂的事情。那些别人给你解释不清楚的，要么他自己也不懂，要么他就是个骗子。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，这次经济危机已经促使经济恢复平衡，这个过程会很痛苦，而且远未结束。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;经济危机的作用是回归（regression），让经济回到它原来的位置。&lt;/p&gt;
&lt;p&gt;经济有一个合理的增速，比如我国近年来的6%左右的增速。经济危机和复苏，都是一个回归过程。各种泡沫，使得经济虚高，泡沫破裂，引发经济危机，经济回落到它应该在的位置；疫情冲击，使得经济停滞，复工复产，政府大发消费券促消费，也是为了让经济尽快回到它原来的位置。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要记住，一国的经济不会因为人们的消费而增长，而是经济增长带动人们的消费。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;和数学有公理一样，经济学也有其底层逻辑，比如本书强调的“经济不会因为消费而增长，而是经济增长带动消费” 。&lt;/p&gt;
&lt;p&gt;万丈高楼平地起。任何有违学科底层逻辑的，0.01%是革命，99.99%是骗局。&lt;/p&gt;
&lt;p&gt;因此，是否违背了底层逻辑也可以作为是否骗局的判断依据。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;两位作者是奥地利经济学派的，该派信奉自由市场经济，主张政府要减少对于市场的干预。&lt;/p&gt;
&lt;p&gt;在故事的后半段，也就是“Why It Crashes”部分，基本上就是在批判小岛政府和中央银行的昏招迭出。简直胡闹。&lt;/p&gt;
&lt;p&gt;2020年很魔幻，我们真真切切地看到了政府是会犯错的，而且可能是明知故犯。其中，以喜剧之王特老总为典型代表。&lt;/p&gt;
&lt;p&gt;2020年3月，在美帝只有十来万确诊病例的时候，特老总的选择是希望尽快复工。这不是草菅人命吗？不过，这背后是有一套逻辑在的：2020年是大选之年，保住经济，他就有望连任。&lt;/p&gt;
&lt;p&gt;一将功成万骨枯。和平年代，一代总统的脚下有多少累累白骨。从他选择救经济而不是保护群众的生命安全起，人命于他何干？&lt;/p&gt;
&lt;p&gt;讲这么多，想表达的是，要当心和我们利益不相关的人。本书故事的最后，为什么小岛政府和中央银行的骚操作不断？以利率调控为例，中央银行具有调控利率的权力，但是它本身和利率没有任何关系。它的决策更多的是出于政治而非经济目的。（奥地利学派观点）&lt;/p&gt;
&lt;p&gt;挂羊头，卖狗肉，猪买单？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但随着经济逐步回暖，棚屋与基金价格逐渐回升，参议院和那些经济学家们也宣布他们胜利了。人们普遍认为，伯南柯比他的历届前任都更足智多谋。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在有一种声音，罗斯福的新政措施对美国摆脱大萧条其实是弊大于利。&lt;/p&gt;
&lt;p&gt;咱不是专业的，不好评价罗斯福的功过是非。不过有一点是肯定的，人喜欢因果，但现实是复杂的。寻找因果，大概率会错配因果。&lt;/p&gt;
&lt;p&gt;现实具有滞后性。罗斯福的很多政策，和他的前任胡佛的别无二致。有没有可能，罗斯福上台之后，胡佛新政开始起效了？&lt;/p&gt;
&lt;p&gt;说来讽刺，搞新政的胡佛本人其实是自由市场经济的支持者。有没有可能，罗斯福上台之后，市场已经自我调节得差不多了？&lt;/p&gt;
&lt;p&gt;又讲了这么多，这里想表达的是，是非功过，要放到一个更大的时空尺度去判断。局部的善，可能是整体的恶；当代的善，也可能是千秋的恶。(反脆弱一书的观点)&lt;/p&gt;
&lt;p&gt;现实的问题，鲜有立竿见影的特效药。如果有，要提防它的副作用。&lt;/p&gt;
&lt;p&gt;故事中，小岛政府和中央银行，一次次地宣告胜利，也一次次地为后世埋下隐患。不知道他们是在解决问题，还是在制造问题。&lt;/p&gt;
&lt;p&gt;从无限游戏的角度来看，一切的胜利都是阶段性的胜利。&lt;/p&gt;
&lt;p&gt;故事中的小岛政府和中央银行影射的就是美帝和美联储。小岛的故事完结了，美帝的故事还在继续。&lt;/p&gt;
</content:encoded><category>笔记</category><category>经济学</category></item><item><title>papernotes</title><link>https://lifelonglove.cn/notes/nmt-by-jointly-learning-to-align-and-translate/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/nmt-by-jointly-learning-to-align-and-translate/</guid><description>梳理注意力机制如何用可变上下文替代固定编码，并在翻译时对齐源词。</description><pubDate>Fri, 04 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/abs/1409.0473&quot;&gt;https://arxiv.org/abs/1409.0473)
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; 本文是将 &lt;em&gt;Attention Mechanism&lt;/em&gt; 引入机器翻译的第一文, 文中称为&lt;em&gt;对齐 Alignment&lt;/em&gt;. 在 encoder 与 decoder 之间增加了一个 &lt;em&gt;alignment model&lt;/em&gt;, 使得翻译不再是将 source 编码成固定长度的向量再解码成 target, 而是维护一个可变的 context, 每翻译一个词, 都从 source 中找出最相关的部分 (即对齐) 着重翻译. 由于使用的 soft-alignment, 梯度可通过 alignment model 反向传播, 因此同时学习翻译与对齐.&lt;/p&gt;
&lt;h4 id=&quot;key-points&quot;&gt;Key Points&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;指出传统 encoder-decoder 框架的不足之处. 随着序列的增长, 将输入的所有信息压缩进一个固定长度的向量越来越困难, 模型性能将急剧下降.&lt;/li&gt;
&lt;li&gt;引入了 aligment model, decoder 每生成一个词的翻译, 都会对输入序列进行搜索, 找出与当前翻译最相关的部分; 结合之前的输出与查询结果, 进行更好的翻译.&lt;/li&gt;
&lt;li&gt;给出了 alignment model 的一个范式:
&lt;ul&gt;
&lt;li&gt;根据之前的输出和对齐结果生成下一个词: $p(y_i|y_1, \dots, y_{i-1}, x)=g(y_{i-1}, s_i, c_i)$; $s_i$, $c_i$ 分别是 decoder RNN 第 i 时刻的 state 与查询得的 context.&lt;/li&gt;
&lt;li&gt;根据之前的输出和 context 计算当前状态: $s_i=f(s_{i-1}, y_{i-1}, c_i)$;&lt;/li&gt;
&lt;li&gt;context vector 是加权求和的结果: $c_i=\Sigma_{j=1}^{T_x} \alpha_{ij}h_j$; $h_j$ 是 endoder 在第 i 时刻的状态, $\alpha_{ij}$ 是对应的权值.&lt;/li&gt;
&lt;li&gt;使用 decoder 的上一个状态计算和 encoder 某时刻状态的相似度, 即对齐程度: $e_{ij}=a(s_{i-1}, h_j)$; (按照原文的表示, 此处为小写的 A)&lt;/li&gt;
&lt;li&gt;概率和必为 1, 因此使用 softmax, soft-alignment 由此得名: $\alpha_{ij}=\frac{exp(e_{ij})}{\Sigma_{k=1}^{T_x} exp(e_{ik})}$. (此处为希腊字母 alpha, 表征概率)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对齐时, 在整个输入序列上搜索相关部分, 每个词都以一定概率和翻译结果对齐, 好处是可以考虑更多的上下文信息, 并很自然地解决了词组的翻译问题, 带来的问题是, 对于 $T_x$ 长度的 source 和 $T_y$ 长度的 target, 要进行 $T_x \times T_y$ 运算.&lt;/li&gt;
&lt;li&gt;具体地, 文中使用感知机来计算相似度, 即 $a(s_{i-1}, h_j)=v_a^T tanh(W_a s_{i-1}+U_a h_j)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-05-04-nmt-by-jointly-learning-to-align-and-translate/origin_nmt_alignment.png&quot; alt=&quot;Alignment Illustration&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;notesquestions&quot;&gt;Notes/Questions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;从概率的角度看, translation 可以理解为 conditional language modeling. 即找出最优的翻译, 最大化 $p(target|source)$ 的概率.&lt;/li&gt;
&lt;li&gt;将 $s_i＄ 和 $y_i$ 分离开, 视觉上和数学上似乎都不太美.&lt;/li&gt;
&lt;li&gt;给出了 Attention 的一个范式, 但计算方法上还比较粗糙, 这也为后面的各种论文提供了发挥空间.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>自然语言处理</category><category>attention</category></item><item><title>论文笔记 4</title><link>https://lifelonglove.cn/notes/word_embeddings_3/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/word_embeddings_3/</guid><description>梳理 CBOW、Skip-gram、负采样和子采样的模型结构、训练目标与效果。</description><pubDate>Fri, 26 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;efficient-estimation-of-word-representations-in-vector-space&quot;&gt;Efficient Estimation of Word Representations in Vector Space&lt;/h2&gt;
&lt;p&gt;本文是 word2vec 的第一篇, 提出了大名鼎鼎的 CBOW 和 Skip-gram 两大模型.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;由于成文较早, 本文使用的一些术语有一些不同于现在的叫法, 我都替换为了现在的叫法.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CBOW 的架构如下所示. 与作者提到的 feedforward NNLM 的架构&lt;code&gt;input-&amp;gt;linear-&amp;gt;hidden-&amp;gt;output&lt;/code&gt;很像. 作者发现模型复杂性主要来自 non-linear hidden layer, 因此去掉了它. 由于 linear layer 被所有 words 共享, 所有 words 都会被映射到同一片空间. CBOW 所要做的就是利用一个单词前后的单词, 称为其&lt;code&gt;上下文, context&lt;/code&gt;来预测它.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/CBOW_architecture.png&quot; alt=&quot;CBOW_architecture.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;图中的 Projection layer 就是 linear layer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Skip-gram 模型如下所示. 看上去与 CBOW 正好相反, 它接受一个单词作为输入, 预测在其上下文中的单词们. 文中提到, 增大 context window 能提升 word vector 的质量.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/skip-gram_architecture.png&quot; alt=&quot;skip-gram_architecture.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看到, 无论是 CBOW 还是 Skip-gram 模型, 都只用了一个 linear layer 而已! 模型结构非常简单, 训练速度较之前的模型快了很多, 从而能应用于更大规模的语料, 以获得更好的结果. 这也是本文提出这两个模型的目的之一.&lt;/p&gt;
&lt;p&gt;本文的试验结果显示, 在某一点之后, 单独增加 word vector 的维数或增大数据规模能取得的性能提升开始下降. 因此作者同时增大 word vector 的维数, 又增大数据规模- -.&lt;/p&gt;
&lt;h2 id=&quot;distributed-representations-of-words-and-phrases-and-their-compositionality&quot;&gt;Distributed Representations of Words and Phrases and their Compositionality&lt;/h2&gt;
&lt;p&gt;本文是 word2vec 的第二板斧, 提出了一些提升的技巧, 上一篇关于 word embedding 的&lt;a href=&quot;/notes/word_embeddings_0to1/&quot;&gt;笔记&lt;/a&gt;中的一些技巧就来自此文.&lt;/p&gt;
&lt;p&gt;上一篇 word2vec 的实验中, 作者们用&lt;code&gt;hierarchical softmax&lt;/code&gt;代替 softmax, 以加速计算. (Hierarchical softmax 详情请看&lt;a href=&quot;/notes/word_embeddings_1to2/&quot;&gt;上一篇笔记&lt;/a&gt;)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以下部分内容在之前的笔记中已经提及, 但作为本文的重点, 还是拎出来再讲一遍.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;negative-sampling&quot;&gt;Negative sampling&lt;/h3&gt;
&lt;p&gt;本文提出了&lt;code&gt;Negative sampling&lt;/code&gt;代替 hierarchical softmax. 实际上, negative sampling 是从&lt;code&gt;NCE, noise contrastive estimation&lt;/code&gt;简化来的. NCE 可以看作近似最大化 softmax 的对数概率, 然而 Skip-gram 模型只关心学习高质量的 word vector, 因此将 NCE 简化成只从噪声分布中采样. Negative sampling 的目标函数如下:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/negative_objective.png&quot; alt=&quot;negative_objective.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Pn(w) 是噪声分布, wO 是 wI 的上下文中的单词, 称为正样本, wi 都是不属于 wI 上下文的负样本. Negative sampling 的任务就是学习对 wO 和 wi 的正确分类. 文中的实验显示, 对于小数据集, 取 5 到 20 个负样本即可, 而大数据集使用 2 到 5 个负样本.&lt;/p&gt;
&lt;h3 id=&quot;subsampling-of-frequent-words&quot;&gt;Subsampling of frequent words&lt;/h3&gt;
&lt;p&gt;Subsampling of frequent words 带点信息论的味道, 即最常使用的词, 能提供的信息量不如低频单词, 比如 France 与 Paris 的共用, 比 France 与 the 共用带来的信息量显然更大. 换个角度, 高频单词在多次迭代训练之后, 其向量不会再显著地改变.&lt;/p&gt;
&lt;p&gt;于是, 文中使用 &lt;img src=&quot;/img/2018-01-26-word_embeddings_3/subsampling_of_frequent_words.png&quot; alt=&quot;subsampling_of_frequent_words.png&quot;&gt; 对单词进行 subsampling, t 为阈值, f(wi) 为词频. 词频越高, 显然 P(wi) 就越大, 即高频单词会以更大的概率会剔除, 而词频小于 t 的单词则不会被剔除.&lt;/p&gt;
&lt;p&gt;Subsampling 带来的另一个好处是, 使得单词的 context 比实际大. 具体见前面的&lt;a href=&quot;/notes/word_embeddings_0to1/&quot;&gt;笔记&lt;/a&gt;吧&lt;/p&gt;
&lt;h3 id=&quot;phrase-embedding&quot;&gt;Phrase Embedding&lt;/h3&gt;
&lt;p&gt;一些 phrase 的语义并非其中各词的简单组合, 比如&lt;strong&gt;New York&lt;/strong&gt;, 因此有时候需要单独地学习&lt;code&gt;phrase embedding&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;要确定哪些 phrase 具有特殊含义, 文中提到的方法首先去寻找频繁同时出现, 但在其他上下文又不频繁出现的单词, 然后将这样的 phrase 用一个唯一的 token 表示.&lt;/p&gt;
&lt;p&gt;简单地使用 n-gram, 会大大增大单词表, 并不是一种恰当的做法. 文章使用了一个基于 unigram, bigram 的数据驱动方法, 通过公式 &lt;img src=&quot;/img/2018-01-26-word_embeddings_3/phrase_score.png&quot; alt=&quot;phrase_score.png&quot;&gt; 计算两个单词能组合成一个有效 phrase 的分数.delta 用于防止过多 phrase 包含太低频的单词.&lt;/p&gt;
&lt;p&gt;对训练数据应用多次以上方法, 可以学习更长的 phrase.&lt;/p&gt;
&lt;p&gt;本文还有一个不同于&lt;strong&gt;king-man+woman=queen&lt;/strong&gt;有趣的发现: &lt;strong&gt;Russian+river=Volga River&lt;/strong&gt;. 具体点就是, 两个 word vectors 的 element-wise addition 能得到一个语义相关的 vector. 文中的解释是, Skip-gram 模型预测单词上下文, 所以 word vector 可以说表示了 context 的分布. 而 word vectors 又与输出层的概率有对数关系, 因此两个 word vector 相加与两个 context distribution 的积相关. 而 context distribution 的积又可以看作是&lt;code&gt;求与&lt;/code&gt;: 词 a 同时出现在词 b, c 的 context 中, 可以看作 b, c 对 a 都有较高的概率, 则 a 有很大的概率是 b, c 向量和的结果.&lt;/p&gt;
&lt;p&gt;本文指出, 在他们的实验中, 对性能影响最大的是: 架构的选择, word vector 的维数, subsampling rate, size of context window.&lt;/p&gt;
&lt;h2 id=&quot;glove---global-vectors-for-word-representation&quot;&gt;GloVe - Global Vectors for Word Representation&lt;/h2&gt;
&lt;p&gt;本文指出, 学习 word vector 的模型族主要是以下两类:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;global matrix factorization method&lt;/code&gt;, 比如&lt;code&gt;Latent Semantic Analysis, LSA&lt;/code&gt;, 能有效地利用统计信息, 但在 word analogy 任务上效果相对较差, 这意味着其向量空间结构并非最优;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local context window method&lt;/code&gt;, 比如 skip-gram model, 在 word analogy 任务上效果好, 但利用语料的统计信息的能力较差, 因为它基于分离的局部上下文进行训练, 而非全局的 co-occurrence 统计.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所有学习 word representations 的无监督方法, 主要利用了 statistics of word occurences. 但是问题是, 词意是如何从统计中涌现出来的, word vectors 是如何表示词意的? 对此, 本文分享了作者的理解, 提出了 Global Vectors &lt;code&gt;GloVe&lt;/code&gt; 模型, 即用模型直接捕获语料的全局统计量.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;符号说明: 用$X$表示单词间同时出现次数的矩阵, $X_{ij}$表示词 j 出现在词 i 上下文中的次数, $X_i=\Sigma_k X_{ik}$表示出现在词 i 的上下文中单词总数. $P_{ij}=P(j|i)=X_{ij}/X_i$表示词 j 出现在词 i 上下文的概率.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作者们的想法是这样的: 单词间的关系可通过它们与不同&lt;code&gt;probe words&lt;/code&gt;同时出现的比例来估计. 要估计 ice 与 steam 的关系, 令 i=ice, j=steam, k 为 probe word. 首先取 k=solid, $P_{ik}/P_{jk}$会是一个大的值 (solid 常出现在 ice 上下文, 但几乎不出现在 steam 上下文, 下同); 而取 k=gas, $P_{ik}/P_{jk}$会是一个很小的值. 而取 k=water 或 k=fashion 时, $P_{ik}/P_{jk}$会是一个接近 1 的值 (water 与 ice, steam 都相关, 出现在它们上下文中词频差不多; fashion 与 ice, steam 都不怎么相关). 如此一来, $P_{ik}/P_{jk}$就能比较好地区分与两个词各自相关的词 (solid 和 gas) 和都相关或都不相关的词 (water 和 fashion), 同时也能区分各自相关的词.&lt;/p&gt;
&lt;p&gt;基于以上想法, 作者们指出学习 word vector 的合理方法应该从&lt;strong&gt;同时出现的概率比&lt;/strong&gt;开始, 而不是单词各自的概率.&lt;/p&gt;
&lt;p&gt;然后文章对概率比 $P_{ik}/P_{jk}$ 进行了一系列转化, 有兴趣的同学可以去看看, 有理有据, 很详细.&lt;/p&gt;
&lt;p&gt;考虑到同等对待所有的 co-occurrences 反而不好, 因为那些很少同时出现的词, 可能是噪声, 携带的有用信息量太少, 文章引入了&lt;code&gt;加权最小二乘回归模型, weighted least squares regression model&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-role-of-context-types-and-dimensionality-in-learning-word-embeddings&quot;&gt;The Role of Context Types and Dimensionality in Learning Word Embeddings&lt;/h2&gt;
&lt;p&gt;本文探讨了上下文以及维度对于 word embeddings 的影响.&lt;/p&gt;
&lt;p&gt;word embeddings 通常在 intrinsic tasks 和 extrinsic tasks 上都进行评估. 所谓 intrinsic task, 考虑单词间的语义关系, 但是不放到具体语境中, 比如同义词, 或者类似国家与首都的关系; 而 extrinsic task, 就是下游的各种 NLP 任务, 比如情感分析等.&lt;/p&gt;
&lt;p&gt;本文指出, 近来的研究表明 intrinsic evaluation 更容易进行 (word embedding 相关论文多以 intrinsic task 进行实验), 但 intrinsic evaluvation 的结果与 extrinsic evaluation 的结果相关不大, 就是说 intrincis evaluation 结果特别好的 word vector 在 extrinsic evaluation 中结果并非最好. 但 extrinsic evaluation 在实际应用中是相当重要的.&lt;/p&gt;
&lt;p&gt;本文的结论是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;应仔细地为 extrinsic NLP tasks 挑选合适的 word embeddings, 而不是仅仅依赖 intrinsic evaluation 的结果, 一个原因是 intrinsic task 倾向于特定类型的上下文和高维向量.&lt;/li&gt;
&lt;li&gt;对于 extrinsic task, 当增大 word vector 的维数不能再提升性能时, 简单地将多个不同上下文习得的 word vectors 拼起来, 还能再提升一点性能.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了研究上下文类型对 word embedding 的影响, 文中首先将3种语料混合起来, 得到一个更大的语料, 然后在 3 种上下文中学习 word embedding:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;window-based word embeddings&lt;/code&gt;, 此时的上下文就是以 target 为中心的窗口;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependency-based word embeddings&lt;/code&gt;, 此时的上下文不再是 1 中的窗口, 而是与 target 语义相关的单词组成的上下文;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;substitute-based word embeddings&lt;/code&gt;, 此时的上下文是由 Language Model 生成的, 一个单词的上下文表示为可以替换它的单词组成的列表. 比如&lt;em&gt;I &lt;strong&gt;love&lt;/strong&gt; my job.&lt;em&gt;中的&lt;/em&gt;love&lt;/em&gt;, 其上下文是[quit 0.5, love 0.3, hate 0.1, lost 0.1], 单词是在语境中可替换&lt;em&gt;love&lt;/em&gt;的单词, 概率则是 LM 给出的.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后文章给出了不同类型上下文的感性(非量化)结果, 如下, 是 3 种上下文习得的与&lt;em&gt;playing&lt;/em&gt;最近的单词们.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/qualitative_effective_of_context_type.png&quot; alt=&quot;qualitative_effective_of_context_type.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;很明显, 它们在 extrinsic NLP tasks 上的性能会有所不同, 但又很难说清楚孰优熟劣. 它们只是表现了不同角度的相似性. 因此文章又针对 word embedding conbination 进行了实验. 说白了就是用不同的方法将基于不同上下文习得的 word embedding 相结合, 不放过任何可利用的信息.&lt;/p&gt;
&lt;p&gt;文中使用了 3 种方法: 1) 简单的拼接, 比如两个 128 维的向量拼成一个 256 维的向量; 2) 使用&lt;code&gt;SVD, singular value decomposiion, 奇异值分解&lt;/code&gt;对拼接后的向量降维; 3) 使用(线性)&lt;code&gt;CCA, canonical correlation analysis, 典型相关分析&lt;/code&gt;处理不同上下文的 word embeddings.&lt;/p&gt;
&lt;p&gt;但文中的实验结果表明, 简单的向量拼接的效果更好. 作者们猜测原因在于: &lt;strong&gt;不同上下文类型习得的 word embeddings 捕捉到的信息不一样, 但是是互补的, 简单的拼接保留了差异性也就保留了这些信息&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;enriching-word-vectors-with-subword-information&quot;&gt;Enriching Word Vectors with Subword Information&lt;/h2&gt;
&lt;p&gt;如题所示, 本文提出了一种分层地表示 word embedding 的方法. 一个单词被表示为 n-gram 的词袋. 然后 word vector 就与每一项 n-gram 都相关, 比如表示为 n-gram vector 的和.&lt;/p&gt;
&lt;p&gt;Subword 背后的思想: 大多数现有方法都用一个分离的 vector 来表示一个 word, 这就忽视了 word 的内在结构, 而它可能蕴含丰富的词意. 这对于拉丁语系的语言可能是很有帮助的. 这样做的一个好处是, 对于 corpus 中出现频率极低的, 或者从未出现过的 word, 利用 character level information, 能更好地表示它们.&lt;/p&gt;
&lt;p&gt;之前的研究也有使用 character level models 做 NLP的. 这些模型不要求 segmentation, 直接从 character 学习 word representation.&lt;/p&gt;
&lt;p&gt;本文提出的方法也很简单, 在一个 word 前后加上 &lt;code&gt;&amp;lt;&lt;/code&gt; 与 &lt;code&gt;&amp;gt;&lt;/code&gt;, 然后将单词分成 n-gram 的词袋, 此处前后缀能起到区别于其他单词序列的作用. 比如使用 3-gram, &lt;code&gt;where&lt;/code&gt; 被表示成 &lt;code&gt;&amp;lt;wh&lt;/code&gt;, &lt;code&gt;whe&lt;/code&gt;, &lt;code&gt;her&lt;/code&gt;, &lt;code&gt;ere&lt;/code&gt;, &lt;code&gt;re&amp;gt;&lt;/code&gt;. 英文单词 &lt;code&gt;her&lt;/code&gt; 的词袋是 &lt;code&gt;&amp;lt;he&lt;/code&gt;, &lt;code&gt;her&lt;/code&gt;, &lt;code&gt;er&amp;gt;&lt;/code&gt;, 因此不应将 where 中的 3-gram &lt;code&gt;her&lt;/code&gt; 与单词 &lt;code&gt;her&lt;/code&gt; 搞混. 另外, 将带前后缀的原单词 &lt;code&gt;&amp;lt;where&amp;gt;&lt;/code&gt; 也加入词袋. 最后 word vector 就用 n-gram vectors 的和表示.&lt;/p&gt;
&lt;p&gt;Subwords 模型允许在 word 间共享表示, 因此对于低频单词, 能学到更可靠的表示.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 我看到一位大牛的话, 深以为然:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;I feel like a significant percentage of Deep Learning breakthroughs ask the question &quot;how can I reuse weights in multiple places?&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Convolutional layers reuse in multiple locations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Recurrent (LSTM) layers reuse for multiple timesteps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Capsules reuse across orientation.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;component-enhanced-chinese-character-embeddings&quot;&gt;Component-Enhanced Chinese Character Embeddings&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;PS: 本文中出现的 Chinese word 是能完整表意的汉字或词组, 比如&lt;strong&gt;太阳&lt;/strong&gt;, 而单独的一个汉字被称为 character. 以下我将默认使用字表示 character, 词表示 word.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文的引言部分抛出了一个不太相关的问题: &lt;strong&gt;由 window 定义的 context 会不可避免地包含一些语义并不太相关的词, 而丢失掉一些更重要的, 更相关的词&lt;/strong&gt; 一种解决方案是, 在 context 中 keep order features (常规的 word2vec, CBOW 或 Skip-gram, order 都无法被利用起来) 或者利用 dependency relations 来求得一个包含 order information 的“更好”的 context. 另一种解决思路是, 使用神经网络来&lt;code&gt;构词, morphological composition&lt;/code&gt; 或将 morphological knowledge 加入神经网络框架作为额外输入.&lt;/p&gt;
&lt;p&gt;本文的思想是: 汉字可以表示成各&lt;code&gt;部&lt;/code&gt;的组合, 我们比较熟知的概念是&lt;code&gt;部首&lt;/code&gt;, 但部首与非部首都蕴含了很多信息, 能帮助计算机更好地理解汉字, 甚至在不给定 context 的情况下, 根据部提供的信息也能推断出汉字的意思.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;春,推也。从艸屯,从日,艸春时生也。会意,屯亦声…今隶作春字,亦作芚。——说文&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文的具体做法就是: 从汉字中提取各&lt;strong&gt;部&lt;/strong&gt;构成一个列表, 并基于部首通常比其他部包含更重要的想法, 将其放在列表的首位. 之后的训练使用了改进的 CBOW 与 Skip-gram, 与常规 word2vec 方法的区别在于, 本文提出的 CBOW 条件于 context character + component list, Skip-gram 同样如此. 下图中, $c_i$ 表示 context character, $e_i$ 表示该汉字对应的 component list, 即部的列表.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/component-enhanced_character_embedding_models.png&quot; alt=&quot;component-enhanced_character_embedding_models.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;文中对于部首这样处理的, 可以借鉴下:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/transformation_of_chinese_components.png&quot; alt=&quot;transformation_of_chinese_components.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;阿里被 AAAI 2018 接收的一篇文章利用了笔画信息来学习 wordembedding. (不过我还没来得及看)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;joint-learning-of-character-and-word-embeddings&quot;&gt;Joint Learning of Character and Word Embeddings&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在 Chinese NLP 领域, 应该默认以 word 为词, character 为字, 本文也是如此. 以后如无意外, 不再赘述.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文可以算是前面 subword 的中文翻版, 但早了 2 年发表, 并且文中明确提到了 &lt;code&gt;very little work has been done to explore sub-word units and how they can be used to compose word embeddings&lt;/code&gt;, 不知道 subword 是否受此启发.&lt;/p&gt;
&lt;p&gt;如题名所示, 本文提出的方法是以 character embedding 来增强 word embedding, 称为 &lt;code&gt;character-enhanced word embedding, CWE&lt;/code&gt;, 基本思想就是, 一个词的语义不仅能从上下文中获得, 还能从构成这个词的汉字中推断出. 比如&lt;strong&gt;智能&lt;/strong&gt;的意思, 也就是&lt;strong&gt;智&lt;/strong&gt;与&lt;strong&gt;能&lt;/strong&gt;的语义组合.&lt;/p&gt;
&lt;p&gt;鉴于中文与英文的差别, 细节上仍是有一些不同. 不同于英文使用 n-gram 来表示 subword, 每个汉字都有一个对应的 character embedding, 此时就以 word embedding +  character embedding 作为 context. 如下图所示, 左边是传统的 CBOW, 右边是本文提出的 CWE. 这样既学到了 word embedding, 又学到了 character embedding.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/cbow_and_cwe.png&quot; alt=&quot;cbow_and_cwe.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;此外, 本文还提出了 Chinese word embedding 的困难, 不妨了解下:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;与词相比, 汉字拥有的意思更多. 一个汉字在不同词中, 可能有不同的语义. 因此, 一个汉字对应一个 character embedding 是不够的;&lt;/li&gt;
&lt;li&gt;不是所有词都能用汉字意思的语义结合来表意. 尤其是音译的词, 比如&lt;strong&gt;巧克力&lt;/strong&gt;可以说&lt;strong&gt;巧&lt;/strong&gt;, &lt;strong&gt;克&lt;/strong&gt;, &lt;strong&gt;力&lt;/strong&gt;没有半毛钱关系, 此时仍然用上述方法反而是有害的.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;文章采取的对策则是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 multiple-prototype character embedding, 即为一个汉字分配多个 character embedding, 以表示它的不同意思;&lt;/li&gt;
&lt;li&gt;简单粗暴地创建 non-compositional words (多个汉字组合成词的过程被称为 compose) 列表, 只使用他们的 word embedding.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;有多种方式可用于组合 word embedding 与 character embedding, 表示为如下公式. 其中 w 表示 word embedding, c 表示词内汉字的 character embedding.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/word_embedding_and_character_embedding.png&quot; alt=&quot;word_embedding_and_character_embedding.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;文章指出简单地将前后项相加和 concatenation 效果差不多, 但速度快很多, 唯一的要求是 word embedding 与 character embedding 维数要相同. &lt;img src=&quot;/img/2018-01-26-word_embeddings_3/word_embedding_add_character_embedding.png&quot; alt=&quot;word_embedding_add_character_embedding.png&quot;&gt;. 此处算式乘以 1/2 的目的在于, 保持 compositional 和 non-compositional words embeddings 的相似.&lt;/p&gt;
&lt;p&gt;此外, 对于 target words, 仅考虑它们的 word embedding.&lt;/p&gt;
&lt;p&gt;由于 CWE 将 word embedding 与 character embedding 相结合的机制, 即使一个 word a 不在另一个 word b 的 context window 中, 单单 character embedding 的改变也会影响 a 的表示.&lt;/p&gt;
&lt;p&gt;针对一字多义的问题, 文章采取了几种方案:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;position-based character embeddings&lt;/code&gt;, 针对 character 在 word 中的位置为其分配向量, 分为 3 种情况: 词头, 词中和词尾. 具体地说, &lt;strong&gt;能力&lt;/strong&gt;和&lt;strong&gt;智能&lt;/strong&gt;中的&lt;strong&gt;能&lt;/strong&gt;用不同的向量表示.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cluster-based character embeddings&lt;/code&gt;, 对 character 所有出现的情况做聚类, 为每个聚类分配一个向量. 文章指出可以对 position-based character embeddings 再做聚类, 即更细分了, 称为 position-cluster-based character.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nonparameters cluster-based character embeddings&lt;/code&gt;, 上面的聚类方法具有固定的类数, 而无参聚类方法能为 characters 分配不同数量的聚类, 聚类数在训练时习得, 更灵活.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;bag-of-tricks-for-efficient-text-classification&quot;&gt;Bag of Tricks for Efficient Text Classification&lt;/h2&gt;
&lt;p&gt;基于 NN 的模型在&lt;code&gt;文本分类, text classification&lt;/code&gt;上能取得非常好的结果, 但训练与测试速度都很慢, 这就限制了它们在超大数据集上的应用; 而&lt;code&gt;线性分类器, linear classifier&lt;/code&gt;在文本分类任务上通常作为 strong baseline, 而且由于简单性, 使用合适的特征能取得 state of the art 的结果, 又能应用到超大规模的数据集.&lt;/p&gt;
&lt;p&gt;鉴于以上想法, 本文提出了&lt;code&gt;fasttext&lt;/code&gt; 模型, 是真快, 又快又好.&lt;/p&gt;
&lt;p&gt;句子分类的简单有效的 baseline 做法是: 以词袋的形式表示句子, 然后训练一个 linear classifier. 然而, 由于特征间与类间没有共享参数, 可能会限制了 linear classifier 的泛化性能. 通常的解决办法是: 1) 将 linear classifer 因式分解成&lt;code&gt;低秩矩阵, low rank matrics&lt;/code&gt;; 2) 使用多层神经网络.&lt;/p&gt;
&lt;p&gt;fasttext model 使用如下架构. 很简单, 首先是一个 weight matrix, 即 look-up table, 将 word 映射为其对应的 word representation, 输入 NN, 以其输出作为 text representation, 最后再将 text representation 输入 linear classifier. 使用 softmax 计算文本所属类别的概率.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/fasttext_architecture_1.png&quot; alt=&quot;fasttext_architecture_1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当类增多时, softmax 的计算开销会变得很大, 因此 fasttext 使用基于&lt;code&gt;哈夫曼编码树, Huffman coding tree&lt;/code&gt;的&lt;code&gt;Hierarchical softmax&lt;/code&gt;, 优点是测试时也有加速效果. (softmax 的更多内容, 请看&lt;a href=&quot;/notes/word_embeddings_1to2/&quot;&gt;这篇&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;BOW 对于 context window 中的&lt;code&gt;word order&lt;/code&gt;是无感的, 而 word order 可能是很有帮助的. fasttext 使用 bag of n-grams 作为额外特征以捕获 local word order 的信息. (显式地增加 order feature 的计算开销非常大)&lt;/p&gt;
&lt;p&gt;最后, fasttext 使用&lt;code&gt;hashing trick&lt;/code&gt;, 获得 n-grams 的映射.&lt;/p&gt;
&lt;h2 id=&quot;advances-in-pre-training-distributed-word-representation&quot;&gt;Advances in Pre-Training Distributed Word Representation&lt;/h2&gt;
&lt;p&gt;如题名所示, 本文就预训练 word vector 提供了一些指导方法.&lt;/p&gt;
&lt;p&gt;在标准的文本中, 词频分布服从 Zipf 分布, 即大多数词仅来自单词表的一个小子集 (有点像二八原则). 此时, 平等地考虑所有单词的 co-occurrence 会使模型对最高频单词过拟合, 对剩下的单词欠拟合. 解决方法就是 word2vec 第二板斧中提到的 subsampling frequent words.&lt;/p&gt;
&lt;p&gt;上文也略微提到, context 中的 word order information 也是有用的, 但是显式地对单词及其位置进行学习, 不实际且易于过拟合. Mnih &amp;amp; Kavukcuolu 2013 介绍了一种简单有效的方法: 学习&lt;code&gt;位置表示, position representation&lt;/code&gt;, 用其为 word vectors 重新加权, 如下. dp 是 context window 中对应 p 位置的向量, u_{t+p} 是 word vector&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2018-01-26-word_embeddings_3/reweight_with_position_representation.png&quot; alt=&quot;reweight_with_position_representation.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Phrase embedding, 前文已经介绍, 不再赘述. 需要指出的是, 即使 phrase embedding 在后续应用中并不用到, 带 phrase embedding 技巧也能提高 word vector 的品质.&lt;/p&gt;
&lt;p&gt;利用 subword information, 前文也介绍到了, 不再赘述.&lt;/p&gt;
&lt;p&gt;文中指出, 对大型语料先进行&lt;code&gt;去重, de-duplicating&lt;/code&gt;能显著提升 word vector 的品质, 很重要; word2vec 训练快, 效果好, 占内存远小于 GloVe, 但是能获得在大语料上预训练好的 GloVe 模型更好.&lt;/p&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>自然语言处理</category></item><item><title>[译记]Word Embeddings</title><link>https://lifelonglove.cn/notes/word_embeddings_0to1/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/word_embeddings_0to1/</guid><description>回顾词向量发展、语言模型目标、Word2Vec 架构与分布式表示的基本直觉。</description><pubDate>Mon, 25 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;在做一个课程作业, 看了一些 Word Embeddings 相关的论文和博客. 以下内容基本上是看博客的时候摘译的, 写得很好, 恨不得全翻了. 论文笔记反正已经欠了一屁股了:(&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;brief-history&quot;&gt;Brief history&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bengio 2003 年的文章发明了 word embeddings 一词, 此时 word embedding 和模型参数一起训练&lt;/li&gt;
&lt;li&gt;Collobert 2008 年的文章 (A unified architecture for natural language processing) 第一次将 word embeddings 作为后续任务的工具.&lt;/li&gt;
&lt;li&gt;Mikolov 2013 年的两篇文章创造性地提出了 word2vec.&lt;/li&gt;
&lt;li&gt;Pennington 2014 年提出了 GloVe 模型, 预示着 word embeddings 成为 NLP 领域的主流.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;models&quot;&gt;Models&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在之前的神经网络学习任务中, word embeddings 只是&lt;code&gt;副产品, by-product&lt;/code&gt;; 后来的 word2vec 等模型则以生成 word embeddings 为直接目标. 两者的主要区别在于:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;计算复杂度 computational complexity&lt;/code&gt;: 用深度神经网络来生成 word embeddings 开销太大; 2013 出现的 word2vec 提出了训练 word embeddings 的简单模型, 计算开销大大减小, 燃. (计算复杂度是 word embeddings 模型的关键之一)&lt;/li&gt;
&lt;li&gt;word2vec 和 GloVe 能将语义关系编码进最终的 word embeddings, 这对于需要这一层关系的后续任务是很有帮助的; 常规的神经网络生成 task-specific embeddings, 不适用于其他任务.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;(word2vec, GloVe 之于 NLP, 就向 VGG 之于 CV)&lt;/li&gt;
&lt;li&gt;word embeddings 模型通常使用 &lt;a href=&quot;https://en.wikipedia.org/wiki/Perplexity&quot;&gt;perplexity&lt;/a&gt; 来评估, 这是一种基于 &lt;code&gt;交叉熵, cross entropy&lt;/code&gt; 的度量方法.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;语言模型, language models, LM&lt;/code&gt;通常在给定前 n-1 个词的情况下预测 w_t 的概率 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/LM_probability.png&quot; alt=&quot;LM_probability&quot;&gt;. 基于&lt;code&gt;链式法则, chain rule&lt;/code&gt; 和&lt;code&gt;马尔可夫假设, Markov assumption&lt;/code&gt;, 通过计算每个词在给定先行词下的概率的积, 能估计整个句子或文档的积: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/LM_sentence_product.png&quot; alt=&quot;LM_sentence_product.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;在基于 n-gram 的 LM 中, 通过单词所在 n-grams 的频率来计算其概率: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/probability_in_ngrams.png&quot; alt=&quot;probability_in_ngrams.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;5-gram + Kneser-Ney smoothing 能得到 smoothed 5-gram models, 这是一个 LM 的 strong baseline.&lt;/li&gt;
&lt;li&gt;在神经网络中, 一般使用 softmax 来计算单词概率: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/softmax_in_NLP.png&quot; alt=&quot;softmax_in_NLP.png&quot;&gt;. h 是输出层前一层的输出向量, v’ 是单词对应的 embedding. 此公式表示的神经语言模型结构如下 (Bengio 2006).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nn_language_model-1.jpg&quot; alt=&quot;nn_language_model-1.jpg&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;推理(测试)时, 每次选择预测概率最大的词(贪婪), 或使用 beam search.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;bengios-classic-neural-language-model&quot;&gt;Bengio’s classic neural language model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Bengio 在 2003 年提出的经典神经语言模型如下所示: 使用了单隐层的前馈神经网络(实线很好理解; 紫色虚线表示通过单词索引乘以共享参数的矩阵 C 得到 word embeddings; 绿色虚线, 还没看过这篇论文的我猜不出). 其目标函数是: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/bengio_language_model_objective.png&quot; alt=&quot;bengio_language_model_objective.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/bengio_language_model.png&quot; alt=&quot;bengio_language_model.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bengio et al 2003 论文指出, softmax layer 是主要瓶颈, softmax 的计算开销和 vocab 大小成比例.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cw-model&quot;&gt;C&amp;amp;W model&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Collobert and Weston (C&amp;amp;W) 在 2008 的时候指出, 在充分大的数据集上训练得到的 word embeddings 习得了&lt;code&gt;句法, syntactic&lt;/code&gt;和&lt;code&gt;语义, semantic&lt;/code&gt;, 能提成后续任务的性能.&lt;/li&gt;
&lt;li&gt;C&amp;amp;W 使用不同的 objective function 避免了 softmax 的计算开销. 其目标是(相比与错误的单词序列, )对于正确的单词序列输出更高的分数. 为实现此目标, 他们提出来&lt;code&gt;pairwise ranking criterion&lt;/code&gt;, 像这样 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/c&amp;amp;w_pairwise_ranking_criterion.png&quot; alt=&quot;c&amp;amp;w_pairwise_ranking_criterion.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;C&amp;amp;W 模型从所有可能的 windows X 中采样正确的 windows x, 再用错误的单词替换掉 x 的中心单词得到 x^(w). 现在来看上面的 objective function, 意在最大化正确单词序列的得分与错误单词序列得分之间的距离, with a margin of 1. (模型如下)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nlp_almost_from_scratch_window_approach.png&quot; alt=&quot;nlp_almost_from_scratch_window_approach.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;word2vec&quot;&gt;Word2Vec&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Technically, word2vec 不属于 Deep learning 的范畴, 它的设计理念是简单, 避免高昂的计算开销, 因此它的架构简单不深, 甚至没有使用 non-linear function.&lt;/li&gt;
&lt;li&gt;Mikolov et al 用两篇文章介绍了 word2vec. 在第一篇文章中, 他们提出了两种新的模型: &lt;code&gt;Continuous bag-of-words, CBOW&lt;/code&gt; 和 &lt;code&gt;Skip-gram&lt;/code&gt;; 第二篇文章使用了其他策略提高了这两个模型的训练速度和精度.&lt;/li&gt;
&lt;li&gt;CBOW 和 Skip-gram 相对于之前模型的优点在于:
&lt;ol&gt;
&lt;li&gt;没有隐层, 避免了隐层带来的开销;&lt;/li&gt;
&lt;li&gt;使得 LM 能学习更多的内容 (得益于 1).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cbow&quot;&gt;CBOW&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;LM 只根据 previous words 来预测下一个单词, 对于只是要生成好的 word embeddings 的模型, 不必受限于 previous words. 类似于 C&amp;amp;W 的做法, CBOW 在 target word 的前后各取 n 个单词, 来预测它. (Bag-of-Word 方法不考虑 bags 的顺序, CBOW 也是如此. 此处的 Continuous 表示模型使用 continuous representations)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/cbow.png&quot; alt=&quot;cbow.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CBOW 的 objective function 和 LM objective 略有不同, 它预测的是中心单词: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/cbow_objective.png&quot; alt=&quot;cbow_objective.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;skip-gram&quot;&gt;Skip-gram&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Skip-gram 的做法和 CBOW 正好相反, 它先确定一个 target word, 然后预测邻近的单词. 其结构如下:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/skip-gram.png&quot; alt=&quot;skip-gram.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Skip-gram 的 objective function 很自然地表示为所有邻近单词的概率和: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/skip-gram_objective.png&quot; alt=&quot;skip-gram_objective.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;给定 target word w_t, w_{t+j} 的条件概率计算与上类似: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/skip-gram_conditional_probability.png&quot; alt=&quot;skip-gram_conditional_probability.png&quot;&gt;. 与上面公式使用隐层状态 h 不同的是, skip-gram 模型没有隐层, 因此直接用输入的 word embedding. 因此, 此处计算的是 input word embedding 与 output word embedding 的内积&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;glove&quot;&gt;GloVe&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;简单地说, GloVe 通过显式地将&lt;code&gt;意义, meaning&lt;/code&gt; encode 成 embeddings space 中 vector 的&lt;code&gt;偏移量, offset&lt;/code&gt;. (&lt;code&gt;Skip-gram with negative sampling&lt;/code&gt;, SGNS 隐式地做了这项工作).&lt;/li&gt;
&lt;li&gt;GloVe 作者表明&lt;code&gt;两个单词同时出现概率的比例, the ratio of the co-occurrence probabilities of 2 words&lt;/code&gt;包含了某种信息, glove 旨在将这种信息 encode 为 &lt;code&gt;向量的差, vector differences&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;为此, 他们提出了一个&lt;code&gt;加权最小二乘, weighted least squares&lt;/code&gt; objective function J, 直接最小化 A. &lt;strong&gt;两个单词的 vectors 的点积&lt;/strong&gt;与 B. &lt;strong&gt;它们同时出现的次数的对数&lt;/strong&gt; 的差:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/glove_objective.png&quot; alt=&quot;glove_objective.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;式中, w_i, b_i 是单词 i 的词向量和 bias, wtilde_j, b_j 是单词 j 的 convtext word vector 和 bias. X_ij 是 i 出现在 j 的context 中的次数. f 是一个 weighting function, 它为很少同时出现和频繁同时出现的情况赋一个较低的权值.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;co-occurrence&lt;/code&gt; 次数可直接 encode 进 word-context co-occurence matrix. GloVe 使用该矩阵, 而不是整个 corpus 作为输入.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;word-embeddings-vs-distributional-semantics-models&quot;&gt;Word embeddings vs. distributional semantics models&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Distributional Semantics Models, DSM&lt;/code&gt; 通过操作 co-occurrence matrixs 来统计单词间的 co-occurrence. 而 neural word embedding models 尝试着预测单词&lt;/li&gt;
&lt;li&gt;Levy et al (2015) 将 GloVe 被视作预测模型, 同时它又利用了 word-context co-occurrence  matrix, 有点像传统方法, 如 PCA, LSA. Levy et al还演示了 word2vec 隐式地 &lt;code&gt;分解, factorize&lt;/code&gt;了 word-contxt PMI matrix.&lt;/li&gt;
&lt;li&gt;所以, DSMs 和 word embedding models 表面上看上去使用了不同的算法来学习 word representations, 本质上却都是基于数据统计量的, 即单词间的 co-occurrence counts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;models-1&quot;&gt;Models&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Pointwise Mutual Information, PMI&lt;/code&gt;是一种度量两个单词间&lt;code&gt;关联强度, strength of association&lt;/code&gt; 的常用方法, 定义为两个单词的&lt;code&gt;联合概率, jointly probability&lt;/code&gt;与&lt;code&gt;边缘概率, marginal probabilities&lt;/code&gt;点积的&lt;code&gt;对数比, log ratio&lt;/code&gt;: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/PMI_equation.png&quot; alt=&quot;PMI_equation.png&quot;&gt;. 当两个单词从来没有同时出现过, P(w, c)=0, PMI(w, c)=log0=-∞. 实际应用时, PMI 常被替换为 &lt;code&gt;Positive PMI, PPMI&lt;/code&gt;, PPMI(w, c)=max(PMI(w, c), 0)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;奇异值分解, Singular Value Decomposition, SVD&lt;/code&gt;是降维的最常用方法之一, 通过&lt;code&gt;Latent Semantic Analysis, LSA&lt;/code&gt;被引入 NLP. SVD 将 word-context co-occurrence matrix 写成三个矩阵的积: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SVD_word_context_co-occurrence_matrix.png&quot; alt=&quot;SVD_word_context_co-occurrence_matrix.png&quot;&gt;. U 和 V 是&lt;code&gt;正交矩阵, orthonormal matrix&lt;/code&gt;, \Sigma 是特征值按降序排列的&lt;code&gt;对角矩阵, diagonal matrix&lt;/code&gt;. 实际应用中, SVD 常被用于因子化 PPMI 得到的矩阵. 一般而言, \Sigma 中只有 top d 个元素会被保留, 从而得到了 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SVD_W_topd.png&quot; alt=&quot;SVD_W_topd.png&quot;&gt; 和 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SVD_C_topd.png&quot; alt=&quot;SVD_C_topd.png&quot;&gt;, 分别用作 word representation 和 context representation.&lt;/li&gt;
&lt;li&gt;Word2vec 介绍了 3 种&lt;code&gt;预处理, pre-processing&lt;/code&gt; corous 的方法, 同样可用于 DSMs
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Dynamic context window&lt;/code&gt;. 在传统 DSMs 中, context window 是不带权的, 固定大小的. 考虑到更接近的单词一般具有更重要的意义, SGNS 和 GloVe 将更多的权值赋给了更近的单词. SGNS 的 windows size 不固定, 训练时, 在 1 和预设最大 window size 中&lt;code&gt;均匀采样, sample uniformly&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Subsampling frequent words&lt;/code&gt;. SGNS 以概率 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/sgns_subsampling_frequent_words.png&quot; alt=&quot;sgns_subsampling_frequent_words.png&quot;&gt;(t 为阈值, f 为词频) 剔除单词来 dilutes (稀疏的意思) 高频单词. subsampling 在创建 windows 之前执行, 因此 SGNS 实际使用的 context windows 要大于 context window size 指示的大小 (这就是因为前面说的高频词汇被挖掉了, 比如 The dog is chasing a ball, context window size=2时, 在挖掉高频词汇 the, is, a 后, ball 就在 dog 的 context 中了).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Deleting rare words&lt;/code&gt;. 在 SGNS 的预处理中, 罕用单词在创建 context windows 前被删除, 这更加增大了 context windows 的实际大小. (Levy et al (2015) 发现这并不能带来显著的性能提升)(context window 增大的例子与上类似)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;PMI 被证明是度量单词间&lt;code&gt;关联性, association&lt;/code&gt;的有效&lt;code&gt;度量, metric&lt;/code&gt;. Levy &amp;amp; Goldberg (2014) 证明了 SGNS 隐式分解了 PMI matrix, 由此, 以下源于此的变种可以引入 PMI 中:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Shifted PMI&lt;/code&gt;. 在 SGNS 中, negative samples 数 k 会影响 PMI matrix 的&lt;code&gt;漂移, shift&lt;/code&gt;, 即参数 k 会将 PMI 值漂移 log k. 将这一特性应用于 PMI, 就得到了 &lt;code&gt;Shifted PPMI, SPPMI&lt;/code&gt;: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SPPMI_equation.png&quot; alt=&quot;SPPMI_equation.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Context distribution smoothing&lt;/code&gt;. 在 SGNS 中, negative samples 从 &lt;code&gt;smoothed unigram distribution&lt;/code&gt; 中采样. 所谓 smoothed unigram distribution 就是 unigram distribution 的 \alpha 次幂, 通常取 \alpha=3/4. 这导致高频单词被采样的次数比少于它们的词频. 将这一特性应用于 PMI, 可以通过将 context words 的频率修改为原先的 \alpha 次幂倍: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/context_distribution_smoothing_to_PMI_1.png&quot; alt=&quot;context_distribution_smoothing_to_PMI_1.png&quot;&gt;, 其中 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/context_distribution_smoothing_to_PMI_2.png&quot; alt=&quot;context_distribution_smoothing_to_PMI_2.png&quot;&gt;, f 是词频函数.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;3 种修改 word vectors 的&lt;code&gt;后处理, post-processing&lt;/code&gt;方法:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Adding context vectors&lt;/code&gt;. GloVe 的作者提出了将 word vectors 和 context vectors 相加作为 output vectors 的方法, 即 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/adding_context_vectors.png&quot; alt=&quot;adding_context_vectors.png&quot;&gt;. 这种做法将&lt;code&gt;一阶相似项, first-order similarity terms&lt;/code&gt;相加起来. 不能应用于 PMI, 因为 PMI 生成的向量是&lt;code&gt;稀疏的, sparse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Eigenvalue weighting&lt;/code&gt;. 前文提到, SVD 能生成 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SVD_W_topd.png&quot; alt=&quot;SVD_W_topd.png&quot;&gt; 和 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/SVD_C_topd.png&quot; alt=&quot;SVD_C_topd.png&quot;&gt;. 其中 C 阵是正交的, W 阵不是. 可以用额外的参数 p 来为特征值矩阵加权, 调整 W 阵: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/weighted_SVD_W_topd.png&quot; alt=&quot;weighted_SVD_W_topd.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Vector normalisation&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Levy et al. 发现 SVD 而不是任何 word embeddings 算法在相似性任务上取得最好的效果, SGNS 在类比任务上效果最好. 他们同时指出:
&lt;ol&gt;
&lt;li&gt;hyperparameters 的设置通常比算法的选择更重要. 没有任何算法总是表现地比其他方法更优.&lt;/li&gt;
&lt;li&gt;在更大的 corpus 上训练, 对于一些任务有帮助. 但超过 1/2 的情况下, 调整超参数的效果带来的提升更大.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;只要 hyperparameters 调得好, 没有哪种方法总是优于其他方法; SGNS 在各种任务中都优于 GloVe; CBOW 在任何任务中都不如 SGNS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;recommendations&quot;&gt;Recommendations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不要对 SPPMI 使用 SVD;&lt;/li&gt;
&lt;li&gt;不要使用“自以为是”地使用 SVD, 比如不使用 eigenvector weighting (不使用 eigenvalue weighting (p=0.5), 性能下降了 15 %)&lt;/li&gt;
&lt;li&gt;对 PPMI 和 SVD 使用 short context (比如 window size of 2)&lt;/li&gt;
&lt;li&gt;对 SGNS 使用多 negative samples&lt;/li&gt;
&lt;li&gt;对所有方法都使用 context distribution smoothing (用 \alpha=0.75 对 unigram distribution 求幂)&lt;/li&gt;
&lt;li&gt;使用 SGNS 作为 baseline (SGNS, robust, fast and cheap to train)&lt;/li&gt;
&lt;li&gt;试着向 SGNS 和 GloVe 加入 context vectors&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ruder.io/word-embeddings-1/index.html&quot;&gt;On word embeddings - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ruder.io/secret-word2vec/index.html&quot;&gt;On word embeddings - Part 3: The secret ingredients of word2vec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>自然语言处理</category></item><item><title>[译记]Word Embeddings 2</title><link>https://lifelonglove.cn/notes/word_embeddings_1to2/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/word_embeddings_1to2/</guid><description>比较分层 Softmax、负采样等方法如何降低大词表输出层的计算成本。</description><pubDate>Mon, 25 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文与上一篇的笔记师出同源, 主要讲 Softmax, 单独拎出来.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;softmax 的高计算开销在于需要计算 hidden state &lt;em&gt;&lt;strong&gt;h&lt;/strong&gt;&lt;/em&gt; 和所有单词的 output word embeddings 的内积, 求和作为分母.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;hierarchical-softmax&quot;&gt;Hierarchical Softmax&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Hierarchical softmax, 分层 Softmax, H-Softmax&lt;/code&gt; 受&lt;code&gt;二叉树, binary tree&lt;/code&gt;的启发而提出. 它用一个 hierarchical layer 替换 flat softmax layer, 每个单词都是一个&lt;code&gt;叶子节点, leaves&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/hierarchical_softmax_example.png&quot; alt=&quot;hierarchical_softmax_example.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H-softmax 将一个单词概率的计算 decompose 成概率计算的序列, 按照到叶子所在叶节点的路径计算其概率即可, 避免了对所有单词概率做归一化的开销. H-Softmax 能将 word prediction 任务加速至少 50 倍!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;平衡二叉树, balanced binary tree&lt;/code&gt; 的深度为 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/log2_V.png&quot; alt=&quot;log2_V.png&quot;&gt;. 因此最多只需要估计 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/log2_V.png&quot; alt=&quot;log2_V.png&quot;&gt; 步计算, 就能得到单词的最终概率. 并且得到的概率是归一化后的, 因为二叉树所有叶节点的概率和为 1.&lt;/li&gt;
&lt;li&gt;每一对子节点都将父节点的概率一分为二. 因此, 遍历树时, 需要计算&lt;code&gt;左枝, left branch&lt;/code&gt;和&lt;code&gt;右枝 right branch&lt;/code&gt;的概率. 比如在节点 n 上选择右枝的概率为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/h-softmax_probability_turn_right.png&quot; alt=&quot;h-softmax_probability_turn_right.png&quot;&gt;. (下图是 h-softmax 的一个例子, sigm 即 sigmoid 函数)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/hierarchical_softmax.png&quot; alt=&quot;hierarchical_softmax.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 H-softmax 中, 不再需要 output embeddings, 取而代之的是, 每个节点都有 embeddings, 各不相同. 因此实际上 H-softmax 和 softmax 拥有差不多的参数量.&lt;/li&gt;
&lt;li&gt;树结构对于 h-softmax 很重要, 比如为相似的概率分配相似的路径. 基于此, Morin &amp;amp; Bengio 在 WordNet 中使用了&lt;code&gt;同义词集, synsets&lt;/code&gt; 作为树的&lt;code&gt;聚类 cluster&lt;/code&gt;, 但效果不如 softmax; Minh &amp;amp; Hinton 使用聚类算法来学习树结构, 通过递归地将单词划分仅两个聚类, 实现了和 softmax 同样的性能&lt;/li&gt;
&lt;li&gt;Notably, 只能在训练阶段获得加速, 因为此时事先知道了 target word, 也就知道了其在树中的路径. 测试时, 要计算最有可能的单词, 仍需要计算所有单词的概率.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;information-content-of-word&quot;&gt;information content of word&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;一个单词的&lt;code&gt;信息量, information content&lt;/code&gt;是它的概率的&lt;code&gt;负对数, negative logarithm&lt;/code&gt;: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/word_information_content.png&quot; alt=&quot;word_information_content.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;而 Corpus 中所有单词的熵 H, 就是所有单词的 information content 的期望, 即: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/entropy_of_corpus.png&quot; alt=&quot;entropy_of_corpus.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;另外, 也可以用数据的平均的比特长度作为熵. 在 balanced bianry tree 中, 对于每个单词都一视同仁, 每个单词的概率都相同, 因此单词的熵等于其信息量. 套用以上公式, vocab_size=10000 的 balanced binary tree的平均单词熵为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/entropy_10000_bbt.png&quot; alt=&quot;entropy_10000_bbt.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;利用好树结构, 可以使用&lt;code&gt;哈夫曼树 Huffman tree&lt;/code&gt;代替平衡二叉树, 为信息量更少的单词赋更短的路径, 能取得更好的效果. 一些单词会以更大的概率出现, 可以认为它们携带的信息量更少. (作为常识: 太阳东升西落, 携带的信息量是很少的) 此时, 同样的 10000 个单词的平均熵是 9.16, 能带来 31 % 的加速. (Morin &amp;amp; Bengio 论文中的实例, 其他训练集的值会不同, 加速效果不知道怎么算的)&lt;/li&gt;
&lt;li&gt;上文提到 LM 通常用 &lt;code&gt;perplexity&lt;/code&gt; 来度量, 它可以表示为 2^H. Jozefowicz et al 2016 提出的 LM 取得了 state-of-the-art perplexity 24.2, 这意味着一个英语单词平均只需要 4.6 bits. 和香农爸爸实验得出的结论, 英语字母的信息率的下界在 0.6 到 1.3 bits之间, 单词的平均长度在 4.6 bits 很接近!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;differentiated-softmax&quot;&gt;Differentiated Softmax&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chen et al 提出了 &lt;code&gt;Differentiated softmax, D-softmax&lt;/code&gt;. 它的动机是, 不是所有单词都需要相同数量的参数, 频繁使用的单词需要更多的参数, 而极少使用的单词可能只需要少量参数去 fit.&lt;/li&gt;
&lt;li&gt;softmax 的矩阵大小为 d x vocab_size (d 为 embeddings 的维度). 鉴于以上思想, D-softmax 使用了&lt;code&gt;稀疏矩阵, sparse matrix&lt;/code&gt;, 根据词频, 将 embeddings 划分进不同的 blocks, 每个 blocks 中的 embeddings 都具有相同的维度. blocks 数和 embddings size 是可调节的超参数.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/differentiated_softmax_1.png&quot; alt=&quot;differentiated_softmax_1.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上图是 D-softmax 的稀疏矩阵的一个示例 (高度为 vocab size, 宽度为embedding size). block A 是常用词的 embedding block, 因为它分配到的参数更多. 图中非阴影部分全部置 0, 不学习这些位置的参数.&lt;/li&gt;
&lt;li&gt;应用 D-softmax 时, 输出层的前一层 &lt;em&gt;&lt;strong&gt;h&lt;/strong&gt;&lt;/em&gt; 用于&lt;code&gt;特征级联, feature concatenation&lt;/code&gt;, 上图右, &lt;em&gt;&lt;strong&gt;h&lt;/strong&gt;&lt;/em&gt; 的高度是 dA+dB+dC, 分别对应各 blocks 的维度. 因此计算 output embeddings 时, 每个 partition 仅与其对应的 h 的部分求内积.&lt;/li&gt;
&lt;li&gt;D-softmax 在测试时同样适用, 也能起到加速效果. 论文指出, D-softmax 在测试阶段是最快的方法, 并且精度也属于第一梯队.&lt;/li&gt;
&lt;li&gt;D-softmax 的缺点是, 词频最低的那部分单词的参数太少, 对这些单词的建模效果很差.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cnn-softmax&quot;&gt;CNN-softmax&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Kim et al. 提出了通过 character-level CNN 生成 input word embeddings 的方法. 受此启发, Jozefowicz et al 2016 将 character-level CNN 应用于 output word embeddings, 称为 &lt;code&gt;CNN-softmax&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/cnn-softmax_1.png&quot; alt=&quot;cnn-softmax_1.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cnn-softmax 也要做 normalization, 但通过 CNN, 模型的参数数量大大减少了. softmax 需要保存 d x vocab_size 大小的 embedding matrix, 现在只需要 CNN 的参数量. (对比 FC 与 CONV)&lt;/li&gt;
&lt;li&gt;测试时, output word embeddings 可以预先计算出来, 因此不存在性能损失&lt;/li&gt;
&lt;li&gt;由于在&lt;code&gt;连续空间, continuous space&lt;/code&gt;表示字符, 导致模型趋向于学习更平滑的 character to word 的 mapping, 后果是 character-based models 很难区分拼写相近但具有不同意义的单词. 为解决该问题, 作者增加了一个 correction factor, learned per word, 显著减小了 softmax 和 cnn-softmax 的性能差. correction factor 的维度是一个超参数, 在模型大小和性能间有一个 &lt;code&gt;trade-off, 平衡&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Jozefowicz et al 还指出可以用 character-level LSTM 来代替 character-level CNN. 他们没有成功地应用 character-level LSTM, Ling et al 在 &lt;code&gt;机器翻译, Machine Translation&lt;/code&gt; 任务中使用了类似层, 取得了很好的结果.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;以上 H-softmax, D-sofmax, CNN-softmax 都属于&lt;code&gt;Softmax-based approaches&lt;/code&gt;. 以下将介绍&lt;code&gt;Sampling-based approaches&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Sampling-based approaches 完全摈弃掉了 softmax layer, 它们通过近似 softmax 的分母来实现 normalization. 这只对于训练阶段有用, 推理时仍需要 softmax 来计算 normalized probability.&lt;/li&gt;
&lt;li&gt;训练时, 其目标是 minimize the cross entropy, 亦即输出的 softmax 的负对数. &lt;a href=&quot;https://cs231n.github.io/linear-classify/#softmax-classifier&quot;&gt;Karpathy 大佬关于 softmax 与 cross entropy 间的联系的解释&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;如上所述, 模型的 objective function 是这样的: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/j_theta_1.png&quot; alt=&quot;j_theta_1.png&quot;&gt;. 对其进行一系列的变形:
&lt;ol&gt;
&lt;li&gt;将负对数改写成和的形式: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/j_theta_2.png&quot; alt=&quot;j_theta_2.png&quot;&gt;;&lt;/li&gt;
&lt;li&gt;将点积替换为 -\epsilon(w): &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/j_theta_3.png&quot; alt=&quot;j_theta_3.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;为了 BP, 计算梯度: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_1.png&quot; alt=&quot;gradient_of_j_theta_1.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;根据 log(x) 的导数是 1/x (如无特殊说明, log 默认为自然对数), 使用链式法则再变形: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_2.png&quot; alt=&quot;gradient_of_j_theta_2.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;将第二项的梯度移到求和内部: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_3.png&quot; alt=&quot;gradient_of_j_theta_3.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;根据 exp(x) 的导数还是 exp(x), 再次使用链式法则, 变形得到: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_4.png&quot; alt=&quot;gradient_of_j_theta_4.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;改写上式, 得到: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_5.png&quot; alt=&quot;gradient_of_j_theta_5.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;观察到第 7 步得到的式子中有一个 softmax 形式的项, 用 P(wi) 替换之, 得到: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_6.png&quot; alt=&quot;gradient_of_j_theta_6.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;将第二项中的负号提到求和之前, 就得到了: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/gradient_of_j_theta_7.png&quot; alt=&quot;gradient_of_j_theta_7.png&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Bengio &amp;amp; Senécal (2003) 表明, 该梯度由两部分组成: 第一项是&lt;code&gt;positive reinforcement&lt;/code&gt; for the target word, 第二项是其他所有单词的 &lt;code&gt;negative reinforcement&lt;/code&gt;, P(wi) 表示依据他们各自的概率加权. negative reinforcement 刚好也是 wi 服从 P(wi) 分布时, \epsilon(wi) 梯度的期望: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/expection_of_the_gradient_of_epsilon_for_all_words.png&quot; alt=&quot;expection_of_the_gradient_of_epsilon_for_all_words.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;大多数 samping-based 方法都通过近似 negative reinforcement 来简化计算.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;importance-sampling&quot;&gt;Importance Sampling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;通过&lt;code&gt;蒙特卡罗, Monte Carlo, MC&lt;/code&gt;方法可近似任意概率分布的期望值, 它就是从概率分布中随机采样, 求均值作为期望值. (依据是大数定理)&lt;/li&gt;
&lt;li&gt;使用上述方法, 需要知道概率分布 P 才能从中进行采样, 而计算 P 正是我们所不愿的. 替代的方法是, 使用找到其他分布 Q (称为&lt;code&gt;proposal distribution&lt;/code&gt;), it’s cheap to sample from Q, 就以 Q 作为 MC 采样的基础. 在 LM 中, 可以简单地使用训练集的 &lt;code&gt;unigram&lt;/code&gt; 分布作为 Q.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;古典, classical``重要性采样, Importance Sampling, IS&lt;/code&gt;的核心内容就是: 通过从 proposal distribution Q 中进行 MC 采样, 来近似 target distribution P. 这仍然需要计算每个单词的概率 P(w). 为避免这样的计算, Bengio &amp;amp; Senécal (2003) 使用了 &lt;code&gt;biased estimator&lt;/code&gt;. 当 P(w) 由乘积得时, 可以使用 biased estimator.&lt;/li&gt;
&lt;li&gt;使用 biased estimator, 将原来 negative reinforcement 的中权重 P(wi) 替换为 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/biased_IS_factor.png&quot; alt=&quot;biased_IS_factor.png&quot;&gt;, 这样就利用了 Q. 其中 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/biased_IS_factor_item_1.png&quot; alt=&quot;biased_IS_factor_item_1.png&quot;&gt;, R 则等于 r(wj) 的和.&lt;/li&gt;
&lt;li&gt;使用 biased_IS, 仍然需要计算 softmax 的分子, 但分母的计算使用了更易计算的 Q.&lt;/li&gt;
&lt;li&gt;基于采样估计概率分布的一个问题是, 近似的准确度与样本数量相关. 样本太少时, P 会偏离 Q, 进而导致模型发散. Bengio &amp;amp; Senécal 提出了一个估计有效样本数量的方法.&lt;/li&gt;
&lt;li&gt;基于 IS 的方法相比 softmax, 加速了 19 倍.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;adaptive-importance-sampling&quot;&gt;Adaptive Importance Sampling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Bengio &amp;amp; Senécal (2008) 表示, 为了防止训练后期, unigram distribution Q 偏离 P, 可将 Q 替换为更复杂的分布. 并提出了 n-gram distribution, 在训练过程中能适应性地做出改变, 从而紧紧拟合 P. 他们使用一个&lt;code&gt;混合函数, mixture function&lt;/code&gt;来在 bigram distribution 和 unigram distribution 中做 interpolation, 最小化 Q 和 P 间的 KL 散度. (不知道论文中是怎么处理这里的 P, 感觉是病态的方法) 实验表明, AIS 加速了 100 倍.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;target-sampling&quot;&gt;Target Sampling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Jean et al (2015) 在显存受限的情况下, 提出了限制 target words 采样数量的方法 (还是采用 AIS): 对训练集进行划分, 每份包含固定数量的样本单词. 这也意味着对 Q 的划分, 对应子集的所有单词具有相等的非等概率, 其他所有单词概率为 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;noise-contrastive-estimation&quot;&gt;Noise Contrastive Estimation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Mnih &amp;amp; Teh 将&lt;code&gt;Noise Contrastive Estimation, NCE&lt;/code&gt;作为更稳定的采样方法 (IS 可能会发散). NCE 不直接估计单词概率, 它使用一个同样旨在最大化正确单词概率的&lt;code&gt;辅助损失, auxiliary loss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;NCE 使用与 C&amp;amp;W (2008) 的 pairwise-ranking 类似的方法: 训练模型区分 target word 与 noise. 任务退化为二分类问题: 对于每个单词 wi, 给定其 context, 即 n previous words, 同时为其生成 k 个 noise samples. context 中的所有单词赋标签 1, noise samples 标记为 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/negative_sampling.png&quot; alt=&quot;negative_sampling.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;此时的 ojbective 为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nce_objective_1.png&quot; alt=&quot;nce_objective_1.png&quot;&gt;. 为避免对 noise samples 期望的计算, 使用 MC 采样的均值作为期望的近似, 于是 objective 变形为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nce_objective_2.png&quot; alt=&quot;nce_objective_2.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;为每一个单词都采样 k 个 noise samples, 因此采样概率可以表示为 P 和 Q 的混合形式: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/mixture_P_and_Q.png&quot; alt=&quot;mixture_P_and_Q.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;用模型概率 P 来表示真实概率 P_train, 那么从混合模型中采样, 样本是正确单词的概率为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nce_true_conditional_probability.png&quot; alt=&quot;nce_true_conditional_probability.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;上式中, 条件概率 P(w|c) 正是 softmax 的结果. 为了避免计算, 将分母改写为关于 c 的函数 Z(c) (因为分母只取决于 h). 在 NCE 中, 可以将 Z(c) 看作模型可以学习的参数. Mnih &amp;amp; Teh (2012) 和 Vaswani et al 将其固定为 1. 根据他们的说法, 这不影响性能. 该方法不仅避免了计算, 还减少了模型参数. Zoph et al 证实了以上做法的可取性, 实验习得的 Z(c) 接近于 1, 并且方差很小.&lt;/li&gt;
&lt;li&gt;在 Z(c)=1 的情况下, &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/probability_of_word_given_context.png&quot; alt=&quot;probability_of_word_given_context.png&quot;&gt;. 代入 P(y=1|w, c), 得: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nce_true_conditional_probability_2.png&quot; alt=&quot;nce_true_conditional_probability_2.png&quot;&gt;. 最后再代入 objective function, 得: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/nce_objective_3.png&quot; alt=&quot;nce_objective_3.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;NCE 有很好的理论保证: 增大 noise samples 数时, NCE 的导数趋向于 softmax 的梯度. Mnih &amp;amp; Teh 表示 25 个 noise samples 足以达到 softmax 的性能, 但是有 45 倍的加速.&lt;/li&gt;
&lt;li&gt;NCE 的一个缺陷是, 每次训练都需要采样不同的 noise samples, noise samples 及其梯度无法保存在 dense matrixs 中, 就无法利用 dense matrix multiplication 的来加速计算.&lt;/li&gt;
&lt;li&gt;Jozefowicz et al (2016) 和 Zoph et al (2016) 各自独立提出了在一个 mini-batch 中共享 noise samples 的方法, 部分解决了以上问题.&lt;/li&gt;
&lt;li&gt;Jozefowicz et al (2016) 表明 NCE 与 IS 间有强联系. NCE 使用二分类问题, IS 可以描述成类似的形式: IS 使用 softmax 和 cross entropy function 来优化&lt;code&gt;多分类问题, multi-class classification&lt;/code&gt;. 当 loss 会导致训练数据与 noise samples 间的&lt;code&gt;tied updates&lt;/code&gt;(不知道是什么, 望解惑), 由于 IS 执行多分类任务, 它比 NCE 更适合 LM.&lt;/li&gt;
&lt;li&gt;Jozefowicz et al (2016) 使用 IS 在 1B Word benchmard 上取得了 state-of-the-art 的性能.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;negative-sampling&quot;&gt;Negative Sampling&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Negative sampling, NEG&lt;/code&gt;可以看作 NCE 的近似, 它更简化了 NCE. NCE 随着 noise samples 数的增多, 其目标会接近 softmax. 但 NEG 没有该理论保证, 因为它的目标是学习更好的 word representation 而不是 low perplexity.&lt;/li&gt;
&lt;li&gt;NCE 与 NEG 的关键区别在于, NEG 以尽可能简化计算为目标近似 P(y=1|w, c), 因此它将 kQ(w) 设为 1, 因此 &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/neg_true_conditional_probability.png&quot; alt=&quot;neg_true_conditional_probability.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;令 kQ(w)=1 的依据是, P(y=1|w, c) 可以转换成 sigmoid function 的形式. 代入 objective function, 经过一系列变换, 可以得到: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/neg_objective.png&quot; alt=&quot;neg_objective.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;当 noise samples 数等于 vocab size, Q 是 uniform distribution 时, NEG 等价于 NEC. 在其他情况下, NEG 仅仅近似 NCE, 它不直接优化正确单词的 likelihood.&lt;/li&gt;
&lt;li&gt;NEG 对于学习 word embeddings 可能更有帮助, 但它缺少&lt;code&gt;渐近一致性保证, asymptotic consistency guarantes&lt;/code&gt;, 使得它不适合 LM.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;self-normalization&quot;&gt;Self-Normalization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Self-normalization 并不是 sampling-based 方法. 如前所述, 将 NCE 的Z(c) 设为 1, 模型能 self-normalization.&lt;/li&gt;
&lt;li&gt;对于 loss function: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/negative_log-likelihood.png&quot; alt=&quot;negative_log-likelihood.png&quot;&gt;, 当限制 Z(c)=1, 或 log(Z(c))=0, 可以避免计算 Z(c) 中的 normalization.&lt;/li&gt;
&lt;li&gt;基于以上观察, Devlin et al (2014) 提出向 loss function 中加入一个&lt;code&gt;均方误差惩罚项, squared error penalty term&lt;/code&gt;, 将鼓励模型保持 log(Z(c)) 尽可能接近 0. &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/negative_log-likelihood_with_squared_error_penalty.png&quot; alt=&quot;negative_log-likelihood_with_squared_error_penalty&quot;&gt;&lt;/li&gt;
&lt;li&gt;Devlin et al (2014) 实现了一个 NM symtem, 在 decode 阶段, 他们就设置 softmax 的分母为 1, 只使用 softmax 的分子和惩罚项计算 P(w|c), 此时损失函数为: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/negative_log-likelihood_with_penalty_term.png&quot; alt=&quot;negative_log-likelihood_with_penalty_term.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;根据 Devlin 的论文, 相比 non-self-normalization LM, self-normalizaton 在 BLEU 上 实现了 15 倍的加速, 只有很小的降分.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;infrequent-normalization&quot;&gt;Infrequent Normalization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Andreas &amp;amp; Klein 建议仅仅 normalize 训练样本的一部分就足够了, 仍然能获得近似 self-normalization 的效果, 于是提出了 &lt;code&gt;Infrequent Normalization, IN&lt;/code&gt;, 它 down-samples the penalty term (这里的 down-sample 不同于 ConvNet 的 subsample), 使之变成一个 sampling-based 方法&lt;/li&gt;
&lt;li&gt;IN 的 loss 被改写成如下形式: &lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/IN_loss.png&quot; alt=&quot;IN_loss.png&quot;&gt;. 只为子集 C 计算 normalization. gamma 控制子集 C 的大小.&lt;/li&gt;
&lt;li&gt;Andreas &amp;amp; Klein 表示 IN 集合了 NCE 和 self-normalization 的优势, 它不必为所有训练样本计算 normalizatoin, 又可控模型准确度和 normalization 近似度间的平衡. 在他们的实验中, 仅对 1/10 的训练样本进行 normalization, 取得了 10 倍的加速, 但没有明显的性能下降.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上各方法的比较, 如下表所示. (性能比较是基于 LM 的)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-25-word_embeddings_0to1/comparison_of_approaches_of_approximate_softmax_for_LM.png&quot; alt=&quot;comparison_of_approaches_of_approximate_softmax_for_LM.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://ruder.io/word-embeddings-softmax/index.html&quot;&gt;On word embeddings - Part 2: Approximating the Softmax&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>自然语言处理</category></item><item><title>论文笔记3</title><link>https://lifelonglove.cn/notes/papernotes03/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/papernotes03/</guid><description>比较 AAE、ALI 与 BiGAN 的对抗推断目标、网络结构和表示学习方式。</description><pubDate>Sun, 17 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;这是上周的笔记, 笔记本摔了, 送去修了整一个星期. 这周的笔记, 以后慢慢补吧. :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/pdf/1511.05644.pdf&quot;&gt;Adversarial Autoencoders&lt;/a&gt; 结合 GAN 与 VAE, 提出了&lt;code&gt;对抗自编码器 AAE&lt;/code&gt;, 执行&lt;code&gt;变分推断 Variational Inference&lt;/code&gt; 来匹配自编码器的潜变量的后验分布与任意的潜变量先验分布. 该匹配过程保证了从先验空间的任意部分都能生成有意义的结果.&lt;/p&gt;
&lt;p&gt;AAE 有两个训练目标: 一个是重构损失, 一个是对抗损失. 后者用于匹配自编码器潜变量的后验分布与任意的潜变量先验分布.&lt;/p&gt;
&lt;p&gt;最简单的 AAE 如下图所示. 上面一行就是自编码器结构, 将 1 编码为 z, 再解码还原为 1; 红线圈出来的是自编码器的 encoder, 也是 GAN 的 G, 右下角那一块则是 D. 不同于普通 GAN 以真实或生成的自然图片作为 D 的输入, AAE 中 D 的输入是潜变量 z, 服从自编码器的后验分布 q(z) (生成的)或先验分布 p(z) (此处作为真实数据).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture1.png&quot; alt=&quot;aae_architecture1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;本文提出了以 GAN 框架作为概率自编码器的变分推断算法, 并调研了监督, 半监督, 无监督的许多应用方式 (下文有较为详细的介绍, 不甚明了的地方请看原文)&lt;/p&gt;
&lt;p&gt;GAN 缺少有效的推断机制, ALI 与 BiGAN, 通过向 GAN 框架引入一个从 x (比如自然图片) 到 z 的推断网络来引入推断机制. D 的输入为 (x, z_hat) 或 (x_tilde, z), 以此来学习联合概率分布 q(x, z_hat) 和 p(x_tilde, z). 结构如下所示:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/ali_architecture.png&quot; alt=&quot;ali_architecture.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;因此, ALI 与 BiGAN 的博弈在 G(生成网络), I(推断网络) 与 D(判别网络)中展开. (G 与 I 联合“欺骗” D.)&lt;/p&gt;
&lt;h2 id=&quot;adversarial-autoencoders&quot;&gt;Adversarial Autoencoders&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1511.05644.pdf&quot;&gt;https://arxiv.org/pdf/1511.05644.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;近年来, 深度生成模型的训练主要都基于 &lt;code&gt;MCMC 马尔可夫链蒙特卡罗(搜索)&lt;/code&gt; 算法. 使用 MCMC 方法的一个缺点是: 随着训练的进行, 其计算得到的对数似然的梯度变得越来越不精确, 因为从马尔可夫链采样的样本无法足够快地混合入模型.&lt;/p&gt;
&lt;p&gt;后来的 &lt;code&gt;变分自编码器 VAE&lt;/code&gt; 则是利用&lt;code&gt;识别网络 recognition network&lt;/code&gt;来预测潜变量的后验分布.&lt;/p&gt;
&lt;p&gt;AAE 有两个训练目标: 一个是重构损失, 一个是对抗损失. 后者用于匹配自编码器潜变量的后验分布与任意的潜变量先验分布. 其结构如下所示:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture1.png&quot; alt=&quot;aae_architecture1.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从“1”到“1”的网络是自编码器, &lt;code&gt;红框中的是自编码器的编码器部分, 也是 GAN 的生成器&lt;/code&gt;. 与之前所见 GAN 不同的是, 这个 GAN 的真实数据采样自预期的先验分布 p(z), 比如高斯分布, 而生成数据则采样自编码结果 q(z). AAE 中的 GAN 的目标就是指导 q(z) 匹配/拟合 p(z).&lt;/p&gt;
&lt;p&gt;而自编码器本身的目标, 就是最小化重构误差.&lt;/p&gt;
&lt;p&gt;一旦 AAE 训练完成, 其解码器就能够作为一个生成模型, 利用 p(z) 生成样本, 比如自然图片.&lt;/p&gt;
&lt;p&gt;AAE 与 VAE 的不同在于: VAE 使用 KL 散度惩罚来利用 q(z); AAE 则使用对抗训练的方式来匹配 q(z) 与 p(z). 在 VAE 中, 通过蒙特卡罗采样的 KL 散度来反向传播, 需要先验分布的明确泛函形式; 而 AAE 只需要够从先验分布采样, 就能引导 q(z) 匹配 p(z).&lt;/p&gt;
&lt;p&gt;VAE 最小化 x 的负对数似然的上界, 表达式如下:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/vae_nll.png&quot; alt=&quot;vae_nll.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看到变分边界由 3 部分组成, 第一部分可以看作自编码器的重构项, 第二三项则可以看作是正则项. 其中第二项鼓励后验分布 q(z) 具有大方差, 第三项则最小化 q(z) 与 p(z) 之间的交叉熵. 在 AAE 中, 保留第一项, 将后两项替换为对抗训练过程, 以驱使 q(z) 去完全匹配 p(z).&lt;/p&gt;
&lt;p&gt;AAE 与 GAN 的不同在于: GAN 通常利用神经网络输出层的像素级的数据分布; 而 AAE 倚赖自编码训练来捕获数据分布. 在 AAE 的训练过程中, 可以令 q(z) 去拟合更低维度的更简单的分布. (这就起到了简化和降维的作用)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Generative Moment Matching Networks, GMMN&lt;/code&gt; 利用&lt;code&gt;最大平均差异 Maximum Mean Discrepancy, MMD&lt;/code&gt;来对神经网络的输出分布进行变形. MMD 可以解释成最小模型分布与数据分布的所有&lt;code&gt;Moment, 矩&lt;/code&gt;的距离.&lt;/p&gt;
&lt;p&gt;AAE 与 GMMN+AE 的最大区别在于: 对抗训练过程作为&lt;code&gt;regularizer 正则器&lt;/code&gt;从训练开始就对 q(z) 进行修改; 而 GMMN+AE 首先训练一个标准的 Dropout 自编码器, 然后去拟合预训练的自编码器的潜变量分布.&lt;/p&gt;
&lt;p&gt;文中利用如下的结构, 将标签信息注入 AAE, 有点像将 GAN 替换为 cGAN. 不同的是, 以下的结构只将标签信息作为 D 的输入, 并以&lt;code&gt;半监督&lt;/code&gt;的方式进行训练. 如图所示, 标签以 one-hot 的形式表示, 其功能类似一个开关, 根据文章描述, 它用于选择相应的 D 的决策边界.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture_info.png&quot; alt=&quot;aae_architecture_info.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;整个无监督学习的过程和&lt;a href=&quot;/notes/papernotes_47/&quot;&gt;上一篇&lt;/a&gt;所描述的有些相似, 读者不妨对比着看. 10 维的 one-hot 向量实际包含 11 个类信息 (都不激活, 可以表示一种情况). 文章此处使用 10 个二维高斯混合分布来表示潜变量 z 的分布. 前 10 个类中的每一个都对应一项二维高斯分布. 而额外的类对应无标签的训练数据, 当无标签数据输入模型, 对应额外的类, 将为整个混合高斯分布选择一个决策边界.&lt;/p&gt;
&lt;p&gt;进行对抗训练时, 训练 D, 通过 one-hot 向量向 D 输入混合高斯组件对应标签, (这里有点凌乱, 我的理解就是输入 p(z) 和标签信息), 无标签数据的 p(z) 就采样自整个混合高斯; 训练 G 时, 就向 D 输入训练样本对应的标签.&lt;/p&gt;
&lt;p&gt;近年来, 生成模型成为半监督学习的最流行的方法, 是因为它们能&lt;code&gt;依据某种规则将类标签信息与其他变分的潜因子分开来&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;文章以下图所示的结构来训练监督 AAE, 以分离类标签与图片风格信息. 可见, 解码器利用标签的 one-hot 向量表示和潜变量 z 来重构图片. 该架构迫使神经网络将独立于标签的所有信息都保存到 z 中.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture_supervised.png&quot; alt=&quot;aae_architecture_supervised.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;效果是这样的, 以下每一行表示 AAE 学习到的数字独立于标签的某种风格.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_supervised_mnist.png&quot; alt=&quot;aae_supervised_mnist.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;文中又提出一种半监督学习的结构, 不过这次半监督学习的是类别信息, 如下所示. 相比于上面监督学习的结构, 多了一个对抗网络用于学习图片的类别, 它确保潜变量 y 不携带风格信息, 而 y 的后验分布 (即编码器得到的 y) 匹配分类分布.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture_unsupervised.png&quot; alt=&quot;aae_architecture_unsupervised.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;在半监督学习的架构之上, 文中又移除了半监督分类步骤, 进行无监督聚类实验.&lt;/p&gt;
&lt;p&gt;非参数的降维技术缺陷是, 由于缺少参数, 新的数据无法在嵌入空间中找到位置. (除非对整个数据集重新降维); 自编码器也被用于数据降维, 然而非正则的自编码器将流形“打散”进不同的空间位置, 导致相似的图片的潜代码不同.&lt;/p&gt;
&lt;p&gt;最后, 文中以如下的结构进行降维实验. &lt;code&gt;对抗正则 adversarial regularization&lt;/code&gt;的过程将相似图片的潜代码聚集在一起, 使得流形连续.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/aae_architecture_rd.png&quot; alt=&quot;aae_architecture_rd.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;相比之前的架构, 该架构将 one-hot 表示的 m 维数据乘以一个 mxn 的矩阵 (n &amp;lt; m), 得到一个 n 维的表示, 实现降维. 此外引入了一个成本函数来惩罚两个簇头之间的欧式距离.&lt;/p&gt;
&lt;h2 id=&quot;adversarially-learned-inference&quot;&gt;Adversarially Learned Inference&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1606.00704.pdf&quot;&gt;https://arxiv.org/pdf/1606.00704.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;作者 Ishmael Belghazi 的博客: &lt;a href=&quot;https://ishmaelbelghazi.github.io/ALI/&quot;&gt;https://ishmaelbelghazi.github.io/ALI/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;与上篇类似, 本文介绍的&lt;code&gt;对抗学习推理 ALI&lt;/code&gt;, 使用对抗的方式联合训练了一个生成网络与一个推断网络. 生成网络就是 GAN 中的 G, 将随机潜变量映射到数据空间, 而推理网络 (以下简称 I) 将训练样本从数据空间反映射到潜变量空间. 因此, 对抗博弈在 G, I 与 D 之间展开.&lt;/p&gt;
&lt;p&gt;基于 VAE 的技术, 通过最大化数据 x 的对数似然来引入随机潜变量 z, 并最大化他们的联合分布 p(x, z). 但由于潜变量的确切边缘分布通常难于追踪, VAE 引入了一个近似后验 q(x, z), 并最大化 p(x) 的对数似然的变分下界.&lt;/p&gt;
&lt;p&gt;基于极大似然估计法的模型会显得保守, 概率质量将扩散到整个数据空间. (Theis et al.2015) 以生成图片为例, 基于 VAE 的模型会模糊化.&lt;/p&gt;
&lt;p&gt;GAN 能得到一个生成高品质样本的生成模型. 但它没有一个有效的推理机制. 因此本文将&lt;code&gt;推理机制&lt;/code&gt;与&lt;code&gt;深度有向生成模型&lt;/code&gt;注入 GAN-like 的对抗框架, 得到 ALI, 其结构简图如下所示.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/ali_architecture.png&quot; alt=&quot;ali_architecture.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;左框表示一个 I, 将真实训练样本 x 映射到潜变量空间, 得到 z_hat; 右框表示一个 G, 将潜变量 z 转化为生成样本 x_tilde. 而 D 被训练为识别 (x, z_hat) 与 (x_tilde, z).&lt;/p&gt;
&lt;p&gt;ALI 的 G 和 I 可以分别看作&lt;code&gt;编码器 encoder&lt;/code&gt;和&lt;code&gt;解码器 decoder&lt;/code&gt;, 它们的联合分布可以看作如下形式:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编码器的联合(概率)分布 q(x, z)=q(x)q(z|x)&lt;/li&gt;
&lt;li&gt;解码器的联合分布 p(x, z)=p(z)p(x|z)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此, 输入 D 的 (x, z_hat) 和 (x_tilde, z) 分别就代表了两个联合分布.&lt;/p&gt;
&lt;p&gt;当 ALI 的训练完成, 两个联合分布匹配之后, 可以假定所有的边缘分布和条件分布都匹配了. 比如可以认为条件概率 q(z|x) 匹配了后验概率 q(z|x).&lt;/p&gt;
&lt;p&gt;ALI 的值函数如下所示. 和 GAN 的值函数的区别仅在于 D(x)=&amp;gt; D(x, Gz(x)), D(G(z))=&amp;gt;D(Gx(z), z).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/ali_value_function.png&quot; alt=&quot;ali_value_function.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;为了使梯度能够从 D 传递到编码器和解码器, 梯度需要流过两个采样过程 &lt;img src=&quot;/img/2017-12-17-papernotes03/ali_sample_process_z_hat.png&quot; alt=&quot;ali_sample_process_z_hat.png&quot;&gt; 和 &lt;img src=&quot;/img/2017-12-17-papernotes03/ali_sample_process_x_hat.png&quot; alt=&quot;ali_sample_process_x_hat.png&quot;&gt;. 对此, 本文使用了&lt;code&gt;重参数化 reparametrization&lt;/code&gt; 技巧, 即不直接从期望的分布中采样, 而是对服从期望分布的噪声进行转换以计算随机变量. 比如 &lt;img src=&quot;/img/2017-12-17-papernotes03/ali_reparametrization_example.png&quot; alt=&quot;ali_reparametrization_example.png&quot;&gt;, 可通过下式计算. (我尝试用 PyTorch 复现本文的代码, 但总是遭遇 numeric problem, 问题就出在 reparameterization 上, 至今未训练成功)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/ali_reparametrization_example2.png&quot; alt=&quot;ali_reparametrization_example2.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;本文还提到, 可以扩展 ALI 的框架至多个随机层. 什么叫多个随机层呢, 是这样的, 不要和多层神经网络搞混了:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/ali_multiple_stochastic_layers.png&quot; alt=&quot;ali_multiple_stochastic_layers.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;你会发现 ALI 和上文的 AAE 有些相似, 但和 AAE 不同的是, ALI 旨不在优化重构损失, 且 D 的输入是 (x, z) 对 而不是边缘样本 z.&lt;/p&gt;
&lt;p&gt;重要的事情多提几遍: &lt;code&gt;Batch Normalization&lt;/code&gt; 对于 GAN 的训练真的很关键! 但不用于 G 和 I 的输出层, 为了稳定训练过程, 也没有应用于 D 中受 z 影响的层.&lt;/p&gt;
&lt;p&gt;本文的几个实验发现是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;样本对之间的过渡很平滑, 中间生成的图片也很真实 (他们对于潜变量空间的研究是, 选取 z1, z2, 然后进行线性插值, 观察中间 z 生成的图片). 这表明 ALI 并没有将概率质量仅仅置于训练样本上, 也扩散到了其他有助于生成的潜在特征.&lt;/li&gt;
&lt;li&gt;尽管 ALI 没有针对重构品质 (quality) 进行优化, 但实际的重构效果很好. 这表明条件概率 q(z|x) 很好地近似了后验概率 p(z|x)&lt;/li&gt;
&lt;li&gt;实验结果表明, ALI 的重构没有专注于像素级的精准, 比如捕获物体的位置, 颜色, 风格等会犯错, 但它能表示更抽象的特征, 这表明, ALI 的潜变量表示对于一些不那么有趣的因素具有更高的不变性, 不会分配更多的能力去捕获它.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后, 本文的附录贴上了超详细的模型超参数表, 可以很方便地复现实验, 点赞. 此外还有一个很有意思的小彩蛋, 有时间我会翻译出来贴出来.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实践经验, ALI 的架构中有部分层同时使用了 BN 和 Dropout. 我自己一开始没有注意它们的先后关系. 网上的指导建议是: Dropout 放在最后.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;另一个问题是, BN 放在非线性激活函数之前还是之后. BN 的论文 (稍后会放出笔记) 中说放在激活函数之前, 但近来的使用趋势是放在激活函数之后.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;adversarial-feature-learning&quot;&gt;Adversarial Feature Learning&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1605.09782.pdf&quot;&gt;https://arxiv.org/pdf/1605.09782.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文提出了 Bidirectional GAN (以下简称BiGAN), 其网络结构与 ALI 极像. 如下所示 (这两篇论文同时被 ICLR2017 收录)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/bigan_architecture.png&quot; alt=&quot;bigan_architecture.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;就连值函数, BiGAN 与 ALI 几乎也是一样的:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-17-papernotes03/bigan_value_function.png&quot; alt=&quot;bigan_value_function.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;与 ALI 相同的概念, 以下不再赘述. 相比 ALI, 本文的证明性更强, 各种数学推理, 仅摘录结论. 欲了解更多的同学直接看原文吧. (我表示看不太懂这诸多推理)&lt;/p&gt;
&lt;p&gt;本文指出, 当达到全局最优时, G 与 E (从 x 到 z 的编码器) 是双射函数, 且互为逆, 即 E=G^-1. BiGAN 近似于一个带 L0 损失函数的自编码器.&lt;/p&gt;
&lt;p&gt;本文还指出, 最优的 D 必须是随机的, 但由于次优的 G 与 E, D 不可能达到随机的状态&lt;/p&gt;
&lt;p&gt;不同于正则的自编码器, L0 损失函数不对数据分布或结构做任何假设; BiGAN 的所有的结构化属性都由 D 习得.&lt;/p&gt;
&lt;p&gt;在原始 GAN 中, 交替真实与生成样本以向 G 提供更强的梯度. 但 BiGAN 提供的“可逆”观测值能为 G 和 E 提供足够强的梯度. 因此, 本文使用在一次迭代中, 同时更新 D, G, E 的方法.&lt;/p&gt;
&lt;p&gt;如本文标题显示的, BiGAN 旨在学习有意义的特征表示. 文中是这样描述的: 原始 GAN 中, D 接收样本作为输入, 并将其习得的中间表示作为相关任务的特征表示, 再没有额外的机制. 它对于生成数据与真实数据的语义上有意义的特征并不十分清晰. 事实上, 当 G 生成了真实数据时, D 可逆会完全忽略生成数据而无条件地预测 p=1/2, 学不到任何有意义的中间表示. BiGAN 就是希望让 GAN 习得有意义的表示.&lt;/p&gt;
</content:encoded><category>笔记</category><category>深度学习</category></item><item><title>Mendeley, 文献管理小助手</title><link>https://lifelonglove.cn/articles/introduction_to_mendeley/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/introduction_to_mendeley/</guid><description>围绕 Mendeley 的跨平台文献管理流程，介绍论文导入、元数据整理、分组、标注、引用插件和云端同步，帮助研究者建立从收集到写作引用的工作链。</description><pubDate>Fri, 01 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本周邂逅了一款文献管理软件——Mendeley，玩了一天，甚得我心，推荐给大家。以下是我写的使用指南，基本涵盖了Mendeley文献管理的所有功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我使用的操作系统是Ubuntu，一款Linux发行版。其实我最初听说的是Endnote，但它并没有提供Linux版本。为了能方便地进行文献管理，不必开一个虚拟机或者重启Windows系统（双系统），我选择了提供全平台支持（Windows、Mac、Linux、iOS、Android、Browser Plugin）的Mendeley。除了全平台支持之外，Mendeley的另一个优势是，它是开源免费的（程序员总是偏爱开源的东西）。最重要的是，它还具有强大的文献管理能力，因此是Endnote很好的替代品。&lt;/p&gt;
&lt;p&gt;以下是Mendeley的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动从导入到库中的PDF 文件中提取文献信息；&lt;/li&gt;
&lt;li&gt;通过Web Importer（浏览器插件）从各种数据库中直接导入文献；&lt;/li&gt;
&lt;li&gt;强大的文件管理功能；&lt;/li&gt;
&lt;li&gt;具有高亮、注释功能的PDF阅读器；&lt;/li&gt;
&lt;li&gt;方便在Word中插入参考文献；&lt;/li&gt;
&lt;li&gt;通过群组功能与其他研究者共享文献；&lt;/li&gt;
&lt;li&gt;公开个人Profile。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;安装完毕Mendeley，首次运行时，软件会提示安装Office插件，如图1所示。Libre Office是Linux桌面版下默认的Office，因此它提示安装Libre Office插件而不是Microsoft Office插件。若安装Windows版，程序应提示安装Microsoft Office插件。安装插件Office插件能极大方便参考文献的编写，因此选择安装。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/1.png&quot; alt=&quot;首次运行Mendeley，提示安装Office插件&quot;&gt;
&lt;small&gt;图1 首次运行Mendeley，提示安装Office插件&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Mendeley的界面比较分明，最上方是菜单栏、工具栏与搜索框；左侧上方是导航，下方是作者信息，此时未导入文献，显示为空；中央就是文献目录区，此时同样为空；右侧是选中文献的基本资料。如图2所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/2.png&quot; alt=&quot;Mendeley的用户界面，未导入文献&quot;&gt;
&lt;small&gt;图2 Mendeley的用户界面，未导入文献]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;在使用文献管理工具之前，我已经搜集了不少资料。得益于Mendeley自动从导入的PDF中提取文献信息的功能，避免了手动录入的麻烦。Mendeley提取文献信息偶尔会出现错误。对文献标题与作者的简单核对，结果如图3所示。&lt;/p&gt;
&lt;p&gt;Mendeley提供了“Citations”和“Table”两种视图，以下是Table形式的试图。可以看到，作者信息都被提取到了左下角的“Filter by Authors”区，可以方便地根据作者查找文献。文献区提供了文献的基本信息，包括作者、标题、发表年、期刊等。而最左侧的三列提供了加星，已读/未读，是否本地存储的有用小功能。由于我的都是本地导入的，因此第三列用一个Adobe PDF的标记表示本地存储。右侧显示的是选中的文献的更细节的信息，包括摘要、关键字、文献的可获得URL等，都是可编辑的。此外，如果阅读文献时做了笔记，点击“Notes”即可查看；“Contents”是选中文献的目录。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/3.png&quot; alt=&quot;Mendeley用户界面，导入文献&quot;&gt;
&lt;small&gt;图3 Mendeley用户界面，导入文献]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;这些文献在不同时期被下载，因此存在重复的情况。Mendeley提供了文献去重功能，在菜单栏“Tools”下有一项“Check for Duplicates”，选择点击，Mendeley将根据文献的细节信息对不同文献进行对比。如图4所示，它从我的文献库中找到7组很可能是重复的文献，“Confidence”表示程序判断对应组是重复的置信度/可能性。原理很简单，就是从文献细节中提取相应字段进行对比，图4中程序选择了摘要、URL、ArXiv ID、ISSN等等，匹配上的字段越多，置信度越高，就是这样。但是，用户是可以修改这些字段的，所以如果这些字段填写错误，去重的错误率也会增大。经比对，这几组确实都是重复文献，但文件名上稍有不同，全部选择合并。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/4.png&quot; alt=&quot;使用Mendeley对文献进行去重&quot;&gt;
&lt;small&gt;图4 使用Mendeley对文献进行去重]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Mendeley提供了基本的文献检索功能，左上角“Mendeley”下的“Literature Search”检索外部文献，右上角的搜索框则提供了在用户的文献库内进行检索的功能。图5展示了使用外部检索的结果。可以看到，很多结果实际上都指向同一篇文章。我怀疑，这是因为不同用户为同一篇文章填写的细节字段不同，而Mendeley的服务器并没有对所有文献进行去重，因此存在同一篇文章的多个版本。这也是导入PDF自动提取文献信息出错的一个原因。Mendeley根据它的数据库为导入的文章赋信息，而不是提取信息。我好几次遇到一篇文章被“信息提取”为另一篇文章的情况，估计就是这个原因。另外，可以看到，搜索结果中，如果用户文献库中已存在某篇文章，在结果前会有一个小勾；如果是用户文献库中不存在的文献，除了对应记录前没有打勾的标记外，选中时，右侧“Details”下会提示保存引用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/5.png&quot; alt=&quot;使用Mendeley进行外部文献检索&quot;&gt;
&lt;small&gt;图5 使用Mendeley进行外部文献检索]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;切换成“Citations”视图时，文献会以指定的参考文献格式展示，图6所示为默认的参考文献格式（American Psychological Association 6th Edition）展示的结果。一种浓郁的参考文献气息扑面而来。这也是文献管理软件的管理文献之外的另一大用途——方便用户编写参考文献。右击文献，有一个“Copy As”的选项，可复制当前的参考文献格式。当然，更方便的还是在行文过程中直接使用Office插件，随时随处插入引用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/6.png&quot; alt=&quot;以Citation视图展示文献&quot;&gt;
&lt;small&gt;图6 以Citation视图展示文献]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;点击菜单栏“View”=&amp;gt;“Citation Style”可更改参考文献格式，极大方便了不同参考文献格式的论文写作。图7所示是上述操作的对话框。在安装软件时，默认已经安装了基本的参考文献格式，用户也可以自行下载安装其他参考文献格式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/7.png&quot; alt=&quot;Mendeley默认提供的引用风格&quot;&gt;
&lt;small&gt;图7 Mendeley默认提供的引用风格]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Mendeley还有一个比较好的地方是，右击文献，选择“Related Documents”可以查找与当前文献相关的文献。这对于研究有所帮助。图8展示了“Wasserstein GAN”的相关文献。10个结果中，有4个是这篇文章本身，可见Mendeley服务器上没有对文献进行去重的危害有多大。“Related Documents”的功能虽然有帮助，但帮助也没有想象中的大，总之能帮助做一点扩展阅读。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/8.png&quot; alt=&quot;使用Mendeley查找相关文献&quot;&gt;
&lt;small&gt;图8 使用Mendeley查找相关文献]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Mendeley内置了PDF阅读器，对于本地存储的文献，双击即可打开。图9展示了Mendeley作为PDF阅读器的界面。从工具栏可以看出，Mendeley提供了做笔记、高亮等功能，还算挺实用的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/9.png&quot; alt=&quot;使用Mendeley作为PDF阅读器&quot;&gt;
&lt;small&gt;图9 使用Mendeley作为PDF阅读器]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;在Office中使用Mendeley来辅助参考文献的写作真的很方便。即使在Windows下，我也是使用WPS Office的，Mendeley并不为WPS Office提供插件（我在网上看到网友要求支持WPS Office的呼声很高）。因此不便演示Windows下如何使用Mendeley插入文献引用。&lt;/p&gt;
&lt;p&gt;在Libre Office中使用Mendeley插入参考文献很简单粗暴。图10是安装了Mendeley插件后，Libre Office Writer（对应Microsoft Office 的Word）的工具栏。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/10.png&quot; alt=&quot;安装Mendeley插件后，Libre Office Writer的工具栏&quot;&gt;
&lt;small&gt;图10 安装Mendeley插件后，Libre Office Writer的工具栏]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;我查了下“Bibliography”是“文献目录”的意思。因此写作的时候，先在文档底部插入一个Bibliography，然后在行文过程中点击“Insert Citation”即可随时插入参考文献的引用，参考文献将自动加入Bibliography中。由于插入引用中间截图的困难性，只好直接上结果了，如图11所示。比如输入“Wasserstein GAN”之后，点击“Insert Citation”将弹出Mendeley的搜索框，输入文献关键字就能插入文献引用。我想，Microsoft Office的插件使用应大同小异，也是极方便的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/11.png&quot; alt=&quot;使用Mendeley插入文献引用的示例&quot;&gt;
&lt;small&gt;图11 使用Mendeley插入文献引用的示例]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Mendeley提供的浏览器插件Web Importer对于即时保存检索到的文献挺有帮助的。图12、13展示了在Web of Science上使用Web Importer的结果。可以看到，作为Endnote的竞品，在Web of Science上使用Web Importer的效果并没有达到预期。比如在图12中，点击插件图标，Web Importer搜索的结果与Web of Science搜索的结果并不完全一致。Web of Science的搜索结果是文献列表，而此时Web Importer给出的却是文献所属期刊列表，这明显不是用户期望的结果。进入文献详情页，再点击插件图标，结果则有些画蛇添足，第一条记录是文献，第二条记录是期刊。再点击文献细节，可以看到基本信息都补全了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/12.png&quot; alt=&quot;在Web of Science上使用Mendeley提供的浏览器插件Web Importer，1&quot;&gt;
&lt;small&gt;图12 在Web of Science上使用Mendeley提供的浏览器插件Web Importer，1]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/13.png&quot; alt=&quot;在Web of Science上使用Mendeley提供的浏览器插件Web Importer，2&quot;&gt;
&lt;small&gt;图13 在Web of Science上使用Mendeley提供的浏览器插件Web Importer，2]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;在Scopus上使用Web Importer的效果比Web of Science好，直接给出了文献列表，而不是期刊，如图14所示。不得不感慨，毕竟都是ELSEVIER出品。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-12-01-introduction_to_mendeley/14.png&quot; alt=&quot;在Scopus上使用Web Importer&quot;&gt;
&lt;small&gt;图14 在Scopus上使用Web Importer]&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;至此，基本完成了对Mendeley文献管理功能的调研。此外，Mendeley还具有社交属性，可进行论文共读。但超出了文献管理的范畴，不再展开。&lt;/p&gt;
&lt;p&gt;尽管未接触过Endnote，但是我发现Mendeley基本满足绝大多数用户对文献管理的需求，是一款很好的工具！&lt;/p&gt;
</content:encoded><category>文章</category><category>工具</category></item><item><title>论文笔记2</title><link>https://lifelonglove.cn/notes/papernotes_47/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/papernotes_47/</guid><description>整理 LAPGAN、DCGAN 与改进 GAN 训练技巧的结构设计、实验发现和局限。</description><pubDate>Sun, 26 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;根据文章的描述, GAN 与 cGAN 的代码实现见&lt;a href=&quot;https://github.com/Engine-Treasure/GANs-in-PyTorch&quot;&gt;GANs-in-PyTorch&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PS: 文中偶尔出现的 &lt;code&gt;[...]&lt;/code&gt; 用于帮助读者断句, 此后不再赘述.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;LAPGAN 的创新点是: &lt;code&gt;使用拉普拉斯金字塔的结构, 以从粗糙到细致的方式生成图片. 在金字塔的每一层, 使用 GAN 方法训练一个图片生成模型&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-26-papernotes_47/img_laplacian_pyramid.png&quot; alt=&quot;Image Laplacian Pyramid&quot;&gt;&lt;/p&gt;
&lt;p&gt;换言之, 该方法利用了自然图片不同尺寸的缩放结构, 构建了一系列的生成模型, 拉普拉斯金字塔的每一层都能够利用特定尺寸的图像结构.&lt;/p&gt;
&lt;p&gt;DCGAN 的贡献是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;复用了训练好的[G 和 D]的部分结构作为特征提取器, 用于监督任务;&lt;/li&gt;
&lt;li&gt;提出了一些训练 GAN 的有效方法, 最主要的是: 全部使用卷积层, 不再需要池化; 移除了神经网络最后的全连接层; 使用 Batch Normalization&lt;/li&gt;
&lt;li&gt;本文还展示了 G 具有有趣的向量算数运算 (z 空间), 能控制生成的样本的一些语义特征. 比如“带眼镜的男人” - “男人” + “女人” = “带眼镜的女人”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Improved Techniques for Training GANs 文章提出了几项重要方法包括:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;feature matching&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minibatch features&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virtual batch normalization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;historical average&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;one-sided smoothing&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;lapgan&quot;&gt;LAPGAN&lt;/h2&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1506.05751.pdf&quot;&gt;https://arxiv.org/pdf/1506.05751.pdf&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;还是想记录一下相关工作, 对于读者也许也能有所启发.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;相关工作&quot;&gt;相关工作&lt;/h3&gt;
&lt;p&gt;DRAW 模型使用了带 RNN + 注意力机制的方式, 通过图片块的轨迹来生成图片.&lt;/p&gt;
&lt;p&gt;Sohl-Dickstein et al 2016 使用 diffusion-based 深度无监督学习方法.&lt;/p&gt;
&lt;p&gt;Theis 与 Bethge 利用 LSTMs 来捕捉空间依赖, 展示了令人信服的自然纹理修补结果.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;LAPGAN 模型在拉普拉斯金字塔框架中集成了条件 GAN, 即&lt;a href=&quot;44_1.md&quot;&gt;上篇&lt;/a&gt;的 cGAN.&lt;/p&gt;
&lt;p&gt;拉普拉斯金字塔是一个线性可逆的图片表示, 由一组&lt;code&gt;带通图片&lt;/code&gt;组成, 间隔一个八度(直译, 不用在意), 加上一个低频残差.&lt;/p&gt;
&lt;p&gt;拉普拉斯金字塔的处理过程从最粗糙的层次开始, 可以理解为分辨率最低的图片, 或者说残差. 对其进行上采样, 简单地说就是放大图片, 从 $j \times j$ 提升为 $2j \times 2j$, 显然这时候图片会变得模糊, 求用图片集中 $2j \times 2j$ 的图片与上采样得到的图片的差异, 记作 $h_k$ . 再对真实的 $2j \times 2j$ 的图片重复以上步骤, 直到原始图片, 比如说 $64 \times \ 64$分辨率的图片.&lt;/p&gt;
&lt;p&gt;LAPGAN 就有一组生成卷积模型组成, 每一层的目的就是捕捉上述 $h_k$ 的分布. LAPGAN 的&lt;code&gt;采样&lt;/code&gt;过程如下图所示.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-26-papernotes_47/lapgan_sample_procedure.png&quot; alt=&quot;Sampling procedure of LAPGAN&quot;&gt;&lt;/p&gt;
&lt;p&gt;前一篇提到 cGAN 的条件可以是任意信息, 并不止于类标签. LAPGAN 正是利用了这一点. 如上图所示, G 条件于&lt;code&gt;残差&lt;/code&gt;, 或者说模糊图像; 生成残差与“真实图像”的差异图像 $h_k$; 再将差异与残差组合成“真实图像”. 上采样得到更高分辨率的残差, 重复以上步骤. 就得到了最终的全分辨率的生成图像.&lt;/p&gt;
&lt;p&gt;D 同样条件于残差, 因为 D 与 G 的条件一致. LAPGAN 的训练过程如下所示. 此时从全分辨率的原始图片 $I_0$ 开始, 对其进行降采样得到 $I_1$, 再对 $I_1$ 进行上采样得到我们所说的残差 $l_0$, 作为 $D_0$ 与 $G_0$ 的条件; $G_0$ 条件于残差 $l_0$, 对 $z_0$ 进行加工, 生成差异图像 $\hat{h_0}$, 作为 $D_0$ 的假样本; 另外由 $I_0$, $l_0$ 得到真实差异图像 $h_0$, 完成第一层的训练. 对 $I_1$ 进行同样的处理, 直到最终层.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-26-papernotes_47/lapgan_train_procedure.png&quot; alt=&quot;Train procedure of LAPGAN&quot;&gt;&lt;/p&gt;
&lt;p&gt;所以归根结底, $G_0$ 生成的是图像与小一号的图像上采样的结果的差异图像. 从图中看来, 就是图像的细节. 据我所知, 许多生成方法都存在生成图片模糊的问题.&lt;/p&gt;
&lt;p&gt;LAPGAN 的关键点就在于, 将生成过程拆分成依次的改进过程. 独立地训练每一层的优点是: 模型更不容易记住训练样本.&lt;/p&gt;
&lt;h2 id=&quot;dcgan&quot;&gt;DCGAN&lt;/h2&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1511.06434.pdf&quot;&gt;https://arxiv.org/pdf/1511.06434.pdf&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;相关工作-1&quot;&gt;相关工作&lt;/h3&gt;
&lt;h4 id=&quot;无标签数据的表示学习&quot;&gt;无标签数据的表示学习&lt;/h4&gt;
&lt;p&gt;在图片上下文, 可以对&lt;code&gt;图像块 image patches&lt;/code&gt;进行分层聚类, 可以习得有用的图片表示. (Coates &amp;amp; Ng 2012)&lt;/p&gt;
&lt;p&gt;另一个计算机视觉研究领域的无监督特征学习的方法是: 自动编码器, 将图片编码成&lt;code&gt;紧凑代码 compact code&lt;/code&gt;再尽可能精确地解码重构图片. (具体地是: 使用卷积, 将神经网络层堆叠成阶梯结构, 使它能将代码中的&lt;em&gt;是什么&lt;/em&gt;和&lt;em&gt;在哪里&lt;/em&gt;组件分离)&lt;/p&gt;
&lt;p&gt;深度信念网络对于学习分层表示也有良好的表现.&lt;/p&gt;
&lt;h4 id=&quot;生成自然图片&quot;&gt;生成自然图片&lt;/h4&gt;
&lt;p&gt;GAN 生成的图片有噪声, 难以理解的. LAPGAN 能生成更高品质的图片, 但图片看起来仍然是抖动的, 因为在将多个模型串起来的时候引入了噪声.&lt;/p&gt;
&lt;h4 id=&quot;cnn-的内部可视化&quot;&gt;CNN 的内部可视化&lt;/h4&gt;
&lt;p&gt;在 CNN 的上下文, Zeiler 等人使用&lt;code&gt;反卷积 deconvolutional&lt;/code&gt; 和最大过滤, 发现每个卷积过滤器的近似目的; Mordvintsev 等人使用梯度下降检查了能够激活特定过滤器子集的理想图片.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;dcgan-架构与训练方法&quot;&gt;DCGAN 架构与训练方法&lt;/h3&gt;
&lt;p&gt;DCGAN 的要点:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用全卷积网络代替判别性的空间池化函数, 以让网络自我学习空间降采样.&lt;/li&gt;
&lt;li&gt;移除了卷积特征之后的全连接层. (事实上, G 的第一层还是全连接的, 但它只用于矩阵乘法, 结果立即被变形了; D 的最后一个卷积层也被展开, 最后输出一个 Sigmoid 输出). (作者们还发现&lt;code&gt;全局平均池化&lt;/code&gt;增加了模型稳定性, 但使收敛更慢了)&lt;/li&gt;
&lt;li&gt;使用 Batch Normalization 稳定学习. (BN 的效果很好, 有了它, 不必为初始化带来的问题而纠结, 也能帮助训练更深的模型.) 直接使用 BN 会导致样本抖动, 使模型不稳定. 因此在 G 的输出层和 D 的输入层不适用 BN.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在 G 中使用 ReLU, 唯一的例外是其输出层使用 Tanh; 在 D 中使用 LeakyReLU. (作者们发现, 使用有界的激活函数能使模型更快饱和, 快速覆盖训练分布的色彩空间.)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;实验与讨论与其他&quot;&gt;实验与讨论与其他&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;最近的研究表明, 模型学习的快慢与泛化性能有直接联系&lt;/code&gt; (Hardt et al. 2015)&lt;/p&gt;
&lt;p&gt;本文使用的评估无监督表示学习算法好坏的方法是: 将习得的算法 (可以理解为 D) 用于监督学习作为特征提取器, 然后评估在此之上的线性模型的性能. 本文提出的具体做法是: 训练好 GAN 模型之后, 在 D 的所有卷积层之后加上最大池化层, 结果得到 4 x 4 的空间网格; 所有这些特征再被展开, 连接成一个向量, 作为一个线性分类器 (文中是 L2-SVM) 的训练输入.&lt;/p&gt;
&lt;p&gt;文中指出, CNN 架构不是 DCGAN 成功的关键点.&lt;/p&gt;
&lt;p&gt;本文表示, 在大型图片数据集上训练的无监督 DCGAN 能习得特征的分层表示. 文中展示了 D 会被卧室的典型部分所激活, 比如床或窗户.&lt;/p&gt;
&lt;p&gt;另外很有意思的是, 如前所述, 本文展示了图片向量也能进行算术运算. 这个运算实际是对 Z 向量的操作, 这也表明对潜变量的修改, 能改变生成样本的语义.&lt;/p&gt;
&lt;p&gt;作者们发现, 从 G 与 D 的 BN 中移除 scale 与 bias 参数能产生更好的结果. 他们猜测, 这可能是因为 BN 引入的噪声能帮助 G 更好地探索低层的数据分布, 从而更好地生成样本.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;improved-techniques-for-training-gans&quot;&gt;Improved Techniques for Training GANs&lt;/h2&gt;
&lt;p&gt;论文地址: &lt;a href=&quot;https://arxiv.org/pdf/1606.03498.pdf&quot;&gt;https://arxiv.org/pdf/1606.03498.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;训练 GAN 需要找到具有连续的, 高维参数的非凸博弈的纳什均衡. 训练 GAN 的典型方法是使用梯度下降, 但梯度下降是用于找到成本函数的极小值, 而不是博弈的纳什均衡, 因此使用梯度下降算法无法使结果收敛.&lt;/p&gt;
&lt;h3 id=&quot;feature-matching&quot;&gt;Feature matching&lt;/h3&gt;
&lt;p&gt;特征匹配通过为 G 指定一个新的观测值, 防止了在当前 D 上的过度训练, 从而解决了 GAN 的不稳定性. 这个新的观测值要求 G 生成匹配真实数据的统计量的数据, 而不是直接最大化 D 的输出, 而我们使用 D 来指定值得匹配的统计量.&lt;/p&gt;
&lt;p&gt;特别地, 我们训练 G 来匹配 D 的中间层的特征的期望值. 因为训练 D 的目的就是找到最能区分真实样本与生成样本的特征, 因此这种做法是很自然的.&lt;/p&gt;
&lt;p&gt;用 $f(x)$ 来表示 D 中间层的激活值, 那么 G 的新观测值就可以表示为 &lt;img src=&quot;/img/2017-11-26-papernotes_47/feature_matching_g_objective.png&quot; alt=&quot;feature_matching_g_objective.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;常规的 GAN 训练, G 的目标是去匹配训练数据的分布, 是一个定点. 实际中无法保证达到该点. 但经验显示特征匹配在常规 GAN 不稳定的情况下是有效的.&lt;/p&gt;
&lt;h3 id=&quot;minibatch-discrimination&quot;&gt;Minibatch discrimination&lt;/h3&gt;
&lt;p&gt;GAN 的主要缺点之一是容易发生 &lt;code&gt;模式崩塌 mode collapse&lt;/code&gt;. 当接近模式崩塌时, 对于许多相似的点, D 的梯度将朝向相似的方向. 由于 D 总是独立地处理样本, 它的梯度之间没有协调机制, 无法让 G 去生成互不相同的样本.相反, G 所有的输出都趋向于同一点, 这使得 D 相信这个点是高度真实的. 于是就发生了模式崩塌. 这之后, G 只生成一个样本, 梯度下降无法分离各输出. 于是, D 的梯度将永远推着 G 生成的单个样本在空间中移动, 算法永不收敛.&lt;/p&gt;
&lt;p&gt;Minibatch discrimination 的概念是这样的: 任何 D 组合地查看多个样本, 而不是以分离的方式, 这就避免了 G 的模式崩塌.&lt;/p&gt;
&lt;p&gt;具体实现是这样的: 用 $f(x_i) \in \mathbb{R}^A$ 表示输入 $x_i$, D 的中间层的特征向量, 乘以一个张量 $T \in \mathbb{R}^{A\times B\times C}$, 得到一个矩阵 $M_i \in \mathbb{R}^{B\times C}$. 然后对各输入 $X_i$ ($i \in {1, 2, …, n}$) 计算得到的 $M_i$ 计算对应行的 L1 距离, 再对结果应用负指数运算, 即 $c_b(x_i, x_j)=exp(-|| M_{i, b} - M_{j, b}||_{L_1})$ (其中 b 表示对应的行). 再之后, 对 $x_i$ 与其他所有样本计算得到的所有的 $c$ 求和, 记作 $o(x_i)$, 用 $o(X)$ 表示 $o(x_i)$ 的矩阵. 将 $o(X)$ 与 $f(X)$ 串起来作为下一层的输入.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-26-papernotes_47/mini-batch_discrimination.png&quot; alt=&quot;mini-batch discrimination&quot;&gt;&lt;/p&gt;
&lt;p&gt;使用 Minibatch discrimination, D 除了判别真实与生成样本, 现在还可以利用样本与同一批里其他样本的不同作为额外信息.&lt;/p&gt;
&lt;p&gt;本文展示了, minibatch discrimination 能快速生成视觉上更真实的样本, 效果甚至超过特征匹配. 特征匹配则在半监督学习中训练一个强分类器更有帮助.&lt;/p&gt;
&lt;h3 id=&quot;historical-avearing&quot;&gt;Historical avearing&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Historical avearing 历史平均&lt;/code&gt; 的概念很直观, 就是在 G 和 D 的成本函数中加入 $|| \theta - \frac{1}{t} \Sigma_{i=1}^{t}\theta[i]||^2$ 这一项, 其中 $\theta[i]$$ 就是过去时间 i 对应的参数值.&lt;/p&gt;
&lt;p&gt;参数的历史平均可以通过在线学习的方式更新, 因此这种学习方式在长时间序列上也能很好地缩放.&lt;/p&gt;
&lt;h3 id=&quot;one-sided-label-smoothing&quot;&gt;One-sided label smoothing&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Label smoothing 单边标签平滑&lt;/code&gt;的概念很简单, 就是用 0.9 和 0.1 来代替原来是 1 和 0 的标签. (0.9 和 0.1 只是举例, 也可以使用 0.8~1.1 的随机数替换标签 1).&lt;/p&gt;
&lt;p&gt;本文指出, 使用双边标签平滑, 则最优 D 表示为 $D(x)=\frac{\alpha p_{data}(x) + \beta p_{model}(x)}{p_{data}(x) + p_{model}(x)}$. $p_{model}(x)$ 出现在分子中是有问题的, 因为当 $p_{data}$ 接近 0, 而 $p_{model}(x)$ 很大时, 来自 $p_{model}(x)$ 的错误样本就不会向更真实的数据改变.&lt;/p&gt;
&lt;h3 id=&quot;virtual-batch-normlization&quot;&gt;Virtual Batch Normlization&lt;/h3&gt;
&lt;p&gt;Batch Normalization 能极大地提高神经网络的优化效果. 但它也使得一个样本高度依赖于同一批的其他样本.&lt;/p&gt;
&lt;p&gt;为解决以上问题, 本文提出了 &lt;code&gt;Virtual Batch Normalization VBN&lt;/code&gt;, 训练过程中, 基于&lt;code&gt;reference batch&lt;/code&gt; 的统计量来归一化 x, 而 reference batch 在训练开始时选定并固定不变. reference batch 根据它自身的统计量进行归一化.&lt;/p&gt;
&lt;p&gt;使用 VBN 训练时需要在两个 Minibatch 上进行前向传播, 因此计算代价大. 本文只在 G 上使用.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;其他&quot;&gt;其他&lt;/h3&gt;
&lt;p&gt;本文提出了一个评估样本的方法: 将每张生成的图片输入 Google Inception Net, 得到条件标签分布 $p(y|x)$. 如果图中包含有意义的物体, 那么 $p(y|x)$ 应具有一个低熵. 此外, 使用 GAN 都希望生成不同的图片 (样本), 即边缘 $\int p(y|x=G(z))dz$ 应具有高熵. 因此, 结合两者, 得到了评估指标: &lt;img src=&quot;/img/2017-11-26-papernotes_47/inception_score_metric.png&quot; alt=&quot;inception_score_metric.png&quot;&gt;, 对结果应用指数运算的目的是后续更容易比较. 该评估方法称为&lt;code&gt;Inception Score&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;根据作者的经验, Inception Score 对于大量样本的多样性评估很有帮助.&lt;/p&gt;
&lt;p&gt;本文强调了图片标签对于图片品质的重要性. 原因是本文提出的 &lt;code&gt;Inception Score&lt;/code&gt; 与人工评估间存在高度相关性. 他们将这归因为人类的视觉系统对于能帮助推理物体类别的图片统计量更适应, 而对于无益于图片解释的局部统计量更不敏感.&lt;/p&gt;
&lt;h4 id=&quot;半监督学习&quot;&gt;半监督学习&lt;/h4&gt;
&lt;p&gt;本文提到一种半监督学习方法, 将 G 生成的样本加入数据集, 作为分类器的正常输入, 标记为第 K+1 类, 相应地将分类器的输出维度从 K 改为 K+1. 然后 $p_{model}(y=K+1|x)$ 就可以作为样本是假的概率, 对应于原始 GAN 框架中的 $1-D(x)$. 这样做的好处是, 只要知道真实的 K 类之一哪个能最大化 $log p_{real}(y \in {1, …, K})$, 就可以估计生成的无标签样本所属的类. Amazing!&lt;/p&gt;
&lt;p&gt;此时可以对损失函数进行分解, 如下 (为了方便, 直接贴图了):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-26-papernotes_47/semi-supervised_loss_function_decomposing.png&quot; alt=&quot;semi-supervised_loss_function_decomposing.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时 $L_{unsupervised}$ 实际上就是 GAN 的博弈值. 最小化 $L_{supervised}$ 和 $L_{unsupervised}$ 的最优解具有性质: $exp[l_j(x)]=c(x)p(y=i, x) \forall j&amp;lt;K+1$, $exp[l_{K+1}(x)]=c(x)p_G(x)$, 这意味着无监督损失和监督损失是一致的.&lt;/p&gt;
&lt;p&gt;该半监督学习的方法引入了 G 与分类器之间的交互. 并且作者们发现使用&lt;code&gt;特征匹配&lt;/code&gt;来优化 G 的效果特别好, 而 mini-batch discrimination 根本没起效.&lt;/p&gt;
&lt;p&gt;最后, 作者们表示 K+1 的分类器是过参数化的, 因为从所有输出对率中减去 $f(x)$ 并不会改变 softmax 的输出. 这意味着可以等效地固定 $l_{K+1}(x)=0 \forall x$, 这样 $L_{supervised}$ 就变成了原来 K 类的分类器的标准监督损失函数, 那么 D 可以表示为 $D(x)=\frac{Z(x)}{Z(x)+1}$, $Z(x)=\Sigma_{k=1}^{K}exp[l_k(x)]$&lt;/p&gt;
</content:encoded><category>笔记</category><category>深度学习</category></item><item><title>论文笔记</title><link>https://lifelonglove.cn/notes/papernotes_44/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/papernotes_44/</guid><description>串联 GAN 开山论文、生成模型综述与条件 GAN，记录核心目标和训练方法。</description><pubDate>Sun, 05 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;我的研究方向是&lt;a href=&quot;https://en.wikipedia.org/wiki/Generative_adversarial_network&quot;&gt;生成式对抗网络 GAN&lt;/a&gt;, 这段时间读的都是 GAN 的相关论文, 我会慢慢地把都读过的论文的笔记上传到 &lt;a href=&quot;https://github.com/Engine-Treasure/papernotes&quot;&gt;Github&lt;/a&gt; 以及博客上来. 由于本期涉及 GAN 的开山之作与一篇综述, 内容比较多, 暂时就放 3 篇笔记.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;generative-adversarial-nets&quot;&gt;&lt;a href=&quot;https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf&quot;&gt;Generative Adversarial Nets&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本文是 GAN 的开山之作.&lt;/p&gt;
&lt;p&gt;GAN 由两个神经网络组成, 一为&lt;code&gt;生成器 Generator&lt;/code&gt;, 一为&lt;code&gt;判别器 Discriminator&lt;/code&gt;. 生成器用于捕捉(真实)数据的分布; 判别器用于估计(输入它的)样本是真实数据的概率, 从而能够指导生成器的训练.&lt;/p&gt;
&lt;p&gt;GAN 存在唯一的解, 即&lt;code&gt;生成器能恢复真实的数据分布, 判别器的输出处处为 1/2&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对于以下相关工作的描述, 我自己也没有研究过, 不曾体会, 暂且做个记录.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GAN 是深度生成模型的一个变种. 但后者存在以下两个难点, GAN 避免了这两个难点.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;近似[(极大似然估计与相应策略)的概率计算]的难度很大; [此处加两个括号, 辅助断句, 读者请自行体会]&lt;/li&gt;
&lt;li&gt;在生成上下文使用分段线性单元难以获益.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;相关工作&quot;&gt;相关工作&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;深度玻尔兹曼机 DBM&lt;/code&gt; 广泛地使用似然函数, 因此需要对似然梯度的大量估计. 这些困难推动了&lt;code&gt;generative machine&lt;/code&gt;的发展, 这些模型不需要显式地表示似然, 能够从期望的分布中生成样本. 其中的一个例子是&lt;code&gt;Generative stochastic networks GSN&lt;/code&gt;, 它可以通过 BP 训练 而不需要大量的近似. 而 GAN 通过剔除 GSN 中使用的&lt;code&gt;马尔可夫链&lt;/code&gt;拓展了 generative machine.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Variational AutoEncoders VAEs&lt;/code&gt; 同样给可微的生成器网络配了另一个神经网络, 该网络作为识别模型, 用于近似推断. VANs 与 GANs 的不同之处在于, GANs 需要通过可见单元进行微分, 因此不能对离散数据建模; VAEs 需要通过隐藏单元进行建模, 因此不能有离散&lt;code&gt;潜变量&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Predictability minimization PM&lt;/code&gt; 也使用了两个神经网络相互竞争的思想. 其第一个网络的每个隐藏单元都训练为和第二个网络的输出不同, 而第二个网络预测每个隐藏单元在给定所有其他隐藏单元的值时的值. GAN 与 PM 的不同在于:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网络间的竞争是 GAN 的唯一训练标准; PM 只是鼓励隐藏单元统计独立的正则器, 竞争并不是主要标准;&lt;/li&gt;
&lt;li&gt;GAN 中的生成器生成丰富的高维数据, 作为判别器的输入, 力图使判别器最终无法判断数据的真假; 而 PM 比较两个神经网络的输出, 一个使输出相似, 一个使输出不同, 最终得到一个标量;&lt;/li&gt;
&lt;li&gt;GAN 是基于 minmax 博弈的, 而不是优化问题, 存在一个&lt;code&gt;值函数 Value Funtion&lt;/code&gt;, 判别器致力于增大其输出, 生成器致力于减小该值, 博弈在鞍点处停止; 而 PM 是一个优化问题, 方法是最小化观测函数.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;另一个与 GAN 的思想相似的是&lt;code&gt;Noise-contrastive estimation NCE&lt;/code&gt;, 它通过训练一个生成模型来学习能帮助区分判定性数据与固定噪声分布的模型权重. NCE 的限制是, 它的“判别器”定义为噪声分布与模型分布的概率密度比, 因此需要对两个概率密度都进行评估与 BP.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;对抗样例&lt;/code&gt;是对分类网络的输入直接应用基于梯度的优化方法找到的样例, 目的是找到与数据相似但会被误分类的样例. 因此对抗样例主要是一种分析工具. 但是对抗样例的存在, 意味着 GAN 的训练可能是低效的, 因为它使得判别器能够自信地识别一个类, 但缺少对人类对于物类的感知的模拟.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-05-papernotes_44/gan_architecture.png&quot; alt=&quot;GAN architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;GAN 的架构如上图所示. 生成器的输入为噪声, 通过训练其参数, 以恢复真实数据分布. 深度学习作为一种表示学习方法, 一般的深度神经网络对数据处理过程可以视为一个编码过程, 而此处的生成器可以视为一个解码过程.&lt;/p&gt;
&lt;p&gt;判别器则接受来自真实数据集或生成器生成的样本, 输出一个标量, 用以表示样本来自真实数据集的概率.&lt;/p&gt;
&lt;p&gt;GAN 的 minmax 博弈的值函数表示如下:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-05-papernotes_44/value_function.png&quot; alt=&quot;gan value function&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 GAN 训练的早期, G (以下均用 G 表示生成器, D 表示判别器) 非常差劲, 其输出结果明显不同于真实数据, 因此 D 能以绝对的自信判定真假, 输出接近 1 或 0 的概率, 导致 G 的梯度消失 ($log(1 - D(G(z)))$饱和), 从而难以训练 G. 此时使用最大化 $log D(G(z))$ 而不是最小化 $log(1-D(G(z)))$ 会有所帮助. 因为该观测值使得 G 与 D 的动态具有相同的定点, 因此能在训练早期提供更强的梯度. (此处, 我不明白)&lt;/p&gt;
&lt;p&gt;本文后面的内容从理论上证明了以下两项, 具体证明过程在此略去:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;minmax 博弈在 $\mathcal{p_g} = \mathcal{p_{data}}$ 达到全局最优解.&lt;/li&gt;
&lt;li&gt;当 G 与 D 有足够的容量时, 在给定 G 时, D 能达到最优值, 而 $\mathcal{p}$ 也能得到更新, 以提升 G.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实际应用中, GAN 通过函数 $G(\mathcal{z};\theta_g)$ 来表示 $\mathcal{p_g}$ 的一个受限族, 并且优化的是 $\theta_g$ 而不是 $\mathcal{p_g}$. 因此 GAN 实际缺乏理论保障.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;KL 散度&lt;/code&gt;用于度量一个概率分布偏离量一个分布的程度. 而 &lt;code&gt;JS 散度&lt;/code&gt; $D_{JS}=\frac{1}{2}(KL(P\mid\mid\frac{P+Q}{2}) + KL(Q\mid\mid\frac{P+Q}{2}))$ 可用于度量两个概率分布之间的相似性. GAN 训练的一个目标就是减小真实概率分布与生成概率分布之间的 JS 散度.&lt;/p&gt;
&lt;p&gt;GAN 的缺点:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;没有显示地表示 $\mathcal{p_g}$&lt;/li&gt;
&lt;li&gt;D 必须与 G 同时训练&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GAN 的优点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不需要马尔可夫链, 只需要 BP 来计算梯度&lt;/li&gt;
&lt;li&gt;学习过程中不需要推理&lt;/li&gt;
&lt;li&gt;可以使用多种技术&lt;/li&gt;
&lt;li&gt;G 不是直接依据数据样本来更新的, 而是依据判别器的梯度, 这意味着输入并不是直接拷贝进 G 的参数的&lt;/li&gt;
&lt;li&gt;可以表示非常锐化, 甚至退化的分布 (基于马尔可夫链的方法要求分布具有一定的模糊性, 以便链在模式间混合)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;generative-adversarial-networks-an-overview&quot;&gt;&lt;a href=&quot;https://arxiv.org/pdf/1710.07035.pdf&quot;&gt;Generative Adversarial Networks: An Overview&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是近期新出的 GAN 的综述.&lt;/p&gt;
&lt;p&gt;GAN 要求 G 与 D 必须是可微的, 但不要求它们是直接可逆的&lt;/p&gt;
&lt;h3 id=&quot;gan-的架构&quot;&gt;GAN 的架构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;LAPGAN&lt;/code&gt; 通过多次放缩, 对生成过程进行分解解决了(使用(相同容量)和(与监督学习的表示能力相同的 CNN)来训练 G 和 D 很困难)的问题, 并使用&lt;code&gt;条件卷积 GAN&lt;/code&gt;来生成每一层的图像. [此处的圆括号用于辅助断句]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DCGAN&lt;/code&gt; 利用了 strided 和 fractionally-strided 卷积来分别支持降采样与上采样操作. 这些操作能处理采样速率与采样位置的变化, 而这正是将图片空间映射到可能的低维潜变量空间及将图片空间映射到 D 的一个关键.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Conditional GAN&lt;/code&gt; 提供了对多模式数据生成的更好的表示.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;InfoGAN&lt;/code&gt; 将噪声源分解为不可再分解的源与一个&lt;code&gt;潜代码 latent code&lt;/code&gt;, 并试图通过最大化潜代码与 G 输出之间的交互信息来发现变量的潜在因子. 实验表明 InfoGAN 习得的表示在语义上是有意义的.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Adversarially Learned Inference, ALI&lt;/code&gt; 与 &lt;code&gt;Bidirectional GAN&lt;/code&gt; 是带推理模型的 GAN, 引入了推断网络, 能在 D 检测 (data, latent) 对时提供帮助. 而此时, G 由两部分组成, 除了常规的解码器, 还有一个额外的推断网络作为编码器.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-05-papernotes_44/cgan_infogan_ali_bigan.png&quot; alt=&quot;cGAN, InfoGAN, ALI/BiGAN&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Adversarial Autoencoders, AAE&lt;/code&gt; 将对抗的思想引入自编码器. 在潜变量空间与其期望先验分布间引入对抗训练, 从而得到一个组合损失函数, 它反映了重构误差以及由候选编码网络产生的先验分布有多不同的一种度量.
&lt;code&gt;Adversarial Variational Bayes, AVB&lt;/code&gt; 试图优化与 VAE 相同的标准, 但使用对抗训练的观测而非 KL 散度.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;训练-gan&quot;&gt;训练 GAN&lt;/h3&gt;
&lt;p&gt;理想情况下, 针对当前 G, D 会训练到最优; 然后训练 G. 但实际中, D 可能不会训练到最优状态, 只会进行有限的几次迭代训练, 并且 D 与 G 同时训练更新.&lt;/p&gt;
&lt;p&gt;GAN 实际是要找到一个鞍点, 因此梯度下降算法对于 GAN 的训练并不合适.&lt;/p&gt;
&lt;p&gt;真实数据只是高维空间中的一个低维流形, 因此训练 G 来恢复真实数据分布, 在空间中也只是很小的一块区域. 由于 $\mathcal{p_g}(x)$ 与 $\mathcal{p_{data}}(x)$ 很可能是不重叠的, 因此 D 可能总会以极高的自信判别真假&lt;/p&gt;
&lt;p&gt;当 D 最优时, 训练 G 等价于最小化 $\mathcal{p_g}(x)$ 与 $\mathcal{p_{data}}(x)$ 间的 JS 散度 (如上所述); 但 D 不是最优时, 对 G 的更新可能是无意义, 或者不准确的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GAN 的训练技巧&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同时对 G 和 D 使用 Batch Normalization, 能使训练更深的模型的过程更稳定&lt;/li&gt;
&lt;li&gt;最少化全连接层, 以提高训练更深模型的灵活性&lt;/li&gt;
&lt;li&gt;LeakyReLU 比 ReLU 具有更好的性能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;训练 GAN 的启发式方法:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;稍微修改 G 的观测目标能提高可获得的信息量. 特别地, D 的训练方法不变, 但训练 G 来匹配 D 的中间层的激活值&lt;/li&gt;
&lt;li&gt;小批量判别. 向 D 添加一个额外的输入, 该特征对小批量中的一个样本与其他样本的距离进行编码, 旨在防止&lt;code&gt;mode collapse&lt;/code&gt;, 因为此时 D 能轻易地知晓 G 是否总是输出同一个结果.&lt;/li&gt;
&lt;li&gt;启发式平均. 当参数偏离之前的均值时, 对其进行惩罚, 以保证博弈能收敛到一个均衡点.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Virtual BN&lt;/code&gt;. 在训练之前, 选择一个小批量的数据, 记为引用小批量, 以后每次训练, 都使用引用小批量的统计量, 从而减少小批量中某个样本对其他样本的依赖.&lt;/li&gt;
&lt;li&gt;one-sided 标签平滑. 即用略小于 1 的数字来表示标签 1, 这使得 D 的分类边界更加平滑, 防止了 D 的过度自信, 从而防止了 G 的梯度消失.&lt;/li&gt;
&lt;li&gt;在样本送入 D 之前, 向其添加噪声. Sønderby et al. 指出 one-sided 平滑会使得 D 偏离最优解的情况. 而使用实例噪声, 能使得真实流形与生成流形彼此更接近, 同时防止了 D 轻易就找到完全分离真假样本的判别边界. 实践中, 这通过同时向真假样本添加高斯噪声实现.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nowozin et al. 指出, GAN 的训练可能可以被泛化为不仅仅最小化 JS 散度, 而且最小化 f-散度的估计, 并提出了 f-GANs. f-GANs 在训练之初, G 的梯度不太容易消失. 这是因为, 训练 D 时, 估计了真假数据分布的 f-散度的偏差,而在训练 G 时, 仅最小化 f-散度的估计. f-GANs 后又得到扩展, 训练 D 时, 预测真假数据分布的比例; 训练 G 时, 直接最小化 f-散度.&lt;/p&gt;
&lt;p&gt;WGAN 使用了 Wasserstein 距离的近似作为成本函数, 这能为 G 提供更有用的梯度.&lt;/p&gt;
&lt;p&gt;最近的研究表明, &lt;code&gt;权重剪切 weight clip&lt;/code&gt; 较小了 D 的容量, 强迫它学习更简单的函数.&lt;/p&gt;
&lt;p&gt;BiGAN 和 ALI 提供了图片到潜变量空间的映射, 但重建的品质并不高&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;潜变量空间结构&quot;&gt;潜变量空间结构&lt;/h3&gt;
&lt;p&gt;潜变量空间的数据可以被高度结构化, 从而可能支持高级的语义操作&lt;/p&gt;
&lt;p&gt;一些 GANs 变种提供了从图片到潜变量映射的网络, 因此能够探索和利用结构化的潜变量空间. 通过编码器, 带标签的数据集可以映射到潜变量空间, 从而发现能表示高层属性的“概念向量”.&lt;/p&gt;
&lt;p&gt;Gurumurthy et al. 对&lt;code&gt;混合高斯分布&lt;/code&gt;的潜变量空间进行建模.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;gan-的应用&quot;&gt;GAN 的应用&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;此处仅对应用做一个简单的记录&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;分类与回归
&lt;ul&gt;
&lt;li&gt;GAN 训练完成之后, 神经网络可以复用于下游任务. D 的卷积层可用于特征提取, 只需要在其上添加简单的线性模型即可&lt;/li&gt;
&lt;li&gt;通过推断机制的联合训练, 能提供数据表示的品质. (当在训练路径中注入标签信息, ALI 取得了 state-of-the-art 的分类结果)&lt;/li&gt;
&lt;li&gt;在标签有限的情况下, 对抗训练可用于生成更多的训练样本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;图片生成
&lt;ul&gt;
&lt;li&gt;GAN 的条件化后来被扩展到自然语言中, Reed et al. 用 GAN 从文本描述中生成图片&lt;/li&gt;
&lt;li&gt;作为文本描述的扩展, GAWWN 条件于物体位置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;图片到图片的转化
&lt;ul&gt;
&lt;li&gt;pix2pix 用于图片到图片到“翻译”&lt;/li&gt;
&lt;li&gt;CycleGAN 通过引入循环一致性损失, 尝试在转化之后保留原始图片, 并反转转化过程.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;超分辨率
&lt;ul&gt;
&lt;li&gt;SRGAN 通过对抗损失组件扩展了之前的模型, 限制图片在自然图片的流形之上.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conditional-generative-adversarial-nets&quot;&gt;&lt;a href=&quot;https://arxiv.org/pdf/1411.1784.pdf&quot;&gt;Conditional Generative Adversarial Nets&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是一篇比较短小的论文. 就提出了一个概念: &lt;code&gt;同时向 G 和 D 输入相同的额外信息, 引导生成过程&lt;/code&gt;. 这些额外信息也称为条件, 可以是类标签, 或者是其他任何辅助信息.&lt;/p&gt;
&lt;p&gt;这个方法很实用. GANs 原来是漫无目的的生成样本的, 加入条件之后, cGAN (我喜欢称&lt;code&gt;条件 GAN&lt;/code&gt; 为 cGAN) 就真正实现了指哪打哪, 可以定向生成样本, 比如 MNIST 的数字 5 (Give me 5!)&lt;/p&gt;
&lt;p&gt;cGAN 的值函数见下, 架构见上.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-11-05-papernotes_44/cgan_value_function.png&quot; alt=&quot;cGAN Value Funtion&quot;&gt;&lt;/p&gt;
</content:encoded><category>笔记</category><category>深度学习</category></item><item><title>神: 算法, 无人驾驶x共享=真·无人驾驶, 智能与意识的分离</title><link>https://lifelonglove.cn/articles/brief_review_of_Homo_Deus/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/brief_review_of_Homo_Deus/</guid><description>读《未来简史》后，从算法可能成为新权威、无人驾驶与共享出行的结合，以及智能和意识逐渐分离三条线索，思考技术将如何重塑人文主义社会。</description><pubDate>Sun, 22 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;书中自有颜如玉.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近读完了两本书, 其一是尤瓦尔•赫拉利的“未来简史”. 和它的前辈“人类简史”一样, 都是难得的男女老少皆宜的好书, 力荐.&lt;/p&gt;
&lt;p&gt;“未来简史”的开篇与“人类简史”有大量的重合, 这对于没有读过“人类简史”的读者是一种较好的知识补充; 但对于读过“人类简史”的读者, 能帮助回顾, 但总的来说, 显得冗长.&lt;/p&gt;
&lt;p&gt;同“人类简史”一样, 书中的文字总是能让人感受到一种悲天悯人的情感. 是“人类简史”, 但书中却充斥着对其他物种的悲悯; 是“未来简史”, 但全书却在大谈&lt;em&gt;宗教&lt;/em&gt;, &lt;em&gt;科学&lt;/em&gt;, &lt;em&gt;人文主义&lt;/em&gt;, &lt;em&gt;人工智能&lt;/em&gt;等, 人文主义如何塑造了现代社会, 人工智能将如何改变现代社会. 两本书中随处可见对历史, 对人类, 对未来的各种思考. 重要的事情说第二遍, 力荐.&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;“未来简史”一书从过去一直讲到未来, 涵盖的内容非常多, 拣我自己感触最多的几点谈谈.&lt;/p&gt;
&lt;h3 id=&quot;如果有神-那一定是算法&quot;&gt;如果有神, 那一定是算法&lt;/h3&gt;
&lt;p&gt;很多著名数学家都相信上帝是存在的, 而且上帝是一位数学家. 因为宇宙中处处可见数学之美.&lt;/p&gt;
&lt;p&gt;作为一名人工智能领域的准从业者, 我也相信, &lt;strong&gt;如果神真的存在, 那么未来世界的算法就是神!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;塑造了现代社会的人文主义的主流&lt;strong&gt;自由主义&lt;/strong&gt;有三个假设:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个人是一个不可分类的个体;&lt;/li&gt;
&lt;li&gt;真正的自我是完全自由的;&lt;/li&gt;
&lt;li&gt;根据前两点, 每个人都比其他任何人更了解自己.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而&lt;strong&gt;生命科学&lt;/strong&gt;对这 3 个观点都提出了挑战:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生物就是算法, 人类不是不可分割的个体, 而是由可分割的部分组成;&lt;/li&gt;
&lt;li&gt;构成人类的算法并不“自由”, 而是由基因和环境压力塑造, 虽然可能依据决策论或随机做出决定, 但绝不“自由”;&lt;/li&gt;
&lt;li&gt;因此, 外部算法理论上有可能比人更了解Ta自己.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于以上观点, 近来的推荐系统是很好的证明, 而亚马逊是其中的典型代表之一. 以下是在亚马逊上进行的一些实验.&lt;/p&gt;
&lt;p&gt;当你第一次在亚马逊上购物, 它的推荐系统由于对你的购物习惯、购物需求一无所知, 就会根据其他用户的情况为你推荐商品. 比如以下是我以浏览器的访客模式登录中国亚马逊得到的推荐, 没有任何点击, 甚至没有任何滑动. 它为我推荐了相机. 多次刷新页面, 每一次都是相机. (声明: 我对相机没有任何需求.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-22-brief_review_of_Homo_Deus/amazon_recommendation_0.png&quot; alt=&quot;亚马逊第一次推荐&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在我想买一支日本百乐的钢笔. 于是搜索“&lt;strong&gt;百乐 钢笔&lt;/strong&gt;“, 根据提示选择”在办公用品中搜索“, 再装模作样点了 7 支钢笔. 然后返回首页. 它不再给我推荐相机, 转而推荐钢笔及其配件.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-22-brief_review_of_Homo_Deus/amazon_recommendation_1.png&quot; alt=&quot;亚马逊第二次推荐&quot;&gt;&lt;/p&gt;
&lt;p&gt;这次推荐的商品都是百乐的, 也许它推测我是一只百乐迷. 虽然我一开始只想买钢笔, 但它连墨水都推荐了.&lt;/p&gt;
&lt;p&gt;我一想, 对呀, 有了笔, 怎么能没有墨呢? 于是我又搜索“&lt;strong&gt;墨水&lt;/strong&gt;“, 同样”在办公用品中搜索“. 这次我把前 9 个不同品牌的墨水都点了个遍, 再返回首页. 它的推荐策略又改变了, 主推墨水, 但钢笔也没落下.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-22-brief_review_of_Homo_Deus/amazon_recommendation_2.png&quot; alt=&quot;亚马逊第三次推荐&quot;&gt;&lt;/p&gt;
&lt;p&gt;墨水是各种品牌的墨水, 而且都不是百乐的, 钢笔也不独独是百乐的 (前两款笔还是百乐的). 它揣测我, 我也来揣测它. 可能它在我点了各种品牌的墨水之后, 发现我并不是百乐迷. 但是我之前搜索钢笔的时候, 确实点的都是百乐的钢笔. 所以就有了现在的推荐.&lt;/p&gt;
&lt;p&gt;最后做一组实验, 现在我重定义我自己, 重新搜索“&lt;strong&gt;百乐 墨水&lt;/strong&gt; 在办公用品中搜索“. 这次的搜索结果很驳杂, 我只点了 4 款百乐的墨水.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-22-brief_review_of_Homo_Deus/amazon_recommendation_3.png&quot; alt=&quot;亚马逊第四次推荐&quot;&gt;&lt;/p&gt;
&lt;p&gt;除了一款派克的墨水 (第四款), 都是百乐的墨和笔. 也许它推测, 我有 85% (5/6) 的几率是百乐的忠实用户吧. 剩下一款派克的墨水, 是美亚的畅销商品.&lt;/p&gt;
&lt;p&gt;从这一系列实验可以看出, 外部算法真的懂人心意. 目前它只是记录了我极为有限的几次搜索与点击, 就取得了这样的了解. 如果我经常在亚马逊上闲逛购物, 它是否会比我更了解我自己: &lt;em&gt;在新商品上架之后, 也许我还没注意到, 系统就投我所好给我推荐了. 我惊讶于它的精准推荐, 但还是消费了, 于是它更智能了. (即使不消费, 它也更智能了)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;关于这个话题, “未来简史”书中使用的例子是总统大选. 它还提到一个概念, 叫&lt;em&gt;&lt;strong&gt;峰终定律&lt;/strong&gt;&lt;/em&gt;, 意思是&lt;em&gt;人会忘记绝大多数事情, 只记得几件极端的事件, 并对最近的事件赋予完全不成比例的高权重.&lt;/em&gt; 它在大选中的效果是这样的:&lt;/p&gt;
&lt;p&gt;在某位总统的任期上, 我 (选民) 对他的政治举措多有不满, 并发誓下次他要连任竞选的时候坚决不投他的票. 但是临近大选, 总统为竞选作出了很多改变, 颁布了许多便民政策, 竞选口号也很得人心. 我对他的感觉因此发生了改变, 之前对他的种种不满也被淡化, 最后还是投了他的票. 总统成功连任了.&lt;/p&gt;
&lt;p&gt;人会遗忘, 但计算机不会. 这时候如果有一个外部算法, 它记录了我长期以来对总统的观感, 即使最后我对他的态度有所改善, 但总的来说, 总统并不是我心目中的合格总统. 这一点, 算法不会忘. 而且, 它还能根据我的个性与习惯、生活与工作, 替我选出最能代表我利益的竞选人.&lt;/p&gt;
&lt;p&gt;如果为每个选民都配备一个算法, 那最终选出的总统是不是最能代表最广大人民群众的利益呢?&lt;/p&gt;
&lt;p&gt;虽然很多人都会矢口否认, 但事实是算法正在变得越来越智能, 而它或多或少已经比人更了解他自身了. 我相信, 在看得到的未来, 它会全面地比人更了解自身. 那时候算法就成了全知全能的神.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;银河帝国里的哈里·谢顿用一个数学公式推演了全人类的未来. 而统筹所有个人算法的算法也具有同样的能力.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;作为一个选择困难症患者, 几年以前, 我曾想过给自己写一个“决策器”, 输入事件或商品的信息, 由它替我做决定. 且不说创造的困难性, 放弃决策是一件多么恐怖的事情? 细思恐极.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;无人驾驶x共享-威力无穷&quot;&gt;无人驾驶x共享, 威力无穷&lt;/h3&gt;
&lt;p&gt;近年来, &lt;em&gt;自动驾驶&lt;/em&gt;一直是一个热议的话题, 全世界几乎所有的大公司都在开发自动驾驶技术. 而书中也有一段关于自动驾驶的描述, 当时我的真实感受是“真正的自动驾驶”. 原文摘录如下:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我自己也有一辆车, 但多半就是停着. 就我平常的一天来说, 早上8点04分开车, 半小时到大学, 把车挺好, 就放着一整天. 18点11分我又回到车上, 开车半小时回家, 就这样. 所以, 我每天只有1个小时会用到车, 何必另外23小时还要把车留着呢? 如果有个智能共乘系统, 就能解决这个问题. 我能让计算机知道我需要在8点04分离开家, 由计算机在那个时候把最近的无人驾驶汽车调过来, 准时让我上车. 把我送到大学之后, 这辆车就能用于其他目的, 而不用在停车场白白等待. 到了18点11分, 我走出大学校门, 就会有另一辆全民共享的无人驾驶汽车挺在我旁边, 带我回家. 这样一来, 只要有5000万辆共享的无人驾驶汽车, 就能取代10亿辆私家车, 而且所需的道路、桥梁、隧道和停车空间都会大大减少.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里描述的场景很有科技感和未来感. 它给了我的几点启发.&lt;/p&gt;
&lt;h6 id=&quot;1-为什么现在的分时租赁单车都美其名曰共享单车&quot;&gt;1. 为什么现在的分时租赁单车都美其名曰共享单车?&lt;/h6&gt;
&lt;p&gt;因为共享的本质就是对一件物品, 在时间上或空间上的分开的合理有效使用 (这不是一个严格的定义, 属个人观点). 分时, 在英文中是一个组合词&lt;strong&gt;time-sharing&lt;/strong&gt;, 这或许能给你一个更直观的体会.&lt;/p&gt;
&lt;p&gt;如果要给 2016 年定一个主题的话, &lt;strong&gt;共享&lt;/strong&gt;必为其一. 在资本遇冷的 2016 年, 共享单车成为为数不多的宠儿之一. 在资本的催熟下, 共享单车蓬勃发展.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-22-brief_review_of_Homo_Deus/sohu_bikes_app.jpg&quot; alt=&quot;共享单车 APP 霸屏&quot;&gt;
&lt;small&gt;图片来自搜狐: 共享单车 APP 霸屏了!&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;共享单车火了之后, 我看到一些学者揭露说, 这不过是分时租赁罢了 (我也是第一次接触到这个概念). 难道就不算共享了吗? 按照我个人的定义, 我觉得算.&lt;/p&gt;
&lt;p&gt;共享单车之后, 许多共享XX如雨后春笋般冒出来, 比如共享汽车、共享充电宝等等，都是分时租赁形式.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;插话&lt;/strong&gt;:
在共享单车之前, 许多城市就已经有了公共自行车的租赁服务, 或为政府主导的, 或为政企合作的. 为什么偏偏共享单车火了? 一个原因是无桩停车带来的极大便利 (这同时也带来了极大的混乱). 另一个重要原因则是流量从线上扩散到线下——使用智能手机就能方便地扫码解锁, 说走就走. 这里不得不提一下微信之父张小龙的一个观点, 他很早之前就提出: 搜索框是 PC 时代的流量入口, 二维码是移动互联网时代的流量入口. 诚不我欺. 想想你现在每天要和多少二维码打交道, 也许大宝并不天天见， 但二维码天天见.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6 id=&quot;2-为什么像-uber-lyft-这样的提供私家车共享服务的公司也在做自动驾驶&quot;&gt;2. 为什么像 Uber, Lyft 这样的提供私家车共享服务的公司也在做自动驾驶?&lt;/h6&gt;
&lt;p&gt;像宝马这样的传统车企和谷歌这样的高科技公司联合开发自动驾驶技术, 很好理解.传统车企有造车经验, 谷歌等公司则有技术储备.&lt;/p&gt;
&lt;p&gt;但是像 Uber, Lyft 这样的提供私家车共享服务的公司也在做自动驾驶就有点匪夷所思了. 难道它们要让自动驾驶技术和共享私家车的司机竞争, 淘汰掉司机, 也淘汰掉公司的现有业务吗?&lt;/p&gt;
&lt;p&gt;不然. 人类司机被自动驾驶技术淘汰掉是一种必然的趋势. 而自动驾驶最好的伙伴就是共享. 就像文中描述的, 自动驾驶x共享, 才能最大程度地发挥自动驾驶的威力, 也是对车辆最有效的利用.&lt;/p&gt;
&lt;p&gt;所以 Uber, Lyft 开发自动驾驶技术不是要淘汰掉公司的现有业务, 至少不是现在, 而是要把握住未来. 这些公司主营的共享乘车业务为自动驾驶提供了最广阔的平台. 可以说, 自动驾驶x共享, 是这些公司的必由之路, 要么张开双臂迎接未来, 要么被淘汰.&lt;/p&gt;
&lt;p&gt;如果有自主研发的自动驾驶技术, 它们就能推出自家的共享乘车服务, 减少其他公司的技术依赖. 在这里, 依赖是更大的成本和风险. 如果能尽早推向市场, 占领市场先机, 则是最好不过了. 我猜这也是 Uber 激进地进行自动驾驶路测的原因之一吧 (它之前在没有获得上路许可的情况下, 私自进行路测).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;智能与意识的分离&quot;&gt;智能与意识的分离&lt;/h3&gt;
&lt;p&gt;2017 年被称为人工智能元年. 你肯定或多或少都听说过了解过接触过人工智能, 前文的两个概念也都属于人工智能的范畴. 但你可能没听说过&lt;strong&gt;人工意识&lt;/strong&gt;这个概念.&lt;/p&gt;
&lt;p&gt;人工智能一词正式被提出是在 1956 年, 经过半个多世纪的发展, 有了巨大进展. 但是人工意识的发展几乎是零.&lt;/p&gt;
&lt;p&gt;人工智能在许多任务上已经超越了人类, 比如最近也许是第四火起来的 Alpha Go 系列, 许多学术界工业界的大佬也都承认, 人类也许只有在&lt;em&gt;创造力&lt;/em&gt;方面胜过人工智能. 而创造力属于意识的范畴.&lt;/p&gt;
&lt;p&gt;许多科幻小说和影视里, 都有一个超级人工智能体, 它往往被塑造为一个具有自我意识的个体. 就人工智能而言, 缺少了人工意识, 它的终极形态还会是影视作品中的超级智能体吗?&lt;/p&gt;
&lt;p&gt;人工意识得不到发展, 其原因是: &lt;strong&gt;人类自身对于意识都知之甚少.&lt;/strong&gt; 什么是意识? 意识它不是一个有机体, 它是一种主观的感觉, 像爱与悲伤这样的感觉. 但是我们不知道大脑是如何产生意识的, 也不知道为什么需要它.&lt;/p&gt;
&lt;p&gt;有一些科学家认为意识可能有极高的道德和政治价值, 但这在生物学上没有任何用处. 意识可能就是在复杂的神经网络信号传送之后造成的心理污染, 没有任何功用, 就是存在在那里罢了. 然而, 真是如此吗?&lt;/p&gt;
&lt;p&gt;如果相信人类的一些无意识活动, 那么以目前的发展趋势, 单凭人工智能, 是否能够发展成强人工智能, 最终成为超级人工智能?&lt;/p&gt;
&lt;p&gt;又或许, 意识只是另一种形式的算法, 只是我们还没发现而已?&lt;/p&gt;
&lt;p&gt;本节结合赫拉利的&lt;a href=&quot;http://www.iyiou.com/p/49572&quot;&gt;“人工智能与人工意识的”&lt;/a&gt;的演讲编译.&lt;/p&gt;
&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;希望本文能给你一些启发. 推荐, &lt;a href=&quot;https://book.douban.com/subject/26943161/&quot;&gt;未来简史&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>文章</category><category>有感</category><category>读书</category></item><item><title>微信的世界</title><link>https://lifelonglove.cn/articles/world_of_wechat/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/world_of_wechat/</guid><description>从早期语音聊天降低使用门槛谈起，观察微信如何借 QQ 用户、朋友圈与公众号形成全民社交基础设施，也反思超级应用集中注意力的影响。</description><pubDate>Sun, 15 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;如果评一个国内第一手机应用, 我觉得非&lt;strong&gt;微信&lt;/strong&gt;莫属.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;微信推出于 2011 年 1 月 21 日. 我是在 2011 年我高一的暑假接触到它的, 算是早期用户吧.&lt;/p&gt;
&lt;p&gt;在微信之前, 用 QQ, 都是文字聊天. 微信带来了语音聊天. 在我看来, 就是这样一个聊天方式的变化, 引爆了中国的社交网络: 对于我们 (90 后) 的父辈, 甚至祖辈, 他们可能不会拼音打字, 甚至不识字, 语音的方式正好适合他们. 这就为微信带来了海量的非 QQ 用户. 他们第一次接触微信, 就成为微信最忠实的用户. 微信最初的用户几乎都来自于 QQ, QQ 海量用户的涌入使微信赢在了起跑线上. 腾讯传里提到小米公司的米聊是与微信同期的移动聊天应用, 但因为缺少腾讯那样的用户积累, 败局早已注定. 言归正传, 这一批来自 QQ 的用户早期同时用着 QQ 与微信, 甚至使用 QQ 多于微信, 比如我自己. 但是同时维持着 QQ 和微信两套社交系统的人 (和同学聊 QQ, 和长辈聊微信) 是很难, 或者说不方便的, 既然在微信上也能联系上 QQ 上的同学, 而又只能在微信上与长辈们交流, 使用的重心就自然地逐渐从 QQ 转移到了微信. 这两部分用户一起, 构成了微信庞大的用户群..&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;题外话 1: 大概在 2010 年之前, 腾讯一直因为“抄袭”被诟病. 但是它总能成功. 我戏称这为: 一直在模仿, 一直在超越. 关于模仿, 腾讯传里马化腾给出了说法. 这里我想说明的一点是, 腾讯产品的成功, 首先是被模仿者的成功, 而腾讯能实现超越, 很大的原因就在于 QQ: QQ账号的一号多用以及 QQ 超强的粘性 (社交属性). 其他公司的成功产品像是孤岛, 而腾讯用 QQ 账号将所有这些孤岛都连接起来形成了一片蔚为壮观的大陆. 如今开放授权 (OAuth) 早已不是什么新鲜事了, 用一个 QQ/微信/微博账号就能“免注册”登录其他系统. 但在我看来, 给用户带来的似乎只是账号管理的便利, 而不像 QQ 账号那样有更强的连接属性.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-15-world_of_wechat/atlantic_ocean_way.jpg&quot; alt=&quot;/img/2017-10-15-world_of_wechat/atlantic_ocean_way.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;在我看来, 微信的海量大龄用户带来的另一个重要影响是中国智能手机产业的变革. 如果没有微信 (且不说历史会不会有另一款产品替代微信成为中国社交网络的宠儿), 也许很多大龄智能手机用户根本不会使用智能手机, 或者说使用智能手机的时间要往后延迟很长一段时间. 即使是现在, 很多大龄智能手机用户, 他们手机里都只有微信, 他们只用微信, 也只会用微信. 这些对微信的需求转化为对智能手机的需求, 可以不夸张地说, 微信一定程度上促进了中国智能手机产业的蓬勃发展. 所以不应该是腾讯为国内各大手机厂商付应用的预装机费, 而是手机厂商得向腾讯缴纳“人头税”.&lt;/p&gt;
&lt;p&gt;微信吸引海量用户的另一个重要原因, 很明显的: 简易. 微信的界面很简洁, 最常用功能的操作很方便. 发送语音, 视频聊天, 查找联系人, 查看朋友圈, 这些功能都是傻瓜式的 (褒义), 甚至不必识字, 记住头像和图标位置就能用了, 连我 70 多岁的奶奶和 5 岁的外甥都能上手. 腾讯有一条叫“全民XX”的产品线, 但真正“全民”的产品可以说只有微信一款.&lt;/p&gt;
&lt;p&gt;微信的两个最重要功能之一: 朋友圈. 正是朋友圈赋予了微信强大的社交属性. 我最习惯拿朋友圈和微博进行比较, 朋友圈像是更私密的, 圈子更小的微博. 但我个人不用微博, 因为微信使我养成的对简洁界面与简易操作的依赖, 使我无法接受微博的界面与操作 (我是不会告诉你, 与时俱进的我连发微博都不会); 另一个原因是: 有些事情, 不必毋须也不想与所有人分享, 朋友圈的范围刚刚好, 用户很乐于与朋友分享生活. 事实上, 微信朋友圈的原型应该是 QQ 空间的说说, 现在很多从 QQ 转移来的用户还习惯值称之为说说. 在我离开 QQ 之前, QQ 空间是任何人都可以访问的. 所以微信的一点点改变, 取得的是巨大的成功.&lt;/p&gt;
&lt;p&gt;我本人很喜欢朋友圈这个功能, 但是我关闭了它的入口, 因为“刷朋友圈”现象的存在. 这不止存在于我身上, 还存在于几乎所有朋友圈的使用者身上. 这是移动互联网时代的通病, 显然微信也不能免俗.&lt;/p&gt;
&lt;p&gt;关于微信朋友圈, 我暂时能列出有两个很奇怪的现象.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你在朋友圈看到的多是生活的正面. 如果以用户朋友圈的“说说”来写一本自传 (真的有类似的服务, 请自行搜索), 会发现这人生真是幸福快乐到令人羡慕. 但这样的虚拟人生, 与真实的人生有多少出入? 一般人也不会在网络上分享负面情绪. 这也是虚拟网络世界的通病, 或者不能称之为病, 是一种普遍现象, 我称之为&lt;strong&gt;双重人生&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;微信朋友圈的精准广告业务, 非但不招人烦, 而挺受欢迎的. 收到相同广告的朋友可以看到彼此的留言, 甚至会引发一波热议. 相对于其他形式的广告, 朋友圈广告能捕捉用户评论, 可以说是一种更有效的广告形式.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;微信的第二个最重要功能: 公众号. 它极大地降低了内容制作与分发的成本, 也由此诞生了大一批内容创业者与自媒体. 如果说聊天与朋友圈连接的是亲友, 那么公众号连接的就是企业/团体/自媒体人与用户. 而连接, 就是腾讯一直以来在做的事情.&lt;/p&gt;
&lt;p&gt;微信公众号极大地丰富了微信世界. 但也由于它的极大便利性, 也使得谣言与虚假信息得以更快速的传播, 泛滥成灾. 前文提到, 微信有着海量的大龄用户, 他们中的大多数又是低文化的. 海量未经审核的信息持续冲击着大众的认知, 即使是受过高等教育的用户有时也无法辨别真假, 更别提上述的低文化用户们了. 当然这不是微信的问题, 是技术的问题, 需要更高级的技术来弥补. 很高兴的是, 微信在这一方面从未懈怠过.&lt;/p&gt;
&lt;p&gt;也许绝大部分用户都没意识到, 微信的成功是苹果式的成功. 它自成一个封闭的生态: 所有的信息在微信内部产生, 也在微信内部被消化. 除了搜狗 (腾讯是搜狗的大股东) 拥有微信公众号的接口, 微信内的诸多信息在微信之外很难被搜索引擎搜索到. 今年上半年, 微信推出了搜索的功能, 可以方便地搜索朋友圈, 资讯, 公众号, 小说, 音乐, 表情, 另外还有微信指数. 这表明微信向“世界” (自成一体) 又迈出了关键一步. 至此, 微信已经张开成一张包罗万象的巨大网络, 它是互联网世界的一个&lt;strong&gt;全功能子网&lt;/strong&gt;: 微信搜索就是其中唯一的 “Google”, 微信指数也如同“百度指数”, 你的朋友圈和公众号资讯共同丰富了微信的网络世界, 这里还有各种插件式的功能, 购物有京东, 外卖有美团, 出行有滴滴, 最重要是还有你的联系人. 盗用“ONE一个” APP 的口号: 复杂的世界里, 一个微信就够了.&lt;/p&gt;
&lt;p&gt;今年年初的时候, 微信推出了“小程序”来为微信赋能, 使得微信更加无所不能. 这也是我说微信和苹果越来越像的另一个原因. 如果将微信看作 iOS 系统, 那么微信小程序就对应于 APP Store 里开发者开发的应用程序. 微信完全就是在 iOS/Android 系统之上又搭建了一层系统, 只不过它巧妙地借助微信实现了两者通吃. 微信也实现了从聊天应用/社交软件到微信平台的华丽转身. (微信公众平台也算, 但我本人认为小程序的推出更具有划时代的意义) 对用户而言, 可以不安装 APP, 只在需要的时候使用小程序即可, 这就是微信标榜的“用完即走”. 很多小程序的应用场景是线下, 所以连接了用户与用户, 用户与企业之后, 微信又连接了线上与线下. 真是“一网打尽”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-10-15-world_of_wechat/wechat_la.jpg&quot; alt=&quot;/img/2017-10-15-world_of_wechat/wechat_la.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;我最近又将微信小程序拿来与 Chrome 插件进行比较. 比起前者, 后者是更纯粹的赋能; 而前者是取代. 手机里的“僵尸应用”不必说了, 不用在角落里吃灰了, 以后就跟着微信老大哥混了; 其他应用可能会在微信上找到新的应用场景/新功能 (微信小程序推出之初, 一些公司就用来做原型), 也可能会因为微信分去了流量或者带来了更大的流量. 一开始, 我本人是微信小程序的重度使用者, 但用过大量小程序之后, 发现低频应用不在手机里吃灰却变成了在微信了吃灰. 但是不用安装对用户来说确实是一种便利. 但是, 这和以微信为浏览器访问网页应用又有多大区别呢?&lt;/p&gt;
&lt;p&gt;在微信小程序推出之前, Google 也有在研的小程序项目, 叫 Android Instant App. 但后者是将应用按功能分成不同的模块, 组合不同的模块就能实现一个功能组, 从而实现具体的某个 APP 应有的功能, 而一些基础功能模块是可以共享的. 一个具体的场景举例是: 朋友给你发了一条某网站的视频链接, 你不必去下载相应的应用, 系统会自动地下载对应的模组, 即可立即实现播放.&lt;/p&gt;
&lt;p&gt;最近有一个趋势, 曾经风靡的语音聊天, 在年轻人中, 在工作中越来越不受欢迎. 文字扫一眼, 1 秒钟之内就能获得一条消息的全部内容; 而音频可能是超长的, 人们用语音的趋势又是, 容易把简单的一句话扩展为冗长的一段话, 尽管文字和语音包含的信息量相等. 尽管微信早就推出了语音转文字功能, 但一来准确率不高, 二来还是多了一步操作, 文字消息在推送时就能立即看到. 使用语音的另一个问题是, 无论多长的语音, 都必须从头开始播放, 当一条语音由于各种原因被打断或其他原因需要重听时, 都必须重听前面的几十秒, 这不是问题, 而是灾难. 一些语音聊天应用早就实现了定点听语音, 这样一个简单那的功能对于腾讯这样的公司来说应该是很容易实现的. 但微信专注于扩展功能, 却没有解决真正的痛点. 这是不是可以说是一种不作为?&lt;/p&gt;
&lt;p&gt;曾经我大爱微信, 张小龙就是我心中的神. 但最近我喜欢上更纯粹的感觉, 喜欢为通讯而通讯, 为阅读而阅读. 当浏览微信公众号文章, 突然收到一条消息, 你是否也遇到与我相同的困扰, 是及时点击回复还是刷完再走, 抑或长按页面选择“以浏览器打开”? 因为公众号文章没有断点 (因为误操作退出了文章, 马上再点进去是能回到当前进度的), 这就一个问题, 造成一种很差的体验. 我现在浏览公众号文章都比较潦草, 除了一些早报会点进去看, 其他的文章会直接 TL;DR (Too long, Don’t Read), 或者根据标题的可读性选择稍后阅读, 同样不点进去. 我使用的是“墨加口袋” (有一个聊天人, 也有小程序), 但我最爱的是 Pocket, 由于微信的封闭性, 我现在又不得不维持两套稍后阅读系统. 而事实是, “墨家口袋”只稍微不阅读. 这种割裂感, 很不舒服.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;题外话 2: 文中我强调了很多次微信的封闭性, 也用了一个词“苹果式的成功”. 这种封闭在国内是普遍存在的, 比如在微信中分享淘宝/支付宝的链接, 会编码成一段文字, 用户必须复制-&amp;gt;打开应用-&amp;gt;粘贴-&amp;gt;跳转. 也许用户都已经习惯了这样的做法, 但这些都是竞争带来的恶劣用户体验. 多年之后再回过头来看这段历史, 当年一拍脑门决定这种做法的人将成为笑话. 在今天 9 月之前, 听五月天用虾米, 听周杰伦用 QQ 音乐, 好像偌大的互联网被拆分成小的局域网. 运营商给了你上网的能力, 但互联网公司告诉你: 你有自由选择的权利, 但它们的“能力有限”. 阿里与腾讯的音乐相互授权只是中国互联网泛起的一朵小小涟漪, 大势依旧是割裂. 说到这, 我突然觉得中国联通推出的各种卡, 是多么 RIDICULOUS. 这难道不是中国互联网局域网化最好的体现吗? BATJ 等各大公司纷纷出资参与中国联通的混改, 会强化还是取消各种王/宝/神卡?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;以上, 就是全部了.&lt;/p&gt;
</content:encoded><category>文章</category><category>有感</category></item><item><title>吴恩达深度学习第三课笔记</title><link>https://lifelonglove.cn/notes/andrew_ng_dl_course3_note/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/andrew_ng_dl_course3_note/</guid><description>记录误差分析、正交化策略、训练与测试分布，以及迁移学习的项目决策方法。</description><pubDate>Wed, 23 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;course-3&quot;&gt;Course 3&lt;/h2&gt;
&lt;h3 id=&quot;week1-machine-learning-strategy-1&quot;&gt;week1: Machine Learning Strategy 1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;训练时可采取的一些机器学习策略罗列:
&lt;ul&gt;
&lt;li&gt;收集更多数据&lt;/li&gt;
&lt;li&gt;收集更多样的训练集&lt;/li&gt;
&lt;li&gt;使用梯度下降时, 训练更长时间&lt;/li&gt;
&lt;li&gt;尝试用 Adam 代替梯度下降&lt;/li&gt;
&lt;li&gt;尝试更大/更小的神经网络&lt;/li&gt;
&lt;li&gt;尝试 Dropout regularization&lt;/li&gt;
&lt;li&gt;增加 L2 regularization&lt;/li&gt;
&lt;li&gt;调整神经网络架构
&lt;ul&gt;
&lt;li&gt;替换激活函数&lt;/li&gt;
&lt;li&gt;增减隐层单元数&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;正交 Orthogonalization&lt;/code&gt; 的重要性, 以机器学习中的链式假设为例进行说明: 当我们训练一个模型时, 首先希望它在训练集上有一个好的表现, 有一些技术手段能做到这一点, 比如更大的神经网络, 使用 Adam 算法等; 前一点得到保障之后, 我们希望 dev set 也能表现得足够好, 也有一些技术手段, 比如正则化; 然后, 希望模型在测试集上也能有好的表现, 也有一些技术手段, 比如使用更大的 dev set; 最后希望模型在现实世界表现得足够好. 每一步, 都有一些技术手段可以使用, 我们最好使用&lt;code&gt;相互正交的技术手段&lt;/code&gt;, 即在一个阶段使用的技术手段, 不会影响另一个阶段. 这样, 我们就能独立地改进模型在不停阶段的性能, 而不必担心对其他阶段造成太大影响. (更直观的例子: 开车的时候, 方向盘控制方向, 油门控制加速, 刹车控制减速; 但如果将加减速也加入方向盘的功能, 结果很可能会事与愿违)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;早停 Early Stopping&lt;/code&gt; 既会影响模型在训练集的表现, 也会影响其在 Dev set 上的表现, 根据正交性原则, 是一个不太可取的技术手段&lt;/li&gt;
&lt;li&gt;设置一个&lt;code&gt;评估指标 Evaluation Metric&lt;/code&gt; 来挑选模型, 一般都是一个数值型的量, 方便量化比较模型
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;准确率 Accuracy&lt;/code&gt;, &lt;code&gt;错误率 Error&lt;/code&gt; 是最简单的评估指标. 提供了样本被误分类的信息. Error 可以理解成是错误预测的总和除以预测的总数; Accuracy 则是正确预测的总数除以预测的总数, 或者说 1-ERR&lt;/li&gt;
&lt;li&gt;&lt;code&gt;真正率 (true positive rate, TPR)&lt;/code&gt; 和&lt;code&gt;假正率 (false positive rate, FPR)&lt;/code&gt; 是对于类别不平衡问题特别有用的评估指标. _假正率_是假正例 (实际标签为假, 被误判为真) 的总数除以真实反例的总数; _真正率_是真正例的总数除以真实正例的总数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;查准率 Precision (有时也翻译作精确率)&lt;/code&gt;: 几乎是真正率的同义词. 计算是: 真正例的总数除以预测正例的总数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;查全率 Recall (有时也翻译作召回率)&lt;/code&gt;: 计算公式是: 真正例的总数除以正例的总数 (真正例数 + 假反例数).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F1 Score&lt;/code&gt;: 一项综合考虑 Precision 和 Recall 的评估指标, 定义为 Precision 和 Recall 的&lt;code&gt;调和平均数 Harmonic Mean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不同的应用场景, 优先考虑的评估指标有所不同. 比如疾病检查, 患者关心的是得病的几率, 他在乎的是查准率 (这也是为什么, 此处优先如此翻译); 但比如疾病预防, 医院希望将某疾病的病患都找出来进行隔离治疗, 这时医院可能优先考虑查全率, 宁抓错, 不放过, 如是也.&lt;/li&gt;
&lt;li&gt;Dev set 搭配上单个实值评估指标, 能够加速迭代&lt;/li&gt;
&lt;li&gt;&lt;code&gt;满足与优化指标 Satisficing and optimizing metrics&lt;/code&gt; 说的是, 当考虑多个评估指标时, 选择一个作为&lt;code&gt;优化指标&lt;/code&gt;, 其他的作为&lt;code&gt;满足指标&lt;/code&gt;. 顾名思义, 优化指标就是训练模型要不断提升/降低的量, 比如错误率; 而满足指标就是, 只要模型的性能在设定的容忍范围内即可, 而不在此范围内的, 均不予考虑, 比如内存消耗在 10 MB 以内.&lt;/li&gt;
&lt;li&gt;重要的事情说第二遍: 训练集与 (验证集, 测试集) 的数据分布可以不同, 但验证集与测试集的数据分布必须相同.&lt;/li&gt;
&lt;li&gt;验证集的目的, 是为了从多个训练好的模型中, 选择一个最优的. 因此, 只要能够区分不同算法/模型的性能就够了, 不必太大&lt;/li&gt;
&lt;li&gt;测试集的目的, 是为了检验最终模型的泛化能力, 也不必太大.&lt;/li&gt;
&lt;li&gt;有些情况下, 指标+验证 (metric+dev) 选出的最优模型在实际应用中不一定是最好的, 比如涉及情色内容的分类器. 此时, 就要对评估指标进行调整, 一种方法是使用&lt;code&gt;权值 weights&lt;/code&gt;. 仍然以涉及情色内容的分类器为例, 原来, 我们使用的错误率计算是这样的: $$\frac{1}{m}\Sigma I(y_pred==y)$$ (其中 I 是指示函数, m 是样本容量); 现在我们要坚决反情色内容, 可以这样 $$\frac{\Sigma w*I(y_pred==y)}{\Sigma w}$$ 计算错误率, 为非情色图片设置很小的权值, 情色内容设置很大的权值, 那么误判情色图片就会造成一个极大的误差, 使最终的误差变大, 降低该分类器的指标.&lt;/li&gt;
&lt;li&gt;我们通过指标来评估模型的性能. 但根据正交的原则, 更恰当的做法是, 先根据要实现的目标, 定义好指标 (英文的描述比较通畅: figure out how to define a metric that captures what you want), 简而言之就是, 先设定好目标; 然后, 训练模型向目标看齐, 不断提升性能, 提升指标. 这样分两步的方式看似与之前没差别, 但实际上差别就在于一个是先定目标, 然后训练; 一个是不管怎样, 先训练, 然后选一个指标来评估其性能. 差别还是很大的&lt;/li&gt;
&lt;li&gt;一种评估机器学习性能的方式是和人比, 这里就引入了 &lt;code&gt;human-level&lt;/code&gt; 的概念. 机器学习的准确度 Accuracy 提升有两道坎, 一道是 human-level, 人类非常擅长一些工作, 比如判断是猫与否, 因此将其设为第一道坎, 表达了对人类的尊重; 另一道坎是 &lt;code&gt;贝叶斯可能误差 Bayes Possible Error&lt;/code&gt;, 这是学习任务的理论最优值, 可以说是, 看得到摸不着的天花板.&lt;/li&gt;
&lt;li&gt;当机器的水平逊于人类时, 要提升其性能, 可以参考以下几点:
&lt;ul&gt;
&lt;li&gt;获得人类标记的数据&lt;/li&gt;
&lt;li&gt;手动分析错误, 思考人是如何判断的&lt;/li&gt;
&lt;li&gt;分析 bias/ variance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Course 2 的笔记中提到过: 训练误差与人类水平 (误差) 之间的差距, 吴恩达老师称为&lt;code&gt;可避免偏差 avoidable bias&lt;/code&gt;, 训练误差与验证误差之间差距, 称为&lt;code&gt;方差 variance&lt;/code&gt;. 根据此二者, 有四种情形: 低/高偏差, 低/高方差. 当偏差较大时, 应首先降低偏差, 提高模型在训练集上的性能; 方差较大时, 应关注降低方差.&lt;/li&gt;
&lt;li&gt;Human-level error is  a proxy for Bayes error.&lt;/li&gt;
&lt;li&gt;不同人, 对于相同任务的水平是不一样的: 专业的和业余的, 水平就明显不一样; 一群专业的, 水平一般也会比一个人高. 根据上面那句准则, 人类的最高水平就代表了 Human-level. 当然, 具体情况还得具体分析.&lt;/li&gt;
&lt;li&gt;在一些任务方面, 机器已经吊打人类了, 比如 TI7 上, AI 就击败了 Dendi. 当机器不如人类的时候, 学习任务/训练模型, 有一条比较清晰的脉络; 当机器超过人类之后, 一些情况下, 训练就会像陷入泥泞一样, 难于提高, 因为没有清晰的目标了.&lt;/li&gt;
&lt;li&gt;机器胜过人类的场景:
&lt;ul&gt;
&lt;li&gt;在线广告&lt;/li&gt;
&lt;li&gt;产品推荐. 所有有时候找人推荐, 不如上亚马逊咯&lt;/li&gt;
&lt;li&gt;对率回归&lt;/li&gt;
&lt;li&gt;语音识别&lt;/li&gt;
&lt;li&gt;一些图片识别&lt;/li&gt;
&lt;li&gt;借贷分析&lt;/li&gt;
&lt;li&gt;医药&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;监督学习的两个基本假设:
&lt;ol&gt;
&lt;li&gt;训练能得到很好的性能&lt;/li&gt;
&lt;li&gt;训练集的性能能够很好地泛化&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;减小 avoidable bias 的一些手段:
&lt;ul&gt;
&lt;li&gt;训练更大的模型&lt;/li&gt;
&lt;li&gt;训练更长时间/使用更优的优化算法&lt;/li&gt;
&lt;li&gt;调整神经网络架构/超参数搜索&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;减小方差的一些手段:
&lt;ul&gt;
&lt;li&gt;更多的数据&lt;/li&gt;
&lt;li&gt;正则化 (L2, dropout, 数据增强)&lt;/li&gt;
&lt;li&gt;调整神经网络架构/超参数搜索&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;week2-machine-learning-strategy-2&quot;&gt;week2: Machine Learning Strategy 2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;深度学习对于训练集的随机误差有相当强的鲁棒性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;误差分析 Error Analysis&lt;/code&gt; 就是分析错误, 找出原因. 一种方法是表格分析法: 比如分类任务, 选择一定数量的误分类样本, 列出一系列误分类的原因, 然后统计分析, 找出主要原因, 或定位易解决的原因. 下图是表格分析的一个例子&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-23-andrew_ng_dl_course3_note/chart_error_analysis.png&quot; alt=&quot;chart_error_analysis.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要处理误标记的问题, 应同时处理验证集与测试集的误标记样本, 确保同分布. 不仅误标记处理, 任何对验证集的处理, 都应以相同的方式再处理测试集.&lt;/li&gt;
&lt;li&gt;必要时候, 可以对正确分类的数据也进行分析 (也许存在错错得对的情况也说不定)&lt;/li&gt;
&lt;li&gt;观察验证样本, 思考如何改进. 比如猫的分类器, 当考虑是否有必要更好地处理狗以获得更好的性能时, 就可以取一定数量的误分类的验证集样本, 统计狗的数量, 然后决定是否要实现对狗的强化识别.&lt;/li&gt;
&lt;li&gt;上述的表格分析法, 实际上就能让我们同时考虑多个方法.&lt;/li&gt;
&lt;li&gt;要进行深度学习, 要做的第一件事情是: &lt;code&gt;快速构建第一个系统, 然后迭代&lt;/code&gt;, 验证想法, 早发现早治疗.&lt;/li&gt;
&lt;li&gt;当训练集与 (验证集, 测试集) 具有不同的数据分布时, 混合所有样本随机洗牌划分数据集是错误的处理方式. 正确的处理方式是, 从同分步的验证集, 测试集中取出部分样本加入训练集. 很多时候, 海量的训练数据是容易获得的, 而验证测试数据是稀缺的.&lt;/li&gt;
&lt;li&gt;还有一种数据划分方式是, 从训练集中划出少量样本作为 training-dev set, 其与训练集同分布, 但不用于训练. 此时, train set error 与 training-dev set error 之间的差距称为方差, training-dev set error 与 dev error 之间称为 &lt;code&gt;数据不匹配 data mismatch&lt;/code&gt;, dev error 与 test error 称为&lt;code&gt;验证集的过拟合程度 degree of overfitting to dev set&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;解决数据不匹配问题的几种方法:
&lt;ul&gt;
&lt;li&gt;手动分析误差, 理解训练集与验证/测试集的差异&lt;/li&gt;
&lt;li&gt;使训练数据更接近验证/测试数据; 或者收集更多与验证/测试数据相似的数据加入训练集. 一种可用的技术手段是: &lt;code&gt;人工数据合成 artificial data synthesis&lt;/code&gt;. 比如对于语音识别, 将人声与噪声进行合成, 得到较真实的现实场景的声音数据. 这时会带来的一个问题是, 如果使用同一份噪音, 可能会造成过拟合, 因为噪声也被当作了有价值的特征.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;迁移学习 transfer learning&lt;/code&gt;, 简而言之就是将从某一学习过程得到的知识应用到新的领域. 目前这要求, 任务是相近的. 比如放射诊断只有不多的数据, 对于训练一个高准确率的深度神经网络远远不够, 这时就可以借用一个训练好的图片识别深度神经网络的大部分层次, 只替换掉接近输出层的部分层次, 之后通过简单训练, 就得到了放射诊断的深度神经网络. 这就是迁移学习, 它将图片识别的知识迁移到了放射诊断, 理论依据是, 图片识别与放射诊断的低层特征是相似的, 比如线条的特征, 这就是两个学习任务共同的知识. 如下图所示:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-23-andrew_ng_dl_course3_note/illustration_of_transfer_learning.png&quot; alt=&quot;illustration_of_transfer_learning.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上面提到, 迁移学习目前的要求是, 学习任务是相近的, 知识是相通的. 可以把图片识别的知识迁移到放射诊断, 但不能迁移到语音识别.&lt;/li&gt;
&lt;li&gt;迁移学习另一个比较硬性的要求是, 大训练集训练得的知识可以迁移到小训练集的学习任务. 但反之被认为是不可取的, 一来这样的知识不可靠, 二来没有太大意义.&lt;/li&gt;
&lt;li&gt;总结一下迁移学习的一些前提条件:
&lt;ul&gt;
&lt;li&gt;任务 A, B 具有相同的输入&lt;/li&gt;
&lt;li&gt;要将任务 A 习得的知识迁移到任务 B, 要求任务 A 的数据量大于 B&lt;/li&gt;
&lt;li&gt;A 的低层特征对学习任务 B 有帮助&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;多任务学习 Multi-task learning&lt;/code&gt;, 简而言之就是在一个训练任务中, 同时学习多种经验. 以自动驾驶为例, 车辆需要识别行人, 其他车辆, 交通标志, 信号灯等, 从包含以上多个目标的图片中进行识别训练, 那么同一个训练过程, 就需要识别出多个物体. 每个物体的识别, 都是一项学习任务, 因此, 同时识别多个物体就属于 &lt;code&gt;多任务学习&lt;/code&gt; 了.&lt;/li&gt;
&lt;li&gt;多任务学习的样本输出值是一个 (n, 1) 维向量, 其中 n 是学习任务数. 假设以 0-1 标记图片中各物体出现与否, 多任务学习的输出有点类似 one-hot 向量. 但不同于 one-hot 向量有且仅有且必须有一个元素被标记为 1, 多任务学习的输出向量可以认为就是单任务学习的输出的组合, 每个位置上的输出值都表示图片中是否存在对应物体. 这就要求图片的标签, 即 y 也是一个 (n, 1) 维向量. 相应地, 多任务学习的&lt;code&gt;损失函数 Loss&lt;/code&gt; 也是一个向量&lt;/li&gt;
&lt;li&gt;有时候, 多任务学习的样本标签带有一些问题标记, 通常用 ? 表示. 只要不是全是问题标记的情况, 该样本依旧可以用于训练, 只是最后处理的时候忽略该项, 或采用其他技术手段即可. 而如果直接丢弃该样本, 那对于现实生活中存在的大量信息缺失情形, 可用于训练的数据将大大减少, 因此要不抛弃不放弃.&lt;/li&gt;
&lt;li&gt;总结一下多任务学习的一些前提条件:
&lt;ul&gt;
&lt;li&gt;学习的一系列任务, 都能从共享的低层特征中收益. 比如图片中多物体的识别, 低层特征如线条就是共享的, 所有的物体识别都能从中获益. 此处有一个推论, 就是得益于特征共享, 多任务学习的性能会比独立训练多个单任务神经网络更好.&lt;/li&gt;
&lt;li&gt;通常, 各学习任务的数据量是相近的. 以自动驾驶为例, 要学习识别行人, 车辆, 交通标志, 信号灯, 那么, 能够识别出行人, 车辆, 交通标志, 信号灯的样本数量应该是相近的. 一个直观的感觉是, 有 1000000 个样本, 其中 999000 能识别出车辆, 只有 1000 样本能识别出行人, 那么行人不是更像噪声吗&lt;/li&gt;
&lt;li&gt;多任务学习的一个假设是: 只要训练一个足够大的神经网络, 就能在各项任务上都有足够好的性能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;端到端学习 end to end learning&lt;/code&gt;, 简而言之就是没有中间过程, 从输入直接到输出. 以语音识别为例, 传统的做法是, 1) 从音频中提取特征; 2) 从特征中提炼音素; 3) 构词; 4) 转录成文本. 而端到端学习, 就是简单粗暴的从音频到转录文本. 如下所示:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-23-andrew_ng_dl_course3_note/illustration_of_end-to-end-learning.png&quot; alt=&quot;illustration_of_end-to-end-learning.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;端到端学习的优缺点:
&lt;ul&gt;
&lt;li&gt;优点:
&lt;ul&gt;
&lt;li&gt;让数据发声&lt;/li&gt;
&lt;li&gt;只需要更少的人工参与&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点:
&lt;ul&gt;
&lt;li&gt;可能需要大量的数据, 大到足以让数据发声&lt;/li&gt;
&lt;li&gt;剔除了潜在的有帮助的人工参与&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;人工参与到机器学习/深度学习的各环节, 可以将人的经验, 知识等注入系统, 也许能帮助系统更好更快的学习. 此外, 将端到端学习拆分成多个相对独立的学习过程, 有点分治的味道, 易于实现, 性能也可能比设计不佳的端到端学习更好.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>机器学习</category></item><item><title>吴恩达深度学习第二课笔记</title><link>https://lifelonglove.cn/notes/andrew_ng_dl_course2_note/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/andrew_ng_dl_course2_note/</guid><description>整理正则化、初始化、梯度检查、优化算法与超参数调试等深度网络训练实践。</description><pubDate>Tue, 22 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;course-2-improving-deep-neural-networks-hyperparameter-tuning-regularization-and-optimization&quot;&gt;Course 2: Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization&lt;/h2&gt;
&lt;h3 id=&quot;week-1-practical-aspects-of-deep-learning&quot;&gt;week 1: Practical Aspects of Deep Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;训练深度学习模型是一项 empirical 的任务:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/idea_code_experiment.png&quot; alt=&quot;idea_code_experiment.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统机器学习由于数据量比较小, 通常将数据分成 7:3 这样的 train/test 集, 或 6:2:2 这样的 train/dev/test 集; 但深度学习由于用到大量的数据, 不必为 dev 或 test 分配太多的数据, 应将绝大多数数据用于 train, 因此一般性的数据划分可以是 98:1:1 或 99.5:0.4:0.1 这样的.&lt;/li&gt;
&lt;li&gt;training set 与 dev/test 的数据分布可以不一样, 但务必确保 dev 与 test 的数据具有相同的分布.&lt;/li&gt;
&lt;li&gt;设立 dev 的目的是, 从训练得到的多个模型中找到最优的模型, 而 test 的目的是检验选择的模型的泛化能力. 因此可以不要 test set. 上述的 train/test, 更确切的说法应为 train/dev.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;高偏差 high bias&lt;/code&gt; 对应&lt;code&gt;欠拟合 underfit&lt;/code&gt;, &lt;code&gt;高方差 high variance&lt;/code&gt; 对应 &lt;code&gt;过拟合 overfit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;训练误差远高于&lt;code&gt;人类水平 human level&lt;/code&gt;, 意味着高偏差, 欠拟合; 检验误差与训练误差很大, 意味着高方差, 过拟合. (对于某项任务, 人类误差也很大的情况下, 训练误差很大, 但接近人类水平, 不称为欠拟合) (训练误差与人类水平 (误差) 之间的差距, 吴恩达老师称为&lt;code&gt;可避免偏差 avoidable bias&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;basic recipe for machine learning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/basic_recipe_for_machine_learning.png&quot; alt=&quot;basic_recipe_for_machine_learning.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如上图所示, 首先要保证 low bias (考究的是训练集的性能), 如果偏差很大, 尝试更大的神经网络, 比如训练更长时间, 修改模型; 在保证了 low bias 之后, 要检验 variance, 即验证集的性能, 如果方差很大, 可以尝试更多的验证数据, 或者通过&lt;code&gt;正则项 regularization&lt;/code&gt; 引入惩罚. 最终得到低偏差低方差的模型&lt;/li&gt;
&lt;li&gt;减小偏差的技术, 可能会引起方差增大; 反之亦然. 这称为 &lt;code&gt;偏差-方差平衡 bias-variance trade-off&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;正则化, 实际上是引入了对参数 (w, b) 的新的限制. 比如增加了 L2 正则项的成本函数, 最小化成本函数, 所求的是 L2 范数限制下的参数. 以下是一张很好的示意图:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/L2_regularization_illustration.png&quot; alt=&quot;L2_regularization_illustration.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;图片截自&quot;Python Machine Learning - Sebastian Raschka&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上图中以交点作为最优的参数值的数学依据是: $$a+b&amp;gt;=2 \sqrt{ab}, 当且仅当 a=b 时, 等号成立$$&lt;/li&gt;
&lt;li&gt;添加 L1 正则项, 将得到稀疏向量 (可用于特征选择), 示意图如下:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/L1_regularization_illustration.png&quot; alt=&quot;L1_regularization_illustration.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;图片截自&quot;Python Machine Learning - Sebastian Raschka&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;L2 正则化&lt;/code&gt;有时候也被称为 &lt;code&gt;weights decay (权重衰减)&lt;/code&gt;. 因为在添加了 L2 正则项的情况下, 反向传播时, $$dW^[l]$$ 也多了一项 $$\lambda \frac{W^{[l]}}{m}$$. 权值更新如下所示. $$1-\frac{\alpha \lambda}{m} &amp;lt; 1$$, &lt;code&gt;weights decay&lt;/code&gt; 因此得名:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/weights_decay.png&quot; alt=&quot;weights_decay.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正则化能够防止过拟合, 一个理解角度是: 添加了正则项之后, 模型将更偏好于较小的权值. 假设正则化系数 $$\lambda$$ 足够大, 那么权值将趋向于 0, 这相当于闭掉了一些神经元, 模型更简单了, 由参数确定的分割超平面更平滑.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/regularization_prevent_overfitting.png&quot; alt=&quot;regularization_prevent_overfitting.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正则化能够防止过拟合, 另一个理解角度是: 以 tanh 激活函数为例, 随着正则化系数 $$\lambda$$ 增大, 权值 $$W^{[l]}$$ 减小, 则 $$z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}$$ 也减小 (暂时忽略 b), 对于 tanh 函数而言, 在 z 较小的情况下, 函数几乎是线性的. 大量线性单元构成的神经网络, 也可以简化为一个线性模型. 由此, 正则化后的分割超平面也就更平滑了, 防止了过拟合.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/regularization_prevent_overfitting_2.png&quot; alt=&quot;regularization_prevent_overfitting_2.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Dropout regularization&lt;/code&gt; 对每个样本进行训练时, 每一层的每个神经元都以一定概率关闭, 这样每次训练都得到一个较小的神经网络, 以这个较小的神经网络对样本进行学习, 从而起到正则化的作用.&lt;/li&gt;
&lt;li&gt;一种最常用的 Dropout regularization 是 &lt;code&gt;Inverted Dropout&lt;/code&gt;: 该算法对每一层, 首先随机初始化一个与 a 大小相同的向量, 根据每个元素与固定概率 k 的关系, 得到一个布尔向量 d (或者说 0-1 二值向量), 然后原激活函数值向量 a 乘以 d, 得到新向量 a, 由于 d 中存在 0 元素, 原 a 向量中的元素乘以 0, 结果还是 0, 相当于被屏蔽了. 最后为了保持该层输出不变, 变换后的 a 向量除以 k, 弥补“屏蔽”带来的损失.&lt;/li&gt;
&lt;li&gt;不同于一般的正则化, Dropout regualarization 只在训练时使用, 测试预测时, 不使用.&lt;/li&gt;
&lt;li&gt;对于 Dropout regularization 防止过拟合的一种理解是: 任一特征都是不可靠的, 每次都对上一层所有输出的特征进行学习是很勉强的 (reluctant), 因此, 每次关停某一层的一些神经元, 下一层神经元的输入就减少了, 从而避免了对不可靠 (以随机概率走远) 特征的学习, 从而避免了过拟合.&lt;/li&gt;
&lt;li&gt;与 L2 正则化类似, Dropout regularization 起到了 shrink weights 的作用, 关停上一层某一神经元, 相当于其对应的权值也无效了.&lt;/li&gt;
&lt;li&gt;Dropout 正则化, 可以为每一层独立设置&lt;code&gt;保留 (神经元) 概率 keep probability&lt;/code&gt;, 增加了灵活性. 对于很可能会过拟合的层, 可以设置较小的 keep_prob. 一般不对输入层进行 drop out.&lt;/li&gt;
&lt;li&gt;Dropout 正则化的一个缺点是, 很难去定义成本函数 J, 因为每次都随机关停了一些节点. 对此, 比较好的做法是, 在执行 Dropout 正则化之前, 先观察成本函数随时间的变化, 确保无误之后, 再进行 Dropout 正则化.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;数据增强 Data augmentation&lt;/code&gt; 是一种在需要更多训练数据时, 可以采取的技术手段. 它对现有的一份样本, 进行一定程度的变换 (比如图片的对称变换, 旋转变换), 得到新的样本.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;早停 early stopping&lt;/code&gt; 也是一种防止过拟合的技术手段. 训练开始时, 训练误差和检验误差都随着迭代的进行而下降, 训练误差理论上会持续下降, 而检验误差在达到某一最小值之后, 随着迭代的继续, 反而会上升. 此时, 应在检验误差达到最小值的时候提前结束迭代, 这就是 early stopping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/early_stopping.png&quot; alt=&quot;early_stopping.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;正规化 normalizing&lt;/code&gt; 将不同尺度的特征缩放到同一尺度 (每个样本都减去样本均值, 再除以样本方差), 比如 [0, 1] 或 [-1, 1] 的范围. 带来的一个好处是, 使梯度下降算法更快收敛. 直观的感受, 如下图所示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/normalize_accelarate_gradient_descent.png&quot; alt=&quot;normalize_accelarate_gradient_descent.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;梯度爆炸/消失, vanishing/exploding gradients&lt;/code&gt;: 对于一个非常深的深度神经网络, 忽略 b, 仅考虑参数 w, 当每一层的权值都略大于 1 或单位矩阵, 前向传播到最后, 激活函数值将爆炸 (因为指数级增长); 当每一层的权值都略小与 1 或单位矩阵时, 到最后, 激活函数值将无限小 (指数级衰减). 这从一方面证明了&lt;code&gt;随机初始化参数的重要性&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;对于单个神经元, $$z=w^\mathrm{T} x+b$$, 一个直观的感觉, 输入特征数 n 越大, z 也会越大. 为了防止 z 过大或过小, 一种方式是选择更小的权值, 通常的做法是, 初始化权值时, 使权值的方差 $$Var(w_i)$$ 等于一个较小的值, 比如 $$\frac{1}{m}$$:
&lt;ol&gt;
&lt;li&gt;对于 ReLU 激活函数, 权值通常初始化为: &lt;code&gt;np.random.randn(shape) * np.sqrt(2/n)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对于 tanh 激活函数, 权值通常初始化为: &lt;code&gt;np.random.randn(shape) * np.sqrt(1/n)&lt;/code&gt;. 也称为 &lt;code&gt;Xavier 初始化&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;另外还有一种权值初始化的方式是: &lt;code&gt;np.random.randn(shape) * np.sqrt(2/(n + o))&lt;/code&gt;  其中 o 为输出特征数&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;所谓&lt;code&gt;梯度检查 gradient checking&lt;/code&gt;, 就是检验微分是否正确. 通常的做法是, 利用极限原理, 设 g(theta) 是 f(theta) 的导数, 则梯度检查就是验证 $$\frac{f(\theta + \epsilon) - f(\theta)}{\epsilon}$$ 近似 $$g(\theta)$$, 或者用 $$\frac{(\theta + \epsilon) - f(\theta - \epsilon)}{2\epsilon}$$, 能获得更高的精度. 一般而言, 误差在 $$10^{-7}$$ 以内的, 表明微分没问题; 误差在 $$10^{-7} ~ 10^{-5}$$ 的, 可能有点问题; 误差在 $$10^{-3}$$ 的, 很有可能微分出错了.&lt;/li&gt;
&lt;li&gt;Gradient Checking 的一些注意点:
&lt;ul&gt;
&lt;li&gt;只在调试的时候用, 训练时不需要&lt;/li&gt;
&lt;li&gt;梯度检查失败, 说明算法肯定存在问题&lt;/li&gt;
&lt;li&gt;别忘了正则化&lt;/li&gt;
&lt;li&gt;梯度检查对 Dropout 无效, 因为 Dropout 随机关停神经元, 很难对成本函数进行定义&lt;/li&gt;
&lt;li&gt;随机初始化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;week-2-optimization&quot;&gt;week 2: Optimization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;小批量梯度下降, MBGD&lt;/code&gt;, minibatch_size=1 时, 就是随机梯度下降算法; mibibatch_size=训练集容量 m, 就是批量梯度下降算法.&lt;/li&gt;
&lt;li&gt;SGD 收敛很快, 但它实际无法利用向量化带来的计算加速, 它的快只是因为它每次以一个样本来执行梯度下降算法&lt;/li&gt;
&lt;li&gt;实际应用中, minibatch_size 通常不取 1, 也不取 m, 通常取 $$2^n$$, 以适应内存或显存的需求, 但不取太大的值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;指数加权平均 Exponentially weighted averages&lt;/code&gt;, 有时候也称指数加权移动平均, 引入过去对现在的影响, 而不单单考虑当前值, 因此可以抚平短期波动, 反映出长期的趋势. 其公式是: $$v_t = \beta v_{t-1} + (1 - \beta) \theta_t (v_0 = 0)$$. 其中 $$\theta$$ 是原始变量, v 是对 $$\theta$$ 进行了指数加权平均得到的变量, $$\beta$$ 是不知名系数, 用于控制平均的量, $$\beta$$ 的值越大, 将计算之前越多数据的平均, 一般默认取 0.9&lt;/li&gt;
&lt;li&gt;由于 $$v_0 = 0$$, 指数加权平均刚开始计算时, $$v_t$$ 与 $$\theta_t$$ 偏差很大. &lt;code&gt;bias correction&lt;/code&gt; 用于解决该问题, 做法是, 计算得 $$v_t$$ 之后, 再除以 $$1 - \beta^t$$, 作为最终结果.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;动量算法 Momentum&lt;/code&gt;, 也称为 &lt;code&gt;Gradient descent with momentum&lt;/code&gt;, 就是将指数加权平均引入梯度下降算法, 用参数更新量的指数加权平均结果代替参数更新量, 来更新参数. 以 W 为例, 就是先计算 $$v_dW = \beta v_dW + (1 - \beta)dW$$, 然后用 $$v_dW$$ 来更新 W, 即 $$W = W - \alpha v_dW$$. 有时候, 也用 $$v_dW = \beta v_dW + dW$$ 来计算 $$v_dW$$.&lt;/li&gt;
&lt;li&gt;引入动量, 能加速梯度下降的收敛, 可以用下图来说明. 使用 MBGD 时, 由于不能得到全局最优解, 梯度下降的过程是曲折的, 但大方向是向全局最优解靠近的 (这一点很重要), 可能如图中蓝色曲线所示 (极端例子). 使用指数加权平均之后, 不是向最优解靠近的移动量 (图中竖直方向) 相互抵消, 而向最优解靠近的移动量持续叠加, 就得到了图中红色曲线. 下图比较直观地展现了动量对梯度下降的加速效果.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/momentum_accelarate_gradient_descent.png&quot; alt=&quot;momentum_accelarate_gradient_descent.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RMSprop (Root Mean Square prop)&lt;/code&gt; 与动量算法有相似的地方, 但又不同. 具体地, 以参数 W 为例, 其他参数处理相同, RMSprop 先求 sdW, 这是 dW^2 的指数加权平均值, 然后更新参数时, 使用公式: $$W = W - \alpha * d_W / \sqrt{sd_W+\epsilon}$$. (RMS 由此得名, 对均方 $$sd_W$$ 求根) ($$\epsilon$$ 是为了避免除零之类的错误, 至于为什么再求个根, 我也很好奇).&lt;/li&gt;
&lt;li&gt;RMSprop 能加速梯度下降收敛, 解释如下: 对 $$dW$$ 求平方, 首先放大了参数 W 的增大或减小量, 然后求指数加权平均, 累加了增大或减小的效果, 结果 $$s_dW$$ 就是一个极大或极小的值. 假设 $$s_dW$$ 极大, $$\frac{dW}{\sqrt{s_dW}}$$ 就变小; $$s_dW$$ 极小, $$\frac{dW}{\sqrt{s_dW}}$$ 就变大, 从而减轻了梯度下降的震荡, 保证梯度下降的正轨.&lt;/li&gt;
&lt;li&gt;RMSprop 的一个好是, 由于使用 $$\frac{dW}{\sqrt{s_dW+\epsilon}}$$ 减轻了震荡, 就可以使用一个较大的学习率 $$\alpha$$, 加速学习.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Adam&lt;/code&gt; 是 Momentum 和 RMSprop 的有机结合, 当然, 计算 $$v_dW$$ 和 $$s_dW$$ 时使用的 $$\beta$$ 是不一样的, 分别记作 $$\beta_1$$ 和 $$\beta_2$$. 最后参数的更新为: $$W = W - \alpha \frac{v_dWc}{\sqrt{s_dWc +\epsilon}}$$. $$v_dWc$$ 和 $$s_dWc$$ 分别是经过 bias correction 的 $$v_dW$$ 和 $$s_dW$$.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Adam&lt;/code&gt; 有 4 个超参数: $$\alpha$$, $$\beta_1$$ (动量算法的 $$\beta$$), $$\beta_2$$ (RMSprop 用到的 $$\beta$$), $$\epsilon$$. 一般而言, $$\beta_1$$ 默认取 0.9, $$\beta_2$$ 默认取 0.999, $$\epsilon$$ 取 $$10^{-8}$$, 只剩下 $$\alpha$$ 需要调节.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;学习率衰减 learning rate decay&lt;/code&gt; 对于 MBGD 以及 SGD 相当关键. 原因在于, MBGD/SGD 并不能获得全局最优解, 即使选择了一个能收敛的学习率, 当成本曲折地下降到全局最优解附近时, 由于学习率始终不变, 成本可能会以一个较大的偏差幅度在全局最优解附近震荡. 如下图所示, 相比于蓝色曲线, 我们觉得红色曲线更好, 因为它震荡的幅度更小, 更逼近直线. 学习率衰减就是要让 MBGD/SGD 在收敛至全局最优解时, 尽可能逼近全局最优解, 而不是大幅地徘徊.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/illustration_of_learning_rate_decay.png&quot; alt=&quot;illustration_of_learning_rate_decay.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;常用的学习率衰减公式:
&lt;ol&gt;
&lt;li&gt;$$\alpha = \frac{\alpha_0}{1 + decay_rate * epoch_num}$$. 每一轮迭代, 都对学习率进行衰减.&lt;/li&gt;
&lt;li&gt;$$\alpha = k * \frac{\alpha_0}{\sqrt{epoch_num}}$$ 或者 $$\alpha = k * \frac{\alpha_0}{\sqrt{t}}$$&lt;/li&gt;
&lt;li&gt;$$\alpha = 0.95^epoch_num * \alpha_0$$. 指数衰减, 底数可以自由地选择小于 1 的数&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;梯度为 0 的点, 不一定是极小极大值点, 也可能是&lt;code&gt;鞍点&lt;/code&gt;. 比如 $$y=x^3$$ 在 0 处就是鞍点. 对于深度学习而言, 由于参数数量极大, 在高维空间中, 一般很难得到如下图左图所示的全局最优情况, 往往因为各参数的极值点各不相同, 更多的可能是得到类似下图右图所示的情况, 称为&lt;code&gt;Problem of plateaus&lt;/code&gt;. 因为曲面比较光滑, 梯度下降陷入不好的局部最优的可能性变得很小, 但带来的另一个问题是, 微分长期保持在一个极小的值, 致使梯度下降得极慢, 收敛需要相当长的时间.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/local_optima_in_neural_networks.png&quot; alt=&quot;local_optima_in_neural_networks.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;week-3-hyperparameter-tuning--batch-normalization--programming-frameworks&quot;&gt;week 3: Hyperparameter Tuning &amp;amp; Batch Normalization &amp;amp; Programming Frameworks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;传统机器学习调节超参数时可能会使用&lt;code&gt;网格搜索 grid search&lt;/code&gt; 来选择最优超参数, 但对深度学习, 网格搜索的意义不大, 更通常的做法是随机选择超参数值, 然后&lt;code&gt;由粗到细 coarse to fine&lt;/code&gt;, 重复随机选择超参数.&lt;/li&gt;
&lt;li&gt;所谓&lt;code&gt;随机选择&lt;/code&gt;, 并不是都采取均匀随机采样. 比如, 对于均匀分布的变量, 可以随机采样; 对于非均匀分布, 比如梯度下降学习率 $$\alpha$$ 的范围是 [0.0001, 1], 比较正确的做法是, 在 (0.0001, 0.001, 0.01, 0.1, 1) 附近进行随机采样, 具体的做法是: &lt;code&gt;r = -4 * np.random.rand()&lt;/code&gt; $$a = 10^r$$ 再比如指数加权平均的超参数 $$\beta$$, 范围是 [0.9, 0.999], 一般也认为不是均匀分布的, 此时要利用 $$1-\beta$$ 构造表达式, 首先 $$1-\beta$$ 的范围是 [0.001, 0.1], 此时就可以用上述相同的方法进行构造: $$\beta = 1 - 10^r$$&lt;/li&gt;
&lt;li&gt;一个经验之谈是: 偶尔 (比如每隔几个月) 重新测试超参数. 理由是, 随着数据量的增多, 过去的模型可能会过时 (stale).&lt;/li&gt;
&lt;li&gt;深度学习的两种打开方式:
&lt;ol&gt;
&lt;li&gt;熊猫式 (Babysitting one model): 在计算资源有限的情况下, 难于同时训练多个模型, 因此一般精心地训练一个模型&lt;/li&gt;
&lt;li&gt;鱼子式 (Training many models in parallel): 在计算资源充足的情况下, 可以同时训练多个模型, 最后选择最优的即可&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;我们已经知道对样本输入进行正规化 (减去样本均值, 除以样本方差) 能加速学习. &lt;code&gt;Batch Normalization&lt;/code&gt; 是输入正规化的一个扩展: 对每一层的输出 a 也进行正规化. 但更通常的做法是对线性加权的结果 z 进行正规化.&lt;/li&gt;
&lt;li&gt;Batch Norm 的具体实现是: 在计算得 z 之后, 根据 $$\mu=\Sigma \frac{z_i}{m}; \sigma ^2=\Sigma z_i - mu$$ 求得 $$z_{norm}$$. 只进行到这一步, 每一层的 z 都具有相同的分布, 均值都是 0, 方差都是 1. 有时候我们并不希望这样, 也许不同的分布会有奇效, 也许我们就是想要不一样的分布. 因此通常还要通过 $$\tilde{z_i}=\gamma z_i+ \beta$$ 来调整 z 的分布. $$\gamma$$, $$\beta$$ 是模型可以学习的参数, 同 W, b 的性质一样.&lt;/li&gt;
&lt;li&gt;Batch Norm 有效, 可以用下图来解释. 越是靠前的层次, 对神经网络整体的影响一般越深远, 比如第一隐层的输入发生改变, 则后续的 2, 3, 4, 5 层的输入都要发生改变. 而如果只是第 4 隐层的发生改变, 影响的只是输出层而已. 对下图而言, 先不看输入层与第一隐层, 如果以第二隐层作为输入, 我们已经学好了 W3, b3, W4, b4, W5, b5, 对服从某一分布的输入, 能做出高精度的预测了. 现在重新考虑输入层与第一隐层, 这两层的加入, 会改变第二隐层的输入数据的分布, 后续所有层都不得不以较剧烈的幅度重新学习参数, 相当于重新训练了整个神经网络. 而 &lt;code&gt;Batch Norm&lt;/code&gt; 增强了神经网络的&lt;code&gt;鲁棒性 Robustness&lt;/code&gt;, 就是说, 对第一隐层输出的正规化, 使得第二隐层输入的数据分布保持在一个较稳定的状态, 如果第二隐层已经学好了参数, 现在只需微调甚至不需要调整. 后续各层同样如此.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-22-andrew_ng_dl_course2_note/why_does_batch_norm_work.png&quot; alt=&quot;why_does_batch_norm_work.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;总结一下 Batch Norm 的作用就是: &lt;code&gt;限制了靠前层次的参数更新对后续层次输入分布的影响, 从而减小了后续层次输入值的变化, 使得后续层次更稳定. 这使得, 前面的层次持续学习, 但后续层次不得不应变的可能性减小了, 也就是说, 解偶了各层次, 或者说弱化了各层参数之间的联系. 于是, 神经网络的各层可以相对独立地进行学习, 对其他层的依赖大大减小了, 加速了整个神经网络的学习&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如上所述, Batch Norm 的一个副作用是, 提高了神经网络的复用性.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Batch Norm 的其他注意点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个 mini-batch 要独立计算均值与方差, 然后缩放&lt;/li&gt;
&lt;li&gt;对于每个 mini-batch, batch norm 会给 z 值引入噪音 (类似地, dropout regularization 会给每个隐层的 a 引入噪音)&lt;/li&gt;
&lt;li&gt;Batch Norm 有轻微的正则化效果&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;测试时, 我们每次用一个测试样本来计算误差, 无法像训练时那样拥有一个 minibatch, 然后对 minibatch 计算均值/方差. 典型的做法是, 训练时, 记录每一个 minibatch 在每一层的均值/方差, 如 $$mu^{{1}[l]}, \sigma^2 ^{{1}[l]}$$, 再计算 $$\mu$$ 与 $$\sigma^2$$ 各自的指数加权平均, 作为测试用的 $$\mu$$ 与$$\sigma^2$$. 至于 $$\gamma$$ 与 $$\beta$$, 如前所述, 是同 W, b 一样的参数, 直接用训练习得的即可.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;选择深度学习框架的几点建议:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;易于编程 (包括开发和部署)&lt;/li&gt;
&lt;li&gt;运行速度快&lt;/li&gt;
&lt;li&gt;真开放 (开源, 且拥有良好的管理. 那些目前开源, 但缺乏管理, 未来可能被闭源的不算真开放)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>机器学习</category></item><item><title>吴恩达深度学习第一课笔记</title><link>https://lifelonglove.cn/notes/andrew_ng_dl_course1_note/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/andrew_ng_dl_course1_note/</guid><description>梳理神经网络基础、向量化表示、损失函数、反向传播与浅层网络训练。</description><pubDate>Mon, 21 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;week-1-introduction-to-deep-learning&quot;&gt;week 1: Introduction to Deep Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;机器学习的数据分为: &lt;em&gt;结构化数据 (structured data)&lt;/em&gt; 与 &lt;em&gt;非结构化数据(unstructured data)&lt;/em&gt;. 非结构化数据包括: 音频, 图片, 文本等&lt;/li&gt;
&lt;li&gt;深度学习大爆炸的原因是:
&lt;ol&gt;
&lt;li&gt;大数据时代, 数据的爆发式增长&lt;/li&gt;
&lt;li&gt;计算机硬件技术的发展, 计算成本下降, 速度提高&lt;/li&gt;
&lt;li&gt;新算法的发明&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/performance-amount_of_data.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/performance-amount_of_data.png&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;week-2-neural-networks-basics&quot;&gt;week 2: Neural Networks Basics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不同于一般机器学习或深度学习的书籍/教程, 吴恩达的矩阵表示法: &lt;strong&gt;用列向量表示一个样本&lt;/strong&gt;, 因此 &lt;code&gt;X.shape==(n_x, m)&lt;/code&gt;, n_x 是特征数, m 是样本大小&lt;/li&gt;
&lt;li&gt;&lt;code&gt;损失函数, loss&lt;/code&gt; 是单个样本的估计值与真实值的误差; &lt;code&gt;成本函数, cost&lt;/code&gt; 是所有样本的误差总和&lt;/li&gt;
&lt;li&gt;对率回归的损失函数: $$-[ylog(\hat{y}) + (1-y)(log(1-\hat{y}))]$$&lt;/li&gt;
&lt;li&gt;导数的定义, 极限原理&lt;/li&gt;
&lt;li&gt;一个辅助工具: &lt;code&gt;计算图 Computation Graph&lt;/code&gt; (和 Tensorflow 中的概念差不多) 是一个有向图, &lt;code&gt;边 edge&lt;/code&gt; 定义了数据流动的方向, &lt;code&gt;节点 node&lt;/code&gt;表示&lt;code&gt;运算操作 operation&lt;/code&gt;, 流通的是数据.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/computation_graph.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/computation_graph.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上图中, 红线表示反向传播&lt;/li&gt;
&lt;li&gt;神经网络步骤拆解: &lt;code&gt;参数初始化&lt;/code&gt; -&amp;gt; &lt;code&gt;前向传播&lt;/code&gt; -&amp;gt; &lt;code&gt;计算成本&lt;/code&gt; -&amp;gt; &lt;code&gt;反向传播&lt;/code&gt; -&amp;gt; &lt;code&gt;更新参数&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;向量化的一个好处是: 不必显式地循环, 用矩阵运算来代替循环. 充分利用了 &lt;code&gt;SIMD, 单指令多数据&lt;/code&gt; 的优势, 提高计算效率&lt;/li&gt;
&lt;li&gt;神经网络编程指南 1: 尽可能避免显式的 for-loops&lt;/li&gt;
&lt;li&gt;Python 科学计算的&lt;code&gt;广播 broadcasting&lt;/code&gt;: (m, n) 维的矩阵 +-*/ (1, n) 或 (m, 1) 维的矩阵, 后者将自动进行横向或纵向复制, 得到 (m, n) 维的矩阵, 然后计算&lt;/li&gt;
&lt;li&gt;&lt;code&gt;np.random.randn(n).shape  # (n,)&lt;/code&gt; 得到一个秩为 n 的数组, 既不是行向量也不是列向量; &lt;code&gt;np.random.randn((n, 1)).shape  # (n, 1)&lt;/code&gt; 得到一个 (n, 1) 维的的列向量&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;week-3-shallo-neural-networks&quot;&gt;week 3: Shallo Neural Networks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个神经元可以看作是两步计算的结合: 1. 输入的线性加权 $$z=w^ \mathrm{T} + b$$; 2. 非线性变换 $$a=g(z)$$&lt;/li&gt;
&lt;li&gt;几个激活函数: sigmoid, tanh, ReLU, leaky ReLU&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/sigmoid_tanh_relu_leaky-relu.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/sigmoid_tanh_relu_leaky-relu.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;激活函数 activation function&lt;/code&gt; 的必要性: 如果没有激活函数, 或令 $$g(z)=z$$, 无论神经网络规模变得多大, 训练得到的模型都可以表示为一个线性模型: $$w’x + b$$, 就无法学习复杂的特征.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;simoid func 的微分: $$g’(z) = g(z)(1 - g(z))$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tanh func 的微分: $$g’(z) = 1 - g(z)^2$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;relu func 的微分: g’(z)=\begin{cases}0, &amp;amp; \text{z \lt 0}\ 1, &amp;amp; \text{z \ge 0} \end{cases}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;leaky relu func 的微分:g’(z)=\begin{cases} k, &amp;amp; \text{z \lt 0}\ 1, &amp;amp; \text{z \ge 0} \end{cases}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;随机初始化的必要性&lt;/code&gt;: 初始化权值为 0, 将引入&lt;code&gt;对称问题 symmetry problem&lt;/code&gt;. 后果是: 第一隐层的每个神经元进行相同的计算, 输出同一结果, 以致于经过多次迭代, 第一隐层的神经元计算结果仍然相同.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;week-4-deep-neural-networks&quot;&gt;week 4: Deep Neural Networks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;确保深度神经网络正常运行的一个方法是: 检查矩阵的维度:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/dimensions_of_parameters.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/dimensions_of_parameters.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/dimensions_of_za.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/dimensions_of_za.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;深度神经网络更关注隐层的数量, 而不是神经元的数量. L 层的&lt;strong&gt;小型&lt;/strong&gt;深度神经网络可能比拥有大量隐层单元的浅度网络, 拥有更好的性能. 例如下图所示的例子, 要计算 n 个特征的异或, 深度神经网络时间复杂度为 $$O(\log{n})$$, 而单隐层神经网络, 时间复杂度为 $$O(2^n)$$.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/circuit-theory_deep-learning.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/circuit-theory_deep-learning.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;深度神经网络, 从第一隐层到输出层, 逐层学习更复杂的特征, 比如人脸识别 (语音识别), 第一隐层用于学习面部的线条 (低级音频特征), 第二层用于学习五官特征 (音素, phoneme), 第三层用于学习面部整体特征 (单词, 词组, 句子).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第 l 层的前向传播计算, 输入 $$a^{[l-1]}$$, 输出 $$a^{[l]}$$, 并缓存 $$z^{[l]}$$, $$w^{[l]}$$, $$b^{[l]}$$ (用于反向传播); 第 l 层的反向传播计算, 输入 $$da^{[l]}$$, 输出 $$da^{[l-1]}$$, $$dW^{[l]}$$, $$db^{[l]}$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/forwardprop-backprop_kiank.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/forwardprop-backprop_kiank.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;机器学习/深度学习的基本流程:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/workflow_of_nn.png&quot; alt=&quot;/img/2017-08-21-andrew_ng_dl_course1_note/workflow_of_nn.png&quot;&gt;&lt;/p&gt;
</content:encoded><category>笔记</category><category>深度学习</category><category>机器学习</category></item><item><title>ML之梯度下降算法</title><link>https://lifelonglove.cn/articles/gradient-descent/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/gradient-descent/</guid><description>从导数与方向选择出发，梳理批量、随机和小批量梯度下降的更新方式、优缺点与常见调参技巧，并用代码展示优化过程如何逐步逼近目标。</description><pubDate>Sun, 23 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;新的征程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#base&quot;&gt;基本概念&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#introduction-to-algorithm&quot;&gt;算法简介&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#bgd&quot;&gt;BGD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sgd&quot;&gt;SGD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#mbgd&quot;&gt;MBGD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#tips&quot;&gt;Tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#advance&quot;&gt;进阶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#digression&quot;&gt;题外话&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#code-sample&quot;&gt;样例代码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#reference&quot;&gt;参考资料&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;基本概念&quot;&gt;基本概念&lt;/h2&gt;
&lt;p id=&quot;base&quot;&gt;&lt;/p&gt;
&lt;p&gt;本节尝试独立于机器学习算法, 单纯地来讲&lt;code&gt;梯度下降算法 [Gradient Descent, GD]&lt;/code&gt;, 以使梯度下降更具一般性.&lt;/p&gt;
&lt;p&gt;开始之前, 先放 2 个基本概念, 带着这 2 个认识, 在几乎不具备机器学习知识的前提下, 应该也能很好地读懂本节内容:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;机器学习的主要任务之一, 就是通过训练, 习得一组最优的参数. 常以&lt;code&gt;成本函数 [Cost Function]&lt;/code&gt; 作为参数估计的函数, 因此, 机器学习的任务就转变为了最小化成本函数.&lt;/li&gt;
&lt;li&gt;优化是机器学习算法非常重要的组成部分, 几乎每个机器学习算法都有一个优化算法.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;梯度下降算法就是一个被广泛使用的优化算法, 它可以用于寻找最小化成本函数的参数值. 用中学数学的语言来描述梯度下降, 是这样的: &lt;em&gt;当函数 &lt;em&gt;$$J(\omega)$$&lt;/em&gt; 取得最小值时, 求所对应的自变量 &lt;em&gt;$$\omega$$&lt;/em&gt; 的过程&lt;/em&gt; 此处, $$\omega$$ 就是机器要学习的参数, $$J(\omega)$$ 就是用于参数估计的成本函数, 是关于 $$\omega$$ 的函数. 因此, 基本上具备中学数学知识的, 都能理解梯度下降算法.&lt;/p&gt;
&lt;p&gt;梯度下降的基本步骤是:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对成本函数进行微分, 得到其在给定点的梯度. 梯度的正负指示了成本函数值的上升或下降: $$\Delta(\omega)=\frac{\partial J(\omega)}{\partial \omega}$$&lt;/li&gt;
&lt;li&gt;选择使成本函数值减小的方向, 即梯度负方向, 乘以以学习率 $$\alpha$$ 计算得参数的更新量, 并更新参数:$$\omega=\omega-\alpha\Delta(\omega)$$&lt;/li&gt;
&lt;li&gt;重复以上步骤, 直到取得最小的成本&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上就是梯度下降算法最基础也是最核心的概念, 很简单吧.&lt;/p&gt;
&lt;p&gt;下面讲讲梯度下降算法的几个变种, 包括: &lt;code&gt;批量梯度下降 [Batch Gradient Descent, BGD]&lt;/code&gt;, &lt;code&gt;随机梯度下降 [Stochastic Gradient Descent, SGD]&lt;/code&gt;, &lt;code&gt;小批量梯度下降 [Mini-Batch Gradient Descent, MBGD]&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;算法简介&quot;&gt;算法简介&lt;/h2&gt;
&lt;p id=&quot;introduction-to-algorithm&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;bgd&quot;&gt;BGD&lt;/h3&gt;
&lt;p id=&quot;bgd&quot;&gt;&lt;/p&gt;
&lt;p&gt;BGD 是梯度下降算法最原始的形式, 其特点是每次更新参数 $$\omega$$ 时, 都使用整个训练集的数据.&lt;/p&gt;
&lt;p&gt;BGD 的具体实现是这样的:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设假设函数为: $$h_{\omega}=\Sigma_{j=0}^{n} \omega_{j} x_{j}$$
&lt;ul&gt;
&lt;li&gt;所谓假设函数, 就是用于将输入映射为输出的工具, 其返回值也称为估计值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;设成本函数为: $$J(\omega)=\frac{1}{2m}\Sigma_{i=1}^{m} (h_{\omega}(x^{(i)})-y^{(i)})^{2}$$
&lt;ul&gt;
&lt;li&gt;注意, $$\omega$$ 才是函数自变量, $$x$$ 是模型输入, $$y$$ 是输入对应的真实值&lt;/li&gt;
&lt;li&gt;该成本函数中, 真正有效的部分是 $$\Sigma_{i=1}^{m} (h_{\omega}(x^{(i)})-y^{(i)})^{2}$$, 前面的 $$\frac{1}{2m}$$ 是为后续计算方便添加的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对成本函数求导, 需要对每一个参数 $$w_{j}$$ 分别求偏导, 得到它们各自的梯度: $$\frac{\partial J(\omega)}{\partial \omega_{j}}=-\frac{1}{m}\Sigma_{i=1}^{m}(y^{i}-h_{\omega}(x^{i}))x_{j}^{i}$$
&lt;ul&gt;
&lt;li&gt;机器学习模型通常不止一个参数. 成本函数作为参数估计的工具, 要估计每个参数的最优值, 因此需要对每一个参数分别求偏导数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;每个参数都按梯度负方向进行更新: $$\omega_{j}=\omega_{j} + \frac{\alpha}{m}\Sigma_{i=1}^{m}(y^{i}-h_{\omega}(x^{i}))x_{j}^{i}$$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此, BGD 的伪代码形式可以简单地写成:&lt;/p&gt;
&lt;p&gt;repeat {&lt;br/&gt;
    $$\omega_{j}=\omega_{j} + \frac{\alpha}{m}\Sigma_{i=1}^{m}(y^{i}-h_{\omega}(x^{i}))x_{j}^{i}$$&lt;br/&gt;
    (for every j = 0, 1, .. n)&lt;br/&gt;
}&lt;/p&gt;
&lt;p&gt;上式中的求和部分 $$\Sigma$$ 就体现了每一次迭代, 都以整个训练集为对象进行梯度计算.&lt;/p&gt;
&lt;p&gt;BGD 得到的是全局最优解, 因为它总是以整个训练集来计算梯度, 这是 BGD 的优点. 但也因此带来了巨大的计算量, 计算迭代速度很很慢.&lt;/p&gt;
&lt;h3 id=&quot;sgd&quot;&gt;SGD&lt;/h3&gt;
&lt;p id=&quot;sgd&quot;&gt;&lt;/p&gt;
&lt;p&gt;SGD 每次以一个样本, 而不是整个数据集来计算梯度. 因此, SGD 从成本函数开始, 就不必再求和了, 针对单个样例的成本函数可以写成: $$J(\omega)=\frac{1}{2}(h_{\omega}(x^{(i)})-y^{(i)})^{2}$$ (此处的 $$\frac{1}{2}$$ 同样是为了后续计算方便设置的)&lt;/p&gt;
&lt;p&gt;于是, SGD 的参数更新规则就可以写成: $$\omega_{j}=\omega_{j}+\alpha(y^{i}-h_{\omega}(x^{i}))x_{j}^{i}$$&lt;/p&gt;
&lt;p&gt;SGD 的伪代码形式如下:&lt;/p&gt;
&lt;p&gt;repeat {&lt;br/&gt;
    for i = 1, .., m {&lt;br/&gt;
        $$\omega_{j}=\omega_{j}+(y^{i}-h_{\omega}(x^{i}))x_{j}^{i}$$&lt;br/&gt;
        (for every j = 0, 1, .. n)&lt;br/&gt;
    }&lt;br/&gt;
}&lt;/p&gt;
&lt;p&gt;SGD 的关键点在于以随机顺序选取样本. 因为 SGD 存在局部最优困境, 若每次都以相同的顺序选取样本, 其有很大的可能会在相同的地方陷入局部困境, 或者收敛减缓. 因此, 欲使 SGD 发挥更好的效果, 应充分利用&lt;code&gt;随机化 [Randomise]&lt;/code&gt; 带来的优势: 可以在每次迭代之前 (伪代码中最外围循环), 对训练集进行随机排列.&lt;/p&gt;
&lt;p&gt;因为每次只取一个样本来进行梯度下降, SGD 的训练速度很快, 但会引入噪声, 使准确度下降. 这意味着并不是每次迭代都向着全局最优而去, 即并不是每次迭代都能使成本函数值降低. 不过换个思路的话, 噪声在一定程度上以使算法避免了局部最优.&lt;/p&gt;
&lt;p&gt;SGD 的另一个好处是, 可以使用&lt;code&gt;在线学习 [online learning]&lt;/code&gt;. 也就是说, 在模型训练好之后, 只要有新的数据到来, 模型都可以利用新的数据进行再学习, 更新参数,以适应新的变化.&lt;/p&gt;
&lt;h3 id=&quot;mbgd&quot;&gt;MBGD&lt;/h3&gt;
&lt;p id=&quot;mbgd&quot;&gt;&lt;/p&gt;
&lt;p&gt;MBGD 是为解决 BGD 与 SGD 各自缺点而发明的折中算法, 或者说它利用了 BGD 和 SGD 各自优点. 其基本思想是: &lt;em&gt;每次更新参数时, 使用 n 个样本, 既不是全部, 也不是 1.&lt;/em&gt; (SGD 可以看成是 n=1 的 MBGD 的一个特例)&lt;/p&gt;
&lt;p&gt;此处就不再给出 MBGD 的成本函数或其求导公式或参数更新规则公式了, 基本同 BGD, 见上.&lt;/p&gt;
&lt;p&gt;MBGD 的伪代码如下:&lt;/p&gt;
&lt;p&gt;say b=10, m=1000,&lt;br/&gt;
repeat {&lt;br/&gt;
    for i = 1, 11, 21, .., 991 {&lt;br/&gt;
        $$\omega_{j}=\omega_{j} + \alpha \frac{1}{10}\Sigma_{k=i}^{i+9}(y^{k}-h_{\omega}(x^{k}))x_{j}^{k}$$&lt;br/&gt;
        (for every j = 0, 1, .. n)&lt;br/&gt;
    }&lt;br/&gt;
}&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;总结一下以上 3 个梯度下降算法的优缺点:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: left&quot;&gt;梯度下降算法&lt;/th&gt;
&lt;th style=&quot;text-align: left&quot;&gt;优点&lt;/th&gt;
&lt;th style=&quot;text-align: left&quot;&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left&quot;&gt;BGD&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;全局最优解&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;计算量大, 迭代速度慢, 训练速度慢&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left&quot;&gt;SGD&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;1.训练速度快 &lt;br/&gt; 2. 支持在线学习&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;准确度下降, 有噪声, 非全局最优解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left&quot;&gt;MBGD&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;1. 训练速度较快, 取决于小批量的数目 &lt;br/&gt; 2. 支持在线学习&lt;/td&gt;
&lt;td style=&quot;text-align: left&quot;&gt;准确度不如 BGD, 仍然有噪声, 非全局最优解&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;tips&quot;&gt;Tips&lt;/h3&gt;
&lt;p id=&quot;tips&quot;&gt;&lt;/p&gt;
&lt;p&gt;下面将介绍一些梯度下降算法的使用技巧:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;挑选合适的学习率 $$\alpha$$. 最好的选择方法就是监测目标函数值 (本文中就是成本函数值) 随时间的学习曲线.&lt;/li&gt;
&lt;li&gt;绘制成本-时间的曲线, 观察成本随迭代的变化情况, 即梯度的变化情况. 若成本没有随着迭代而下降, 说明学习率过大了 (发散了).&lt;/li&gt;
&lt;li&gt;学习率通常是一个很小的实值, 比如 0.1, 0.001, 0.0001. 学习率如果过大, 成本函数可能无法收敛, 一直处于发散状态.&lt;/li&gt;
&lt;li&gt;学习算法之前, 进行特征缩放能够改善梯度下降. (亲测! 原来使用较大学习率, 成本函数无法收敛; 使用特征缩放之后, 收敛了)&lt;/li&gt;
&lt;li&gt;使用 MBGD 时, 在学习过程中, 可以逐渐增大小批量的大小, 更好地综合发挥 BGD 与 SGD 的优势.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;进阶&quot;&gt;进阶&lt;/h2&gt;
&lt;p id=&quot;advance&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以下内容有点深奥, 笔者对一些概念也不甚熟悉, 仅仅是做个记录&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我注意到 Keras 实现的 SGD 提供了 3 个关键字参数: &lt;code&gt;decay&lt;/code&gt;, &lt;code&gt;momentum&lt;/code&gt;, &lt;code&gt;nestrov&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;decay - 衰减的意思, 表示每一次迭代, 学习率 $$\alpha$$ 的衰减量.&lt;/li&gt;
&lt;li&gt;momentum - 动量的意思, 旨在加速学习, 稍后介绍.&lt;/li&gt;
&lt;li&gt;nesterov - 算法名, 表示是否使用 Nesterov 动量算法.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;使用 BGD 达到极小值时, 整个成本函数的真实梯度会变得很小, 最终减小为 0, 因此 BGD 可以使用固定的学习率; 然而, SGD 中梯度估计引入的噪声源不会在极小点处消失, 因此有必要随着时间的推移逐渐降低学习率, 以保证 SGD 收敛.&lt;/p&gt;
&lt;p&gt;实践中, 一般让学习率线性衰减, 直到第 $$\tau$$ 次迭代:&lt;/p&gt;
&lt;p&gt;$$\epsilon_{k}=(1-\alpha)\epsilon_{0}+\alpha \epsilon_{\tau}$$&lt;/p&gt;
&lt;p&gt;其中, $$k$$ 表示第 k 次迭代, $$\tau$$ 表示第 $$\tau$$ 次迭代, $$\alpha=\frac{k}{\tau}$$.&lt;/p&gt;
&lt;p&gt;上式中, 需要设置的量包括: 初始学习率 $$epsilon_0$$, 最终学习率 $$\epsilon_{\tau}$$ 以及 终止迭代次数 $$\tau$$. $$\tau$$ 通常取几百的大小, $$\epsilon_{\tau}$$ 则设为 $$\epsilon_{0}$$的 $$1 %$$. 因此, 剩下的主要问题就是选择一个合适的 $$\epsilon_{0}$$: 取值太大, 学习曲线会剧烈抖动, 成本会明显增加; 取值太小, 学习过程会变得很缓慢.&lt;/p&gt;
&lt;p&gt;上面提到, 在梯度下降中引入动量的概念, 是为了加速学习, 特别是对于处理高曲率 (曲率: 曲线偏离直线的程度), 小但一致的梯度, 或者带噪声的梯度, 有明显的加速效果. 因为动量算法积累了之前梯度指数级衰减的移动平均 (移动平均: 分析时间序列数据的有效工具), 能够继续沿该方向移动, 从而使成本持续减小.&lt;/p&gt;
&lt;p&gt;从形式上看, 动量算法引入了 $$v$$ 充当速度的角色, 代表参数在参数空间移动的方向和速率. $$v$$ 被设为负梯度的指数衰减平均, 其更新规则如下:&lt;/p&gt;
&lt;p&gt;$$
v \gets \alpha v - \epsilon \nabla_{\omega} (\frac{1}{m}\Sigma_{i=1}^{m}L(f(x^{(i)};\theta),y^{(i)}))
\omega \gets \omega + v
$$&lt;/p&gt;
&lt;p&gt;从上式可以得出的一个结论是: &lt;strong&gt;相对于 $$\epsilon$$, $$\alpha$$ 越大, 之前梯度对现在方向的影响就越大&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;在引入动量的概念之前, $$\omega$$ 的更新步长只是梯度范数乘以学习率, 引入动量之后则取决于梯度序列的大小和排列. 当许多连续的梯度指向相同时, 步长最大&lt;/p&gt;
&lt;p&gt;Nesterov 动量算法是标准动量算法的变种, 其更新规则如下:&lt;/p&gt;
&lt;p&gt;$$
v \gets \alpha v - \epsilon \nabla_{\omega} (\frac{1}{m}\Sigma_{i=1}^{m}L(f(x^{(i)};\theta + \alpha v),y^{(i)}))
\omega \gets \omega + v
$$&lt;/p&gt;
&lt;p&gt;Nesterov 动量算法与标准动量算法的区别在于梯度的计算, 其梯度计算是在施加了当前速度之后, 可以解释为向标准动量方法中添加了一个校正因子.&lt;/p&gt;
&lt;h2 id=&quot;题外话&quot;&gt;题外话&lt;/h2&gt;
&lt;p id=&quot;digression&quot;&gt;&lt;/p&gt;
&lt;p&gt;研究优化算法的收敛率时, 一般会衡量&lt;code&gt;额外误差&lt;/code&gt;: $$J(\theta)-min_{\theta} J(\theta)$$, 即当前成本超出最低可能成本的量. SGD 应用于&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%87%B8%E5%84%AA%E5%8C%96&quot;&gt;凸问题&lt;/a&gt; (研究定义于凸集中的凸函数最小化的问题)时, k 步迭代的额外误差量级是 $$O(1 / \sqrt{k})$$, 在强凸情况下是 $$O(1 / k)$$. 除非假定额外条件, 否则不能进一步改进.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cramer-Rao 界限&lt;/code&gt;指出, 泛化误差的下降速度不会快于 $$O(1 / k)$$. Bottou and Bousquet 因此认为机器学习任务, 不值得探寻收敛快于$$O(1 / k)$$ 的优化算法, 因为:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;更快的收敛可能对应过拟合&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;样例代码&quot;&gt;样例代码&lt;/h2&gt;
&lt;p id=&quot;code-sample&quot;&gt;&lt;/p&gt;
&lt;p&gt;以下是 BGD, SGD, MBGD 的 Python 代码实现, 暂时不包括&lt;a href=&quot;#advance&quot;&gt;进阶&lt;/a&gt;部分提到的高级内容.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; numpy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; pylab&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sklearn.datasets.samples_generator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; make_regression&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bgd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(alpha, x, y, numIterations):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;Copied from Internet&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# number of samples&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.ones(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    J_list &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    x_transpose &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x.transpose()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; iter&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, numIterations):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hypothesis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x, theta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hypothesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.sum(loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# cost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J_list.append(J)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iter &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | J: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%.3f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;iter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, J))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        gradient &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x_transpose, loss) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; alpha &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gradient  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(numIterations), J_list, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;k-&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sgd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(alpha, x, y, num_iter):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;Writtern by kissg&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# number of samples&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.ones(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    J_list &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 随机化序列&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.random.permutation(y.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    x, y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x[idx], y[idx]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(num_iter):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; idx:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            single_hypothesis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x[i], theta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            single_loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; single_hypothesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            gradient &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x[i].transpose(), single_loss)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; alpha &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gradient  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hypothesis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x, theta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hypothesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.sum(loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# cost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J_list.append(J)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iter &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | J: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%.3f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (j, J))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(num_iter), J_list, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;r-&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; mbgd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(alpha, x, y, num_iter, minibatches):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;Writtern by kissg&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# number of samples&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.ones(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    J_list &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(num_iter):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        idx &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.random.permutation(y.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        x, y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x[idx], y[idx]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        mini &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.array_split(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(y.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]), minibatches)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mini:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            mb_hypothesis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x[i], theta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            mb_loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mb_hypothesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            gradient &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x[i].transpose(), mb_loss) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; minibatches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            theta &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; alpha &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gradient  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        hypothesis &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.dot(x, theta)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hypothesis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.sum(loss &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; m)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# cost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        J_list.append(J)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iter &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | J: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%.3f&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (j, J))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(num_iter), J_list, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;y-&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __name__&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;__main__&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    x, y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; make_regression(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n_samples&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n_features&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;n_informative&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                           random_state&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;noise&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;35&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    m, n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.shape(x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.c_[np.ones(m), x]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# insert column, bias&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    alpha &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.01&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # learning rate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(x[:, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], y, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;o&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;#***BGD***#&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta_bgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bgd(alpha, x, y, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;800&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        y_bgd_predict &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta_bgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(x, y_bgd_predict, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;k--&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;#***SGD***#&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta_sgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sgd(alpha, x, y, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        y_sgd_predict &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta_sgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(x, y_sgd_predict, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;r--&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;#***MBGD***#&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    theta_mbgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mbgd(alpha, x, y, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(x.shape[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        y_mbgd_predict &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; theta_mbgd &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.plot(x, y_mbgd_predict, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;y--&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    pylab.show()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Done!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行以上代码, 得到 3 类梯度下降算法的函数图像如下图所示.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2017-07-23-gradient-descent/gradient_descent_cost-iteration.png&quot; alt=&quot;梯度下降, 成本-时间曲线&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;黑色是 BGD 的图像, 是一条光滑的曲线, 因为 BGD 每一次迭代求得的都是全局最优解;&lt;/li&gt;
&lt;li&gt;红色是 SGD 的图像, 可见抖动很剧烈, 有不少局部最优解;&lt;/li&gt;
&lt;li&gt;黄色是 MBGD 的图像, 相对 SGD 的成本-时间曲线平滑许多, 但仔细看, 仍然有抖动.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;参考资料&quot;&gt;参考资料&lt;/h2&gt;
&lt;p id=&quot;reference&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/maybe2030/p/5089753.html&quot;&gt;博客院园 - [Machine Learning] 梯度下降法的三种形式BGD、SGD以及MBGD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.bogotobogo.com/python/python_numpy_batch_gradient_descent_algorithm.php&quot;&gt;(Batch) gradient descent algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://machinelearningmastery.com/gradient-descent-for-machine-learning/&quot;&gt;Gradient Descent For Machine Learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/Python-Machine-Learning-Sebastian-Raschka/dp/1783555130/ref=sr_1_1?ie=UTF8&amp;amp;qid=1470882464&amp;amp;sr=8-1&amp;amp;keywords=python+machine+learning&quot;&gt;Python Machine Learning - Sebastian Raschka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.deeplearningbook.org/&quot;&gt;Deep Learning - Ian Goodfellow, Yoshua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.coursera.org/learn/machine-learning/home/welcome&quot;&gt;Machine Learning at Coursera - Andrew Ng&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>文章</category><category>机器学习</category></item><item><title>观 Quora</title><link>https://lifelonglove.cn/notes/learn-from-quora-1/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/learn-from-quora-1/</guid><description>从 Quora 回答中摘取关于平静、选择、困境与成长的几条人生判断。</description><pubDate>Sun, 16 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;离开你 45 个星期, 我又回来到这里.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;距离上一次写博客, 已经过去了整整 321 天, 45 个星期.&lt;/p&gt;
&lt;p&gt;今日重新执笔, 且不谈技术, 来聊一聊我最近在 Quora 上看到的一些精彩回答.&lt;/p&gt;
&lt;h2 id=&quot;一为何智者多平静&quot;&gt;一、为何智者多平静?&lt;/h2&gt;
&lt;p&gt;先放链接: &lt;a href=&quot;https://www.quora.com/Why-do-intelligent-people-seem-to-be-calm-all-the-time/answer/Domen-Grabec?srid=uiHgX&quot;&gt;Why do intelligent people seem to calm all the time?&lt;/a&gt;【5.4k】&lt;/p&gt;
&lt;p&gt;答主引用了达赖喇嘛的一段话作答:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果能解决问题, 就没必要担忧; 如果解决不了, 担忧又有何用?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;且不论达赖喇嘛这个人, 这句话还是很实在的.&lt;/p&gt;
&lt;p&gt;类似的话, 我高中的时候看 Sky 的自传《当李晓峰成为SKY》也看到过, 原句是:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只有无路可走的人才会生气, 只要有解决问题的方法, 就不必生气.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这句话我一直记着, 五六年过去了, 我有时候还是会发脾气, 会因为拖延症和 Deadline 而焦躁不安. 显然我没做好, 但每当我过分焦躁的时候, 想起这句话, 我就会做几个深呼吸, 放下不必要的负面情绪, 开始行动.&lt;/p&gt;
&lt;p&gt;担忧、焦躁等负面情绪对于解决问题不仅毫无帮助, 反而增加了心理负担, 而且容易使人丧失理性思考的能力, 更容易犯错, 弄巧成拙. 与其浪费时间焦虑担忧, 然后所忧之事成真, 带来深深的挫败感, 不如去做那些该做的或者能做的, 就算最终于事无补, 也能减少一些负罪感.静下心来做事, 人就会暂时忘了烦恼, 理性思考会回来, 就能针对当前情况重新规划, 合理安排. 就像用高徳地图导航, 驶错了方向, 但继续前进, 系统就会提示: 您可能已偏离路线, 正在为您重新规划; 但如果停下车对着地图开始担心是不是驶错了方向, 究竟该往哪儿开, 系统什么也不会提示, 只会停滞不前.&lt;/p&gt;
&lt;p&gt;今天看了《教父1》, 教父 Vito 说:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我花了一辈子, 就学会了小心.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果有什么是我们应该早点知道, 余生可因此受益的, 我想&lt;strong&gt;负面情绪控制&lt;/strong&gt;应当是其中之一.&lt;/p&gt;
&lt;h2 id=&quot;二你学到的最聪明的生活技巧是什么&quot;&gt;二、你学到的最聪明的生活技巧是什么?&lt;/h2&gt;
&lt;p&gt;同样地, 链接在这里: &lt;a href=&quot;https://www.quora.com/What-is-the-most-clever-life-hack-youve-learned/answer/Erick-Diaz-16?srid=uiHgX&quot;&gt;What is most clever life hack you’ve learned?&lt;/a&gt;【20.7k】&lt;/p&gt;
&lt;p&gt;答主开门见山地答道:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每当我想偷懒或颓废的时候, 我就想象 80 岁的我正注释着我.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;单看这一句话, 很有一种《1984》中时刻被老大哥注视着的感觉, 只不过这老大哥是本人罢了.&lt;/p&gt;
&lt;p&gt;其实这个回答是学会珍惜时间的一个技巧.&lt;/p&gt;
&lt;p&gt;《钢铁是怎样炼成的》中保尔柯察金说过:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;人最宝贵的就是生命, 生命对于每个人来说只有一次. 人的一生应该这样度过: 回首往事, 他不会因为虚度年华而悔恨, 也不会因为碌碌无为而羞愧.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不知道答主是不是也背过这段话, 总之有异曲同工之妙. 想象着多年后的自己在回(审)忆(视)着此时此刻, 人大概都不敢有一丝一毫浪费时间的心思吧.&lt;/p&gt;
&lt;p&gt;而事实上, 当我尝试去回忆那些偷懒、无聊的时刻, 什么都想不起来, 仿佛那些岁月都是空白. 我能想起初中以来看过的网文的书名, 但是何年何月何时何地看的, 全都记不起; 我也能想起高中那段疯狂打 DotA 的岁月, 能想起那些一起热血沸腾的人, 但仅此而已. 那些岁月, 宛若水过无痕.&lt;/p&gt;
&lt;p&gt;在这个回答里, 答主还提到, 随着人的成长, 时间被分成越来越大的时间块. 上学的时候, 一堂课 45 分钟, 课间休息 15 分钟; 上了大学之后, 空余的时间更多了, 时间被分成上课和课余两大块; 工作之后, 一个项目、一份报告常常要做几个月, 而一份工作也许是十年二十年. (我似乎可以去知乎回答“为什么越长大时间过得越快”问题了.)&lt;/p&gt;
&lt;p&gt;这个回答有点工作狂的意味, 它鼓励利用一分一秒的时间来做一些&lt;em&gt;有意义&lt;/em&gt;的事, 即使在劳累了一天之后, 只要还剩一丝精力, 也有时间的话. 而把回顾回忆这样的活儿留到 80 岁, 而那时你也能像保尔柯察金那样无愧、甚至有些自豪. 对于这一点, 笔者持保留意见, 众位看客自行取舍吧.&lt;/p&gt;
&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;先放两则吧. 最后, 推荐一下 &lt;a href=&quot;https://www.quora.com/&quot;&gt;Quora&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;我之前给人推荐的时候, 是这样说的: 知乎就是中国版的 Quora. (冒犯知乎了, 知乎已经走出了自己的路) 两者比较的话, 知乎会更接地气点, 而 Quora 给我的感觉会更高雅一点? 没错, 是问号, 找不到更好的修辞: Quora 上的提问总体感觉更严肃一点, 不会像知乎上各种千奇百怪的问题都有, 比如“杨过和小龙女谈恋爱, 神雕什么感受” (并不是说这样不好); 而 Quora 上的回答, 很少有瞎答的, 回答往往给人赏心悦目的感觉.&lt;/p&gt;
&lt;p&gt;One more thing, Quora 是少数国人可直接访问的海外网站. 欢迎众看客们去围观, 上面会有一些让国人哭笑不得的神奇问题.&lt;/p&gt;
</content:encoded><category>笔记</category><category>生活</category></item><item><title>[译|转]Python: 利用 Bokeh 进行可视化</title><link>https://lifelonglove.cn/articles/python-visualization-with-bokeh/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/python-visualization-with-bokeh/</guid><description>通过少量 Python 示例介绍 Bokeh 的图形、数据源、交互工具和浏览器输出方式，展示如何快速把静态数据变成可缩放、可悬停查看的网页图表。</description><pubDate>Mon, 29 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文作者: Mike Driscoll\
原文连接: &lt;a href=&quot;http://www.blog.pythonlibrary.org/2016/07/27/python-visualization-with-bokeh/&quot;&gt;Python: Visualization with Bokeh&lt;/a&gt;\
译者：kissg(赵喧典)\
本文最先发表于微信公众号“编程派”，是本人在 PythonTG 翻译组的译文，相当于自我转载。编程派地址，如需转载，请联系微信公众号“编程派”获得授权。转载时注明来源，作者及原文链接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://bokeh.pydata.org/en/latest/&quot;&gt;Bokeh 包&lt;/a&gt;是一个交互式的可视化库。其利用 web 浏览器进行展示，目标是以 D3.js 的风格绘制图案，这样图会看起来很优美，而且很容易构造。Bokeh 支持大量的流式的数据集。你可以用这个库创建各种图表/图形。它的一个主要竞争对手可能要属 &lt;a href=&quot;https://plot.ly/&quot;&gt;Plotly&lt;/a&gt; 了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;译注：D3.js 是一个可用于创建“数据驱动文档”（Data Driven Documents） JavaScript 库。&lt;a href=&quot;https://d3js.org/&quot;&gt;详情看这里&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;注意：这不是一篇关于 Bokeh 库的深度教程，因为它所能绘制的不同图表和可视化图形实在太多了。因此，本文的目的是带读者领略一下 Bokeh 库的丰姿，看看它能做哪些有趣的事情。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;让我们花一点时间安装 Bokeh。最简单的方式是使用 pip 或 conda，比如使用 pip：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bokeh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条命令会安装 Bokeh 以及它所有的依赖包。因为这个原因，你可能想要在一个虚拟环境下安装 Bokeh，但这完全取决于你。现在，让我们通过一个简单的例子，检查是否安装成功。将下面的代码保存到文件，文件名按你喜欢的来就好。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bokeh.plotting &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; figure, output_file, show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output_file(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/path/to/test.html&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;plot &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; figure(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Line example&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;x_axis_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;y_axis_label&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;y&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;plot.line(x, y, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;legend&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Test&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;line_width&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;show(plot)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，我们仅仅从 Bokeh 库导入了一些条目，也仅仅说明了将输出保存到哪里。你会注意到，输出是 HTML 文档 。然后我们为 x 轴和 y 轴生成了一些值，用于创建图表。再然后，我们创建了一个 figure 对象，设置了标题和两个坐标轴的标签。最后，我们画出这条折线，给出了图例，设置了线宽，并显示。命令 show 会自动打开你的默认浏览器，并在其中显示图表。最终你看到的将是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-08-29-python-visualization-with-bokeh/bokeh_line.png&quot; alt=&quot;Line example&quot;&gt;&lt;/p&gt;
&lt;p&gt;Bokeh 还支持 Jupyter Notebook，唯一需要修改的就是用 &lt;strong&gt;output_notebook&lt;/strong&gt; 代替 &lt;strong&gt;output_file&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;译注：output_notebook()，不再需要参数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bokeh 的&lt;a href=&quot;http://bokeh.pydata.org/en/latest/docs/user_guide/quickstart.html#userguide-quickstart&quot;&gt;快速入门指南&lt;/a&gt;已经提供了一个在网格线上绘制一系列正弦波的简明例子。我稍微削减了代码，只留下一个正弦波。注意了，要让下面的例子能正常执行，你需要先安装好 NumPy。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; numpy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bokeh.layouts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gridplot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bokeh.plotting &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; figure, output_file, show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;N &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.linspace(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;np.pi, N)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;y0 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; np.sin(x)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output_file(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sinewave.html&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; figure(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;plot_height&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Sine&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sine.circle(x, y0, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;navy&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;alpha&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gridplot([[sine]], &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;toolbar_location&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;show(p)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子与前一个的主要不同是，我们用 NumPy 来生成数据点，以及我们将图形放在了&lt;strong&gt;网格线&lt;/strong&gt;内部，而不是画出图形本身。当你运行这段代码，最终看到的图表应该是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-08-29-python-visualization-with-bokeh/bokeh_sine_wave.png&quot; alt=&quot;Sine&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果你不喜欢圆形，Bokeh 还支持其他的形状，总有你喜欢的，比如正方形，三角形，以及其他多种图形。&lt;/p&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;Bokeh 项目确实很有趣，它提供了简单易用的 API，用于创建图案、图表和其他数据可视化形式。Bokeh 的文档梳理得相当好，它包含了大量的例子，以展示你都能用它做什么。你值得浏览一遍它的文档，这样你会见识到，其他的一些图表是长什么样的，以及生成如此美丽结果的代码是多么简短。&lt;/p&gt;
&lt;p&gt;我唯一抱怨的一点是，Bokeh 并没有提供一种通过编程就能保存图片的方法。两年来，这一直是他们致力于修改的 &lt;a href=&quot;https://github.com/bokeh/bokeh/issues/538&quot;&gt;bug&lt;/a&gt;。好消息是，他们找到了一种方式，也许不久就会支持这一功能。除此之外，我觉得它简直酷毙了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;译注, 结语: 我承认这篇译文确实很水, 但原文就是这样的. 当时在学习数据可视化, 就认领了这篇有关 Bokeh 的, 没注意这么水…&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>文章</category><category>Python</category></item><item><title>语言之争: 编程语言 vs. 自然语言</title><link>https://lifelonglove.cn/articles/language-arguments/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/language-arguments/</guid><description>借程序员对操作系统、编辑器和编程语言的偏好之争，类比文学与翻译中的语言选择，主张工具各有语境，真正重要的是用它完成表达与创造。</description><pubDate>Sat, 20 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;有人的地方就有江湖, 有江湖的地方就有恩怨, 人就是江湖.    ——任我行&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;程序猿的江湖, 有没有恩怨, 我不知道. 我知道的是, 程序猿有流派之分, 喜好之争.&lt;/p&gt;
&lt;p&gt;有人用 Linux, 有人用 Mac, 有人用 Windows, 这是操作系统之争; 有人用 Vim, 有人用 Emacs, 有人用其他现代编辑器, 这是编辑器之争; 有人用 C, 有人用 Java, 有人用 Python, 有人用其他编程语言, 这是编程语言之争.&lt;/p&gt;
&lt;p&gt;程序猿相轻, 就同文人相轻一样, 自古而然. 大家都是各自使用的操作系统/编辑器/编程语言的拥趸, 难免会有争执, 都想证明自己的选择是最好的. 我的看法是: 没必要争锋相对, 都是优秀的产品, 我自喜欢我所喜欢的, 如果你也喜欢, 那么谢谢你的认可; 如果你不喜欢, 也请你不要随意贬低它们.&lt;/p&gt;
&lt;p&gt;本文意不在讨论这些, 而是想写一些我个人关于编程语言与自然语言的思考.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我在大学里接触过不少编程语言, 包括课内学的 C++, Java, 汇编语言, C#, 和课外的 HTML5 (严格来讲, H5 是标记语言而不是编程语言), JavaScript, Python, C. 现在在用的是 Python, 也想用 C. 接触过这些编程语言, 一个很明显的感受是: &lt;code&gt;不同语言真的很不同. 抽象程度不同, 语法不同, 程序的执行方式不同, 入门的门槛不同, 代码量不同, 编写代码的难易程度不同, 代码的执行效率不同...&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如下所示, 是 HelloWorld 程序的不同编程语言实现. (代码选自&lt;a href=&quot;https://zh.wikipedia.org/wiki/Hello_World%E7%A8%8B%E5%BA%8F%E6%A0%B7%E4%BE%8B&quot;&gt;维基百科 Hello World 程序样例&lt;/a&gt;, 未修改, 算是各语言的 Hello World 的比较标准的写法.)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Python 代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;♥&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// C 代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    printf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Hello, world!&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;♥&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;java&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Java 代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Hello&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        System.out.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;♥&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;; 汇编语言代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[BITS 16]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;org 0x7c00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov ax,cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov ds,ax&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov es,ax&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     call DispStr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     jmp $;End Hear&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DispStr:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov ax, BootMessage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov bp, ax&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov cx, 16;How long is the String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov ax, 0x1301&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov bx, 0x000c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     mov dl, 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     int 0x10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     ret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;BootMessage: db &quot;Hello, world!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;times 510-($-$$) db 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dw 0xaa55; Bootable Mark&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如你所见, 这些不同编程语言的代码做的都是同一件事: 在屏幕上打印“Hello, World!”. 计算机内部的执行过程可能不同, 暂且不去管它. 很明显地, 不同编程语言对同一个操作的描述各有不同. 这跟我们用不同的自然语言来表达同一概念是一个意思, 比如中文的“你好”, 用英语说就是“Hello”, 用日语说就是“こんにちは”.&lt;/p&gt;
&lt;p&gt;在学习用汇编语言做进制转换的时候, 我的老师告诉我: 你要清楚的一点是, 不管用哪种进制表示, 不变的是数目. 十六进制的 A, 十进制的 10, 二进制的 1010, 它们都是那个数目的表示而已, 再怎么转换, A(16) 不会变成 11(10). 编程也是同理. &lt;code&gt;虽然语言不同, 表达方式不同, 但概念是同一个概念, 操作是同一个操作, 万变不离其宗.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;程序, 科学的定义是: 算法 + 数据结构. 大可以不必这么讲究, &lt;code&gt;程序它就是特定的一系列动作, 行动或操作&lt;/code&gt;. 而&lt;code&gt;所谓的编程, 就是将我们设计好的动作序列用编程语言的方式描述给计算机看, 告诉计算机如何操作.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;最近, 我翻译了几篇文章, 越来越感受到编程语言与自然语言的共通之处: &lt;code&gt;笔者用文字(自然语言)表达想法, 写给人看, 程序猿用编程语言表达想法, 写给计算机看, 表达的都是想法; 翻译人员的职责是将一种自然语言转换成另一种自然语言, 程序猿的&quot;翻译&quot;则是将一种编程语言转换成另一种编程语言, 描述的是同一回事.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;所以, 广义上, 程序猿也算是“作家”和“翻译家”吧. Eric S. Raymond 在 &lt;a href=&quot;http://www.catb.org/esr/faqs/hacker-howto.html&quot;&gt;How to Become A Hacker&lt;/a&gt; 一文中写到, &lt;code&gt;很多黑客都是好的作家&lt;/code&gt;, 应该有触类旁通的意思. 写作/翻译能锻炼一个人的表达能力, 考量程序猿优秀与否的一个标准是编程能力, 其实也是表达能力, 比如说, 普通程序猿一股脑儿用嵌套的方式写逻辑判断或循环结构, 优秀的程序猿用逻辑操作符(and, or 等)写不嵌套的逻辑判断, 用抽象方法写不嵌套的循环结构(关于这一点, 可参看&lt;a href=&quot;/articles/breaking-out-of-two-loops/&quot;&gt;我的这篇译文&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;而为什么算法和数据结构对编程这么重要? 还是拿写作类比的话, 算法是句型句式, 数据结构是词藻, 而写作不就是遣词造句, 程序不就是算法加数据结构吗?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;编程语言, 从最初的只有 0 与 1 的机器语言, 到后来的符号语言——汇编语言, 再到高级语言的 C 语言, 抽象程度更高的 Python 等语言. 可以看出, 这其实是一个越来越人性化, 编程语言越来越向自然语言靠拢的过程.&lt;/p&gt;
&lt;p&gt;虽然说, 计算机实际只能读懂 01 代码, 随着编程语言的抽象程度越来越高, 程序猿编辑的代码转换成机器真正执行的机器代码的中间步骤越来越多, 程序的效率是变低了, 但&lt;code&gt;编程的门槛变得更低了, 程序开发的效率变得更高了&lt;/code&gt;. 这意味着, 全民编程的时代越来越近了, 编程可以像说话写作一样容易, 也可以所想即所得 (What You See Is What You Get). 以后的程序开发, 可以将更多的精力投入在构思, 而不是实现上. 亦即逻辑越来越复杂, 功能越来越强大, 但代码并没有变得更复杂.&lt;/p&gt;
&lt;p&gt;至于程序效率的问题, 软硬件的方式都可以适当缓解. 说是缓解, 因为中间翻译解释造成效率变低, 是不可挽回的.&lt;/p&gt;
&lt;p&gt;另外, 若非要写机器级的代码, 可以用高抽象层次的编程语言(比如 Python), 作为到最终的编程语言(比如 C 语言)之间的过渡, 先写出原型, 在将整个业务逻辑理清理顺之后, 再对代码用 C 语言进行重构.&lt;/p&gt;
&lt;p&gt;最后, 用一句不太正确的话结尾:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;存在的即是合理的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;编程语言越来越&lt;code&gt;自然语言化&lt;/code&gt;的发展, 自有其道理.&lt;/p&gt;
</content:encoded><category>文章</category><category>杂</category><category>小说</category></item><item><title>[译|转]我是如何写一个 Slack 机器人以帮助我在旧金山找到房子的</title><link>https://lifelonglove.cn/articles/apartment-finding-slackbot/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/apartment-finding-slackbot/</guid><description>一位旧金山租客用 Python 抓取房源、按条件打分并通过 Slack 实时提醒，把高压找房流程变成可快速筛选和及时联系房东的自动化系统。</description><pubDate>Sun, 14 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文作者: Vik Paruchuri\
原文连接: &lt;a href=&quot;https://www.dataquest.io/blog/apartment-finding-slackbot/&quot;&gt;How I built a Slack bot to help me find an apartment in San Francisco&lt;/a&gt;\
译者：kissg(赵喧典)\
本文最先发表于微信公众号“编程派”，是本人在 PythonTG 翻译组的译文，相当于自我转载。&lt;a href=&quot;http://codingpy.com/article/apartment-finding-slackbot/&quot;&gt;编程派地址&lt;/a&gt;，如需转载，请联系微信公众号“编程派”获得授权。转载时注明来源，作者及原文链接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;数月前，我从波士顿搬到了湾区。我和 Priya（我女朋友）都听说了各种关于租赁市场的恐怖故事。事实是，找房子是一个痛苦的过程。在 Google 上搜索“How to find an apartment in San Francisco”，得到的&lt;a href=&quot;https://www.google.com.hk/search?q=how+to+find+an+apartment+in+san+francisco&amp;amp;gws_rd=cr,ssl&quot;&gt;许多&lt;/a&gt;建议的页面就是很好的证明。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-08-14-apartment-finding-slackbot/boston.jpg&quot; alt=&quot;Boston&quot;&gt;
&lt;small&gt;波士顿很冷，但在旧金山找房子很可怕&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;我们了解到一些房东会举行开放日活动，届时你需要带上所有的证明，并且只有当你交了押金才会被考虑。我们对流程进行了详尽的研究，发现找房子的时机很重要。一些房东举行开放日活动，任何人都可以参加，而对于另一些房东，第一个去看房往往更能租到房子。因此你需要列一个有关房子的清单，快速审核房子是否符合你的标准，然后打电话给房东安排看房。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;译注：在国外的房产交易行业中，开放日是一种新颖的房产销售方式。它允许对房子感兴趣的人们直接去参观房子。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们浏览了网络上推荐的一些房子租赁网站，比如 &lt;a href=&quot;https://www.padmapper.com/&quot;&gt;Padmapper&lt;/a&gt; 和 &lt;a href=&quot;https://livelovely.com/&quot;&gt;LiveLovely&lt;/a&gt;，但是没有一个网站能为我们提供一个可供查看与评估的实时信息，也没有一个网站能让我们指定额外的标准，比如特定的社区，或者交通便利性。绝大多数湾区房子的清单信息原本都在 &lt;a href=&quot;https://www.craigslist.com/&quot;&gt;Craigslist&lt;/a&gt; 上，之后才被其他站点采集，这就造成了一点担忧：（其他站点）采集的清单信息可能不全，或者它们采集得不够迅速，实时性不强。&lt;/p&gt;
&lt;p&gt;我们想要这样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 Craigslist 上有新的公告时，实时地获得通知。&lt;/li&gt;
&lt;li&gt;过滤掉不是我们期望的社区的房子。&lt;/li&gt;
&lt;li&gt;过滤掉不满足额外标准的的房子，比如公共交通便利性。&lt;/li&gt;
&lt;li&gt;整合房子的清单信息，以便对它们进行评估。&lt;/li&gt;
&lt;li&gt;对于我们感兴趣的房子，要能方便地联系房东。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对问题进行过思考后，我意识到我们可以分四步解决问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 Craigslist 采集清单信息。&lt;/li&gt;
&lt;li&gt;过滤掉不匹配我们的标准的房子。&lt;/li&gt;
&lt;li&gt;将清单信息发送到 &lt;a href=&quot;https://slack.com/&quot;&gt;Slack&lt;/a&gt;，这是一个团队聊天工具，这样我们就能讨论并评估房子。&lt;/li&gt;
&lt;li&gt;将整个过程封装进一个持续的循环中，并部署到服务器上（这样它就能一直运行了）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在本文的余下部分，我们将一一看到每一块是如何建立的，以及如何使用最终的 Slack 机器人帮助我们找房子。借助这个机器人，我和 Priya 在约一周之后就找到了一个我们都喜爱的，价格又合理（就旧金山而言）的卧室，这比我们预期要花费的时间少多了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想要在阅读本文的过程中看一看代码，项目链接在&lt;a href=&quot;https://github.com/VikParuchuri/apartment-finder&quot;&gt;这里&lt;/a&gt;，README.md的链接在&lt;a href=&quot;https://github.com/VikParuchuri/apartment-finder/blob/master/README.md&quot;&gt;这里&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;第一步---从-craigslist-采集清单信息&quot;&gt;第一步 - 从 Craigslist 采集清单信息&lt;/h2&gt;
&lt;p&gt;创建机器人的第一步是从 Craigslist 获取清单信息。不幸的是，Craigslist 并不提供 API，但是我们可以使用 &lt;a href=&quot;https://github.com/juliomalegria/python-craigslist&quot;&gt;python-craigslist&lt;/a&gt; 包来获得房子的公告。用 &lt;code&gt;python-craigslist&lt;/code&gt; 采集页面内容，再用 &lt;a href=&quot;https://www.crummy.com/software/BeautifulSoup/&quot;&gt;BeautifulSoup&lt;/a&gt; 从页面中提取出相关的部分，并转换成结构化的数据。这个包的代码相当简短，值得通读一遍。&lt;/p&gt;
&lt;p&gt;Craigslist 网上，旧金山房子信息的网址是  &lt;code&gt;https://sfbay.craigslist.org/search/sfc/apa&lt;/code&gt;。在下面的代码中，我们将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;导入 &lt;code&gt;craigslistHousing&lt;/code&gt;，这是 &lt;code&gt;python-craigslist&lt;/code&gt; 中的一个类。&lt;/li&gt;
&lt;li&gt;用以下参数初始化类：&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site&lt;/code&gt; - 要采集的 Craigslist 网站。&lt;code&gt;site&lt;/code&gt; 是 URL 的第一部分，比如 &lt;code&gt;https://sfbay.craigslist.org&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;area&lt;/code&gt; - 要采集的网站下的分区。&lt;code&gt;area&lt;/code&gt; 是 URL 的最后部分，比如 &lt;code&gt;https://sfbay.craigslist.org/sfc/&lt;/code&gt;，仅代表旧金山。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;category&lt;/code&gt; - 要查找的房子的类型。&lt;code&gt;category&lt;/code&gt; 是搜索 URL 的最后部分，比如 &lt;code&gt;https://sfbay.craigslist.org/search/sfc/apa&lt;/code&gt;，将将列所有的房子。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filters&lt;/code&gt; - 应用于结果的任何过滤器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_price&lt;/code&gt; - 能承受的最高价&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_price&lt;/code&gt; - 要查找的最低价&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;get_results&lt;/code&gt; 方法从 Craigslits 获取结果，其实是一个&lt;a href=&quot;https://wiki.python.org/moin/Generators&quot;&gt;生成器&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;传入 &lt;code&gt;geotagged&lt;/code&gt; 参数以尝试为每条结果添加坐标。&lt;/li&gt;
&lt;li&gt;传入 &lt;code&gt;limit&lt;/code&gt; 参数以只获取 &lt;code&gt;20&lt;/code&gt; 条结果。&lt;/li&gt;
&lt;li&gt;传入 &lt;code&gt;newest&lt;/code&gt; 参数以只获取最新的清单信息&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;results&lt;/code&gt; 生成器中获取每条 &lt;code&gt;result&lt;/code&gt;，并打印。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; craigslist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CraigslistHousing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;cl &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; CraigslistHousing(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;site&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sfbay&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;area&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sfc&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;category&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;apa&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                         filters&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;max_price&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;min_price&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;results &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; cl.get_results(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;sort_by&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;newest&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;geotagged&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;limit&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; results:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们已经快速地完成了机器人的第一步！现在，我们就可以对 Craigslist 进行采集并获取清单信息了。每一条 &lt;code&gt;Result&lt;/code&gt; 都是带几个字段的字典：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;datetime&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;2016-07-20 16:39&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;geotag&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.783166&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.418671&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;has_image&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;has_map&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;5692904929&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;name&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Be the first in line at Brendas restaurant!SQuiet studio available&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;price&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;$1995&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;url&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;http://sfbay.craigslist.org/sfc/apa/5692904929.html&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;where&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;tenderloin&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是对字段的描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;datetime&lt;/code&gt; - 清单信息公布的时间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;geotag&lt;/code&gt; - 清单信息上标注的坐标位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_image&lt;/code&gt; -  Craigslist 公告上是否带图片。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_mag&lt;/code&gt; - 清单信息是否带有相应的地图。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt; - 清单信息在 Craigslist 上的 id。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; - Craigslist 上显示的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;price&lt;/code&gt; - 月租价。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt; - 查看完整的清单信息的 URL。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;where&lt;/code&gt; - 清单信息创建者标注的房子位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;第二步---过略结果&quot;&gt;第二步 - 过略结果&lt;/h2&gt;
&lt;p&gt;既然我们已经能够从 Craigslist 上获取清单信息了，我们只需对它们进行过滤，就可以看到我们感兴趣的那些。&lt;/p&gt;
&lt;h3 id=&quot;地区过滤&quot;&gt;地区过滤&lt;/h3&gt;
&lt;p&gt;我和 Priya 在找房子时，我们只考虑了一部分区域，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧金山&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Sunset_District,_San_Francisco&quot;&gt;日落区&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Pacific_Heights,_San_Francisco&quot;&gt;太平洋高地&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lower_Pacific_Heights,_San_Francisco&quot;&gt;下太平洋高地&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Bernal_Heights,_San_Francisco&quot;&gt;伯纳尔高地&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Richmond_District,_San_Francisco&quot;&gt;列治文区&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;伯克利&lt;/li&gt;
&lt;li&gt;奥克兰&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Adams_Point,_Oakland,_California&quot;&gt;亚当斯点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lake_Merritt&quot;&gt;梅里特湖&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Rockridge,_Oakland,_California&quot;&gt;岩石岭&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;阿拉米达&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了对社区进行过滤，我们首先需要定义包围盒，用于划出一个边界区域：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-08-14-apartment-finding-slackbot/bounding_box.png&quot; alt=&quot;bounding_box&quot;&gt;
&lt;small&gt;在下太平洋区域画一个包围盒&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;上图中的包围盒是用 &lt;a href=&quot;http://boundingbox.klokantech.com/&quot;&gt;BoundingBox&lt;/a&gt; 创建的。在左下角选择 &lt;code&gt;csv&lt;/code&gt; 选项，以获得包围盒的顶点坐标。&lt;/p&gt;
&lt;p&gt;你也可以使用像谷歌地图这样的工具，通过找出左下角和右上角的坐标来自定义包围盒。找出包围盒之后，我们创建一个社区与坐标的字典：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;BOXES&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;adams_point&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.80789&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.25000&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.81589&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,	&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.26081&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;piedmont&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.82240&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.24768&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.83237&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.25386&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用社区名做字典的键，每个键对应一个列表的列表。第一个内部列表表示包围盒左下角的坐标，第二个则表示右上角的坐标。然后，我们就可以通过检查坐标是否在某个包围盒内进行过滤。&lt;/p&gt;
&lt;p&gt;下面的代码将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遍历 &lt;code&gt;BOXES&lt;/code&gt; 的键。&lt;/li&gt;
&lt;li&gt;检查结果是否在包围盒内。&lt;/li&gt;
&lt;li&gt;若结果在包围盒内，设置合适的变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; in_box&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(coords, box):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; box[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; coords[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; box[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; box[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; coords[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; box[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;geotag &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;geotag&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;area_found &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a, coords &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; BOXES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.items():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; in_box(geotag, coords):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        area_found &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而不幸的是， 并不是所有从 Craigslist 获取的结果都带有坐标信息。是否带坐标信息，取决于发布公告的人是否指定了位置，而坐标可以从位置中计算出。他对于在 Craigslist 发布公告越熟悉，那么他越有可能附上位置信息。&lt;/p&gt;
&lt;p&gt;通常由代理发布的公告会带有位置信息，但他们往往会收取高额租金。房东自己发布的公告一般不带坐标信息，但也会更划算。因此，弄清楚那些不带坐标信息的房子是否在我们期望的社区很重要。我们将创建一个社区的列表，再进行字符串匹配，以检查那些房子是否落在其中。因为许多房子的社区信息是错误的，使得这样做的精确度不如使用坐标高，但聊胜于无。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;NEIGHBORHOODS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;berkeley north&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;berkeley&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rockridge&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;adams point&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要进行基于名字的匹配，我们可以对 &lt;code&gt;NEIGHBORHOODS&lt;/code&gt; 进行遍历：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;where&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hood &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NEIGHBORHOODS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hood &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; location.lower():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; hood&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;采集结果经以上代码处理之后，我们就过滤掉了所有不在我们想要入住的社区中的房子。可能会有一些误报，我们会遗漏掉那些既没有社区信息也没有指定位置的房子，但这个系统已经记录了大量的住房信息。&lt;/p&gt;
&lt;h3 id=&quot;就交通便利性进行过滤&quot;&gt;就交通便利性进行过滤&lt;/h3&gt;
&lt;p&gt;我和 Priya 都清楚我们会很频繁地去旧金山，因此如果我们不住在旧金山的话，我们就想住的离公交进一点。在湾区，公交的主要形式被称为 &lt;a href=&quot;https://en.wikipedia.org/wiki/Bay_Area_Rapid_Transit&quot;&gt;BART&lt;/a&gt;。BART 有一部分地下交通系统，连接了奥克兰，伯克利，旧金山，以及周围的区域。&lt;/p&gt;
&lt;p&gt;为了在我们的机器人上实现这个基础功能，我们首先需要定义一个换乘站的列表。我们可以从&lt;a href=&quot;https://maps.google.com/&quot;&gt;谷歌地图&lt;/a&gt;获取换乘站的坐标，然后建一个字典：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TRANSIT_STATIONS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;oakland_19th_bart&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.8118051&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.2720873&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;macarthur_bart&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.8265657&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.2686705&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;rockridge_bart&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;37.841286&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;122.2566329&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个键都是一个换乘站的名称，对应一个列表。该列表包括了换乘站的经度与纬度。一旦我们构建好了这个字典，我们就可以找出距离每条采集结果最近的换乘站。&lt;/p&gt;
&lt;p&gt;下面的代码将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 &lt;code&gt;TRANSIT_STATIONS&lt;/code&gt; 的键与值进行遍历。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;coord_distance&lt;/code&gt; 函数来计算两个坐标间的距离（公里）。你可以在&lt;a href=&quot;http://www.codecodex.com/wiki/Calculate_distance_between_two_points_on_a_globe#Python&quot;&gt;这里&lt;/a&gt;找到该函数的解释。&lt;/li&gt;
&lt;li&gt;检查站点是否距离房子最近。&lt;/li&gt;
&lt;li&gt;忽略太远的站点（超过 &lt;code&gt;2&lt;/code&gt; 公里，或约 &lt;code&gt;1.2&lt;/code&gt; 里）。&lt;/li&gt;
&lt;li&gt;若当前站点相比先前最近的站点还近，将其作为最近的站点。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;min_dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;near_bart &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bart_dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;N/A&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bart &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;MAX_TRANSIT_DIST&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # kilometers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; station, coords &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; TRANSIT_STATIONS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.items():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; coord_distance(coords[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], coords[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], geotag[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], geotag[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (min_dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; min_dist) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;and&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; MAX_TRANSIT_DIST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        bart &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; station&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        near_bart &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (min_dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; min_dist):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        bart_dist &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dist&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这之后，我们就清楚距离每个房子最近的站点了。&lt;/p&gt;
&lt;h2 id=&quot;第三步---创建slack机器人&quot;&gt;第三步 - 创建Slack机器人&lt;/h2&gt;
&lt;p&gt;在对采集结果进行过滤后，我们就可以将现有的信息发送到 Slack 了。如果你对 Slack 不熟悉，它其实就是一个团队聊天应用。你在 Slack 上创建一个团队，之后就可以邀请成员了。每个 Slack 团队可以有多个频道，所谓频道，就是成员交换消息的地方。频道里的其他人可以对消息进行注释，比如点赞或添加其他表情。有关 Slack 更多的信息，请看&lt;a href=&quot;https://slack.com/is&quot;&gt;这里&lt;/a&gt;。如果你想亲身体验一下 Slack，我们在 Slack 上有一个&lt;a href=&quot;https://www.dataquest.io/chat&quot;&gt;数据科学社区&lt;/a&gt;，如果你感兴趣的话，可以加入。&lt;/p&gt;
&lt;p&gt;通过将结果发送到 Slack，我们就能够与其他人合作，并找出哪些房子是最好的。要实现这一点，我们需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建一个 Slack 团队，我们可以在&lt;a href=&quot;https://slack.com/create#email&quot;&gt;这里&lt;/a&gt;完成创建工作。&lt;/li&gt;
&lt;li&gt;创建一个用于清单信息发送的频道。帮助信息请看&lt;a href=&quot;https://get.slack.help/hc/en-us/articles/201402297-Creating-a-channel&quot;&gt;这里&lt;/a&gt;。建议使用 &lt;code&gt;#housing&lt;/code&gt; 来命名频道。&lt;/li&gt;
&lt;li&gt;获取 Slack API Token，可以在&lt;a href=&quot;https://api.slack.com/docs/oauth-test-tokens&quot;&gt;这里&lt;/a&gt;获得。关于该过程的更多信息，请看&lt;a href=&quot;https://get.slack.help/hc/en-us/articles/215770388-Creating-and-regenerating-API-tokens&quot;&gt;这里&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;编起来&quot;&gt;编起来&lt;/h3&gt;
&lt;p&gt;获得了频道名和 Token 之后，我们就可以将结果发送到 Slack 了。我们将使用 &lt;a href=&quot;https://github.com/slackhq/python-slackclient&quot;&gt;python-slackclient&lt;/a&gt;来实现，这是一个使 &lt;a href=&quot;https://api.slack.com/&quot;&gt;Slack API&lt;/a&gt; 更易于使用的 Python 包。使用 Slack token 来初始化 &lt;code&gt;python-slackclient&lt;/code&gt;，然后我们通过它可以访问多个 API 端口，来管理团队与消息。&lt;/p&gt;
&lt;p&gt;下面的代码将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;SLACK_TOKEN&lt;/code&gt; 来初始化 &lt;code&gt;SlackClient&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;result&lt;/code&gt; 创建消息字符串，&lt;code&gt;result&lt;/code&gt; 包含了我们需要的一切信息，比如价格，房子所在的社区，以及 URL。&lt;/li&gt;
&lt;li&gt;使用用户名 &lt;code&gt;pybot&lt;/code&gt; 发送消息到 Slack，用机器人做头像。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; slackclient &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SlackClient&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SLACK_TOKEN&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ENTER_TOKEN_HERE&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SLACK_CHANNEL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;#housing&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SlackClient(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SLACK_TOKEN&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;desc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{0}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{1}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{2}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{3}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; | &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{4}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;area&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;price&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;bart_dist&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;], result[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sc.api_call(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;chat.postMessage&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;channel&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SLACK_CHANNEL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;desc,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    username&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;pybot&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;icon_emoji&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;:robot_face:&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一切都准备之后，Slack 机器人就可以发送房子信息到 Slack，看起来是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-08-14-apartment-finding-slackbot/slack.png&quot; alt=&quot;slack.png&quot;&gt;
&lt;small&gt;机器人运行时，房子的信息看起来是这样的。注意，你可以用表情来进行评论，比如点个赞。&lt;/small&gt;&lt;/p&gt;
&lt;h2 id=&quot;第四步---实施&quot;&gt;第四步 - 实施&lt;/h2&gt;
&lt;p&gt;既然我们已经把基础工作都做好了，现在就需要让代码持续地跑。毕竟，我们想要结果实时地被发送到 Slack 上。为了进行实施，我们需要以下步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将清单信息存储到数据库，这样，我们就不会重复地发送了。&lt;/li&gt;
&lt;li&gt;从余下的代码中分离出设置的部分，以便更容易进行调整，比如 &lt;code&gt;SLACK_TOKEN&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;创建能持久运行的循环，这样，就能每周七天，每天二十四小时不间断地进行采集。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;存储清单信息&quot;&gt;存储清单信息&lt;/h3&gt;
&lt;p&gt;第一步是使用 Python 包 &lt;a href=&quot;http://www.sqlalchemy.org/&quot;&gt;SQLAlchemy&lt;/a&gt;存储我们的清单信息。 SQLAlchemy 是一个&lt;a href=&quot;https://en.wikipedia.org/wiki/Object-relational_mapping&quot;&gt;对象关系映射&lt;/a&gt;，或者说 ORM，它可以使 Python 与数据库的交互更简单。使用 SQLAlchemy，我们需要创建一张存储清单信息的数据库表，以及一个数据库连接。使用数据库连接使向数据表添加数据更容易。&lt;/p&gt;
&lt;p&gt;在使用 SQLAlchemy 的过程中，我们将联合使用 &lt;a href=&quot;https://www.sqlite.org/&quot;&gt;SQLite&lt;/a&gt; 数据库引擎。该数据库引擎会将我们所有的数据存储到一个单一的文件 &lt;code&gt;listings.db&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;下面的代码将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;导入 SQLAlchemy。&lt;/li&gt;
&lt;li&gt;创建到 SQLite 数据库 &lt;code&gt;listings.db&lt;/code&gt; 的连接，该文件将会被创建于当前目录。&lt;/li&gt;
&lt;li&gt;定义一张数据库表 &lt;code&gt;Listing&lt;/code&gt;，它包含了 Craigslist 清单中所有相关字段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unique&lt;/code&gt; 属性的字段 &lt;code&gt;cl_id&lt;/code&gt; 和 &lt;code&gt;link&lt;/code&gt; 可以防止重复发送清单信息到 Slack。&lt;/li&gt;
&lt;li&gt;利用数据库连接创建会话，会话允许我们存储清单信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sqlalchemy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sqlalchemy.ext.declarative &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; declarative_base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sqlalchemy &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column, Integer, String, DateTime, Float, Boolean&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sqlalchemy.orm &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sessionmaker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;engine &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; create_engine(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sqlite:///listings.db&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Base &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; declarative_base()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Listing&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    A table to store data on craigslist listings.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    __tablename__ &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;listings&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    id&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(Integer, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;primary_key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    link &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    created &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(DateTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    geotag &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    lat &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(Float)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    lon &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(Float)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    price &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(Float)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    cl_id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(Integer, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;unique&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    area &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    bart_stop &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Column(String)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Base.metadata.create_all(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Session &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sessionmaker(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;session &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Session()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;既然有了数据库模型，我们只需要将每条清单信息存储到数据库就可以了，还可以避免重复。&lt;/p&gt;
&lt;h3 id=&quot;从代码中分离出配置部分&quot;&gt;从代码中分离出配置部分&lt;/h3&gt;
&lt;p&gt;下一步就是从代码中分离出配置的部分。我们将创建一个称为 &lt;code&gt;settings.py&lt;/code&gt; 的文件，用于存储配置信息。配置信息包括 &lt;code&gt;SLACK_TOKEN&lt;/code&gt;，这个需要保密，因此不需要也别提交到 git 再推送到 Github，其他的设置如 &lt;code&gt;BOXES&lt;/code&gt;，不算私密，但我们希望能够进行简单地编辑。&lt;/p&gt;
&lt;p&gt;我们将以下设置放在 &lt;code&gt;settings.py&lt;/code&gt;中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MIN_PRICE&lt;/code&gt; - 要搜索的最低房价。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_PRICE&lt;/code&gt; - 要搜索的最高房价。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRAIGSLIST_SITE&lt;/code&gt; - 要搜索的 Craigslist 区域站点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AREAS&lt;/code&gt; - 要搜索的 Craigslist 区域站点的地区列表。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BOXES&lt;/code&gt; - 要查看的社区的坐标包围盒。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEIGHBORHOODS&lt;/code&gt; - 若房子信息中不带坐标信息，用社区列表去匹配。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_TRANSIT_DIST&lt;/code&gt; - 期望的与公交换乘站的最大距离。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TRANSIT_STATION&lt;/code&gt; - 公交换乘站的坐标。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRAIGSLIST_HOUSING_SECTION&lt;/code&gt; - 要查看的 Craigslist 住房分部。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SLACK_CHANNEL&lt;/code&gt; - 机器人发送消息的 Slack 频道。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们还将创建一个 &lt;code&gt;private.py&lt;/code&gt; 文件，它包含以下字段，并自动被 git 忽略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SLACK_TOKEN&lt;/code&gt; - 发送到 Slack 团队的 token。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;创建循环&quot;&gt;创建循环&lt;/h3&gt;
&lt;p&gt;最后，我们需要创建一个循环，以持续运行我们的采集代码。下面的代码将：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当通过命令行调用时：&lt;/li&gt;
&lt;li&gt;打印包含当前时间的状态消息。&lt;/li&gt;
&lt;li&gt;通过调用 &lt;code&gt;do_scrape&lt;/code&gt; 函数运行 Craigslist 采集代码。&lt;/li&gt;
&lt;li&gt;当用户输入 &lt;code&gt;Ctrl + C&lt;/code&gt; 时，退出。&lt;/li&gt;
&lt;li&gt;通过打印回溯信息来处理异常，继续执行不退出。&lt;/li&gt;
&lt;li&gt;若无异常，打印一条成功的消息（对应于下述的 &lt;code&gt;else&lt;/code&gt; 子句）。&lt;/li&gt;
&lt;li&gt;周期性地采集/休眠。默认的周期为 &lt;code&gt;20&lt;/code&gt; 分钟。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; scraper &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; do_scrape&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; traceback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __name__&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;__main__&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: Starting scrape cycle&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(time.ctime()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            do_scrape()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; KeyboardInterrupt&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Exiting....&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            sys.exit(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Exception&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exc:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Error with the scraping:&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, sys.exc_info()[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            traceback.print_exc()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: Successfully finished scraping&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(time.ctime()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        time.sleep(settings.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SLEEP_INTERVAL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们还需要将 &lt;code&gt;SLEEP_INTERVAL&lt;/code&gt; 添加到 &lt;code&gt;settings.py&lt;/code&gt;中，以控制采集的频率。默认的周期是 &lt;code&gt;20&lt;/code&gt; 分钟。&lt;/p&gt;
&lt;h3 id=&quot;运行&quot;&gt;运行&lt;/h3&gt;
&lt;p&gt;既然编程工作已经结束，让我们来看看怎么运行这个 Slack 机器人吧。&lt;/p&gt;
&lt;h4 id=&quot;本地运行&quot;&gt;本地运行&lt;/h4&gt;
&lt;p&gt;你可以在 &lt;code&gt;Github&lt;/code&gt; 上找到&lt;a href=&quot;https://github.com/vikparuchuri/apartment-finder&quot;&gt;该项目&lt;/a&gt;。在 &lt;code&gt;README.md&lt;/code&gt; 中，你可以看到更详细的安装说明。除非你对安装程序很有经验，并且正在使用 Linux，否则建议你按照 &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt;部分的说明进行安装。Docker 是一个能使创建和部署应用更简单的工具。以这种方式，你可以很快地在本地计算机上运行 Slack 机器人。&lt;/p&gt;
&lt;p&gt;下面是通过 Docker 安装运行 Slack 机器人的基本说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建一个名为 &lt;code&gt;config&lt;/code&gt; 的文件夹，将 &lt;code&gt;private.py&lt;/code&gt; 放到其中。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;private.py&lt;/code&gt; 中定义的任何设置，都会覆盖 &lt;code&gt;settings.py&lt;/code&gt; 中的同名默认设置。&lt;/li&gt;
&lt;li&gt;通过在 &lt;code&gt;private.py&lt;/code&gt; 中添加设置，你可以自定义机器人的行为。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;private.py&lt;/code&gt; 中为上述设置项指定新的值。&lt;/li&gt;
&lt;li&gt;比如，你可以在 &lt;code&gt;private.py&lt;/code&gt; 中添加 &lt;code&gt;AREAS = [&apos;sfc&apos;]&lt;/code&gt;，仅仅查看旧金山。&lt;/li&gt;
&lt;li&gt;如果你想发送消息的 Slack 频道不叫 &lt;code&gt;housing&lt;/code&gt;，设置 &lt;code&gt;SLACK_CHANNEL&lt;/code&gt; 的值。&lt;/li&gt;
&lt;li&gt;如果你不想查看湾区的住房信息，你至少需要更新以下设置项：&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRAIGSLIST_SITE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AREAS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BOXES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEIGHBORHOODS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TRANSIT_STATIONS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRAIGSLIST_HOUSING_SECTION&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MIN_PRICE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAX_PRICE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;根据&lt;a href=&quot;https://docs.docker.com/engine/installation/&quot;&gt;这里的指示&lt;/a&gt;，安装 Docker。&lt;/li&gt;
&lt;li&gt;使用默认配置运行机器人：&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker run -d -e SLACK_TOKEN={YOUR_SLACK_TOKEN} dataquestio/apartment-finder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用自定义配置运行机器人：&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker run -d -e SLACK_TOKEN={YOUR_SLACK_TOKEN} -v {ABSOLUTE_PATH_TO_YOUR_CONFIG_FOLDER}:/opt/wwc/apartment-finder/config dataquestio/apartment-finder&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;部署机器人&quot;&gt;部署机器人&lt;/h3&gt;
&lt;p&gt;除非你想要你的计算机 24/7 不间断地运行，否则有必要将机器人部署到服务器上，这样，它就能持续运行了。我们可以在主机提供商 &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt; 处创建服务器。DigitalOcean 可以自动创建一个&lt;a href=&quot;https://www.digitalocean.com/features/one-click-apps/docker/&quot;&gt;带 Docker&lt;/a&gt; 的服务器。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-use-the-digitalocean-docker-application&quot;&gt;这里&lt;/a&gt;是 DigitalOcean 上的 Docker 使用指南。如果你不清楚作者所谓的 “shell”，&lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-connect-to-your-droplet-with-ssh&quot;&gt;这里&lt;/a&gt;是一份使用 SSH 连接到 DigitalOcean 的教程。如果你不想看指南，可以从&lt;a href=&quot;https://www.digitalocean.com/features/one-click-apps/docker/&quot;&gt;这里&lt;/a&gt;开始。&lt;/p&gt;
&lt;p&gt;在 DigitalOcean 上完成服务器的创建之后，你可以用 ssh 连接的方式连接到服务器，然后按照上述 Docker 的安装与使用说明进行部署与使用。&lt;/p&gt;
&lt;h2 id=&quot;接下来&quot;&gt;接下来&lt;/h2&gt;
&lt;p&gt;完成上述的步骤之后，你就拥有了一个能自动帮你找房子的 Slack 机器人。使用这个机器人，我和 Priya 在旧金山找到了远超我们预期的好房子，并且价格比我们想象的旧金山一间卧室的价格还低。它还大大节省了我们的时间。尽管它已经为我们找到了房子，但仍有相当多可以扩展的地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用 Slack 上的赞与踩，训练一个机器学习模型。&lt;/li&gt;
&lt;li&gt;利用 API 自动拉取公交换乘站的位置。&lt;/li&gt;
&lt;li&gt;添加其他的兴趣项，如公园。&lt;/li&gt;
&lt;li&gt;添加健行指数或其他社区质量评分标准，比如犯罪。&lt;/li&gt;
&lt;li&gt;自动解析房东的电话号码与邮箱。&lt;/li&gt;
&lt;li&gt;自动打电话给房东，预约看房时间（如果你能做到这一点，你厉害）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;欢迎在 &lt;a href=&quot;https://github.com/vikparuchuri/apartment-finder&quot;&gt;Github&lt;/a&gt; 上提交 pull requests 到本项目，并且如果该工具对你有帮助，请在此留下评论。期待看到你如何使用它！&lt;/p&gt;
</content:encoded><category>文章</category><category>Python</category></item><item><title>[译|转]跳出两重循环</title><link>https://lifelonglove.cn/articles/breaking-out-of-two-loops/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/breaking-out-of-two-loops/</guid><description>面对需要同时退出两层循环的问题，可以把搜索过程抽象成生成器，再由调用方取得第一个结果；这种写法分离遍历与控制，也更容易复用和测试。</description><pubDate>Sat, 13 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文作者: Ned Batchelder\
原文连接: &lt;a href=&quot;http://nedbatchelder.com//blog/201608/breaking_out_of_two_loops.html&quot;&gt;Breaking out of two loops&lt;/a&gt;\
译者：kissg(赵喧典)\
本文最先发表于微信公众号“编程派”，是本人在 PythonTG 翻译组的译文，相当于自我转载。&lt;a href=&quot;http://codingpy.com/article/breaking-out-of-two-loops/&quot;&gt;编程派地址&lt;/a&gt;，如需转载，请联系微信公众号“编程派”获得授权。转载时注明来源，作者及原文链接。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个普遍存在的问题是，如何立即跳出嵌套的两重循环？例如，要检验字符串中是否存在相同的字符，如何在找到一对相同的字符之后就停止循环？经典的做法是，写一个两重嵌套循环，对字符串的索引进行迭代：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;a string to examine&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s)):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s)):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[j]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            answer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i, j)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            break&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # 如何 break 两次呢???&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处，我们用两重循环来生成用于检验的两个索引。当条件满足时，我们希望能同时结束这两重循环。&lt;/p&gt;
&lt;p&gt;对此，有一些常见的方法。但我个人很不喜欢它们：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将循环放在一个函数中进行，利用函数返回来跳出循环。但这样的做法不尽如人意，因为循环可能不适合重构为一个新的函数，也许你需要在循环的过程中访问其他代码。&lt;/li&gt;
&lt;li&gt;抛出异常，并在两重循环之外捕获它。这是把异常当作 goto 语句来用了。但是这里并没有异常的条件，只是变向地利用了异常机制。&lt;/li&gt;
&lt;li&gt;使用布尔变量来标记循环的结束，并在外循环中检查变量值以执行第二次 break 操作。该方法毫无技术成分，某些情况下可能是有效的，但大多数情况下只造成计算资源浪费，程序效率不高 (noise and bookkeeping)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我更偏向于使用的方法，也是我在 PyCon 2013 上提到的一种方法，&lt;a href=&quot;http://nedbatchelder.com/text/iter.html&quot;&gt;更自然地循环&lt;/a&gt;，就是将两重循环写成一重循环，然后简单地跳出循环。&lt;/p&gt;
&lt;p&gt;这需要花更多的功夫在循环上，但对于抽象化迭代过程是一次很好的练习。这正是 Python 非常擅长的，但人们也很容易忽视 Python 的这一特点，而把它当作一般的语言来使用，不能发挥循环抽象的优势。&lt;/p&gt;
&lt;p&gt;让我们重新考虑这个问题。真的需要两重循环吗？写代码之前，不妨再仔细看一遍描述：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要检验字符串中是否存在相同的字符，如何在找到一对相同的字符之后就停止循环？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这描述中，我并没有看出两重循环的意味。事实上，只需要对索引对进行一重循环就可以了。写法如下：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; unique_pairs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;在 range(n) 范围内生成索引对&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; range&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, n):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            yield&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, j&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;a string to examine&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; unique_pairs(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;len&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s)):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s[j]:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        answer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i, j)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处，我们写了一个生成器用于生成需要的索引对。现在，我们的循环就成了对索引对的一重循环，而不是对索引的两重循环。两重循环依然存在，只是被抽象了出去，移到了 unique_pairs 生成器内部。&lt;/p&gt;
&lt;p&gt;这使我们的代码更贴近自然语言的描述。还应注意到，我们不用再写两次 len(s) 了，这其实是原代码需要重构的另一个标志。而且，如果在其他地方也需要这样迭代的话，还能复用 unique_pairs 生成器。但是要记住，复用并不是写一个函数的必要条件。&lt;/p&gt;
&lt;p&gt;我知道这个方法有点奇异。但它真的是最佳的解决方案了。如果你仍旧停留在两重循环阶段，多想想你脑海中是如何组织程序的。事实上，当你尝试着一次性跳出两重循环，从某种意义上来说，这两重循环就是一回事了，而不是两回事。将二重性隐藏到一个生成器内部，就可以如你所想的那样组织代码了。&lt;/p&gt;
&lt;p&gt;Python 有着强大的抽象化工具，包括生成器和其他能抽象迭代过程的技术。如果你想了解更多，我的演说&lt;a href=&quot;http://nedbatchelder.com/text/iter.html&quot;&gt;更自然地循环&lt;/a&gt;有更多的细节。&lt;/p&gt;
</content:encoded><category>文章</category><category>Python</category></item><item><title>无用面经</title><link>https://lifelonglove.cn/articles/useless-interview/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/useless-interview/</guid><description>回顾三轮并不成功的实习与提前秋招面试，记录准备不足、岗位判断和现场表现中的教训；失败经历同样能帮助后来者校准选择与行动。</description><pubDate>Sun, 31 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;看过太多成功的面经，不妨看看失败的。&lt;/p&gt;
&lt;p&gt;在开始讲述鄙人失败的面经之前，请允许我简单地自我介绍一番：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本人，系浙江省某非浙大本科生，2017年毕业。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此，2016 年上半年，我处于大三下阶段，渐渐开启了我的职业生涯，开始投简历、参加笔/面试。4 月份到现在7 月份，我经历了3 波面试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4 月底，老师与某科技园合作举办了一场小型实习双选会，第一次投简历，参加面试&lt;/li&gt;
&lt;li&gt;5 月底，学院举行大型实习双选会，再次投简历、笔试、面试&lt;/li&gt;
&lt;li&gt;7 月下旬，某 W 公司提前秋招，面试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;归因于第 2 波面试，我目前在某 H 公司实习。虽然有岗位在身，但时间证明，这可能不是一个正确的选择。所以，3 波面试，大致可以算是 3 波失败的经历。写本文的目的就是想给大家一个失败的典范，望诸君引以为戒。&lt;/p&gt;
&lt;h2 id=&quot;兴趣驱动还是能力驱动&quot;&gt;兴趣驱动，还是能力驱动？&lt;/h2&gt;
&lt;p&gt;学期初，某课程 PPT 上看到这样一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Choose a job you love and you will never have to work a day in your life.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;深以为然，然后开始践行这句话，从而开始了第一波失败的面经，囧。&lt;/p&gt;
&lt;p&gt;也是学期初，闲来无事开始写博客，使用了 &lt;a href=&quot;https://github.com/Huxpro/huxpro.github.io&quot;&gt;Hux 的模板&lt;/a&gt;。在修改模板的过程中，莫名其妙对前端产生兴趣，想着如果不能从事 Python 相关的岗位，不如就做前端。然而我的前端知识几乎为零（仅写过几张简单的静态网页，与几个 JavaScript 函数）。&lt;/p&gt;
&lt;p&gt;不巧的是，第一次小型双选会，还真没有 Python 的岗位，我退而求其“次”，投出去的简历，清一色选择了&lt;strong&gt;前端开发&lt;/strong&gt;。其实有很多 C++/Java 开发的岗位，我 C++ 满打满算学了有 2 年，Java 也学了 1 个学期，之前实习也是做 Java 开发，可我在做岗位筛选的时候，第一时间剔除了为数众多的 C++/Java 开发。为什么？不就因为前面那句高逼格的英文吗？&lt;/p&gt;
&lt;p&gt;然后我去参加第一家企业的面试。技术人员不在，我就跟 HR 聊了聊。那是一家初创企业，在整个面试的过程中，我意识到他们需要的是一个有经验的能马上上岗的前端，而不是一个“我可以学”的实习生。面试结束，回去的路上，我开始思考：我怎么就鬼使神差地选择了前端？这样单凭一腔热血而全然不计结果与后果的选择真的正确吗？这真的是所谓的 “choose a job you love” 吗？我很怀疑。&lt;/p&gt;
&lt;p&gt;之后去参加另外一家“企业”的面试，还是前端开发。没有技术人员，“老板”亲自面的试。后来我才发现，这所谓的“企业”就是某科技园内某实验室内的 2 张桌子，2 台计算机，1 张微信公众号二维码，我是公司唯一的员工，“老板”是个大忽悠，我做公司的官网，向他要展示的材料，材料是没有的，项目是子虚乌有的，而他所谓的超前腾讯多少年纯属扯淡放屁。这家“企业”的名字叫“杭州云秒科技有限公司”，任何搜索引擎都搜索不到，因为它根本就不存在。我感觉深受欺骗，做了 1 天，不干了。写这一段不太愉快的经历，就是想告诉大家，&lt;code&gt;择企需谨慎&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;第三家企业没有音信，所以第一波面试至此结束，以相当失败的姿态。事实证明，我莫名其妙地选择前端开发，是极不明智的。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;其实，兴趣驱动的择业没错，但一时兴起能算兴趣吗？现在我不太敢苟同。真正的兴趣，应该是长期的，是可以乐此不疲的，而不是简单的好玩。能力驱动当然也对，至少它能给你一份好的工作。实习生/应届生可能并不真正了解工作是怎样的，也许向往的岗位并不如想象中那么美好。有个词叫：日久生情，你有能力胜任的岗位，做久了也许就喜欢上了，可能不比感兴趣的岗位差。&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;喜欢选择的，还是选择喜欢的？有时候现实让我们不能选择真正喜欢的，不如喜欢已经选择的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;小而精坚还是大而无用&quot;&gt;小而精坚，还是大而无用？&lt;/h2&gt;
&lt;p&gt;经历了“云秒事件”之后，我不太敢找工作了，就静静地等待学院安排的实习双选会的到来，希冀着经学院筛选的公司会靠谱点。&lt;/p&gt;
&lt;p&gt;实习双选会上，我吸取了之前的教训，没有盲目地投前端，但 Python 的岗位依旧几乎没有。有一家公司明确要招 Python 实习生，经慎重考察与考虑，似乎不靠谱，没有投。后来经同学告知，还有另一家 T 公司招 Python 实习生。只能说，当下还是 C/C++/Java/iOS/Android/前端 的市场，去 T 公司应聘 Python 的几乎没有，我就在那里与 HR 聊了好久好久，最后，双方都挺认可对方的。之后我在会场又投了几家并不是招 Python 的企业。&lt;/p&gt;
&lt;p&gt;这一波招聘，比前一波正式点，有笔试的环节。学院通知的 2 场笔试，秉着增加笔试面试经验的心态，我都去了。结果笔试环节都给我侥幸过了，说是侥幸，是因为我都没准备，吃着老本裸考过了。&lt;/p&gt;
&lt;p&gt;在收到任何面试的通知之前，我被前文提到的 T 公司电话面试了。打电话的是 T 公司某部门主管，他说经 HR 反映，觉得我应该是个不错的小孩，就先来跟我电话面试了。&lt;code&gt;看来，招聘现场给 HR 留下好印象，很重要！&lt;/code&gt;电话中，我与主管聊得也很开心，聊了一些对技术理解的话题，也问了一些技术的问题,简单的比如多张表的联合查询，但更多的是价值倾向。说句不知天高地厚的话，我有种千里马终于遇见伯乐的感觉。然后，我就收到 offer 了，幸福来得太突然。这可以算是对我能力的认可吧，为之后的面试增加了不少信心。&lt;/p&gt;
&lt;p&gt;后来陆续收到之前通过笔试的 2 家企业的面试通知。&lt;/p&gt;
&lt;p&gt;第一家是做监控的，面试比较不愉快。原因是，这家企业的岗位完完全全不是我想要的，我只是单纯地想增加面试经验，所以面试的积极性不高，对方问什么答什么，也没怎么补充（&lt;code&gt;个人认为，面试的时候把握主动权，很重要！你要像一个推销员一样把自己推销出去，而不是等着考官来发觉你的优点特点&lt;/code&gt;）。我不知道这算不算技术面，对方只问诸如是怎么学习的，一天学习多久，怎样学习，一天敲多长时间的代码，敲代码花费时间最多在哪的问题。然后他就觉得我做开发的能力不够，问我要不要面测试。你可以说我长得丑，但你不能说我能力差呀。我突然感觉我之前跟他所有的交流都是鸡同鸭讲，后面的面试也索然无味，一面结束就离开了。&lt;code&gt;现在回过头来看，我当时的做法真不对。面试的时候，你可以不开心，但不能闹情绪。如果把面试看作一场表演，在表演结束之前，你都应该保持最佳的状态，好好表演。你看“好声音”，有哪个学员因为没有导师转身而自暴自弃的吗？&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在接到第二家企业 H 公司的面试通知之后，面试之前。我正式收到了 T 公司的 offer，只是没有签协议。我犹豫要不要参加 H 公司的面试。还是秉承着增加面试经验的态度，我去了。H 公司算是大公司吧，实习生只面一次。群面，面试的流程是这样的，首先自我介绍，每个人需要介绍最近的一个项目，考官会针对项目提问，所有人的自我介绍完毕之后，是一些有关团队协作与职业素质的问题。H 公司以前是用 C++ 开发的，后来 Java 分去了半壁江山。而我这几个月都在用 Python 啊，也只能讲 Python 的项目，结果在所有人中，我的自我介绍就显得特别滑稽，心想这下黑了。在后面关于职业素质的问题，是主动回答的，&lt;code&gt;为了弥补自我介绍的不足，我很主动地展现了我的敬业精神&lt;/code&gt;。然后，面试就结束了。&lt;/p&gt;
&lt;p&gt;在漫长的等待 H 公司面试结果的过程中，作为选择困难症患者，我一方面期待失败，这样就能安心地去 T 公司了；另一方面又渴望被肯定。也许是我面试后半段展现的敬业精神打动了考官，我收到了 H 公司的 offer。纠结开始了，H 公司算是大公司，开出的工资也高，但录用通知上只说明了“实习生”，具体的岗位未知；T 公司相对较小，实习工资也堪堪能让我在杭城活下去，但这就够了，而且我在 T 公司的发展方向可以说是明确的，加之 T 公司对我有知遇之恩。这就引出了小标题的问题，是小而精坚，还是大而无用。我自己有选择困难症，做不了决定，就将问题抛给了身边所有能提供帮助的人。&lt;code&gt;事实上，你再怎样巧舌如簧，都不可能真正向旁人描述你的情况。惠子说：子非我，安知我不知鱼之乐。就是这个道理。&lt;/code&gt;我的家人（没一人是计算机专业的）基本认为 T 公司好，而且我之前答应了他们的邀约；身边的同学普遍认为 H 公司好，有人理所当然地认为工资高就对了。没有人能真正说服我，最后我向我导师求助了。经他的分析，H 公司更好，理由如下：&lt;code&gt;大公司，有一个相对完整的平台，一套相对健全的人才培养体系，生态更好，市场更完善。最不济，将来以大公司做跳板，找到好工作的机率更大。小公司如过江之鲫，如果它成功了，选择小公司的你也就跟着发达了。但指不定哪天浪潮一来，它就被淹没了。&lt;/code&gt;听了导师的话，我像是吃了颗定心丸，毅然决然地选择了 H 公司，爽了与 T 公司的约。&lt;/p&gt;
&lt;p&gt;如今我在 H 公司已经实习 3 个星期了。你们不能想象我在做什么。我的导师是做前端的！命运似乎跟我开了个玩笑，千回百转，在我丢了前端的心之后，又转到了前端。但他并没有教我前端，什么都没有。我在 H 公司整天不知道干嘛。这尼玛就是所谓的一人一导师制？真的是应了那句&lt;code&gt;大而无用&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;得不到的，永远在骚动&lt;/code&gt;，我开始假想，如果我当初选择了 T 公司会怎样。也许我现在正愉快地写着 Python 代码，而不是现在累累若丧家之犬；也许我能跟欣赏我的主管把酒言欢(即使我不会喝酒），而不是现在嫌恶我的导师是个猥琐的胖子，两相厌。也许这就是我答应了 T 公司的邀约又爽约的报应。&lt;code&gt;可惜，人生没有如果，只有结果和后果。我现在只能打掉门牙往肚里咽。&lt;/code&gt;也许，我早该申请换一个导师了。&lt;/p&gt;
&lt;p&gt;写 H 公司与 T 公司的经历，就是想告诉大家：&lt;code&gt;当面临多个选择时，比如多个 offer，三思三思再三思。大公司不一定就好，小公司不一定就差。你做不下决定的时候，可以找其他人参谋，但他们的立场不代表你的立场，尽管他们一定会尽力帮你，但他们无法真正站在你的角度思考问题，也不能真正体会你的难处。而真正需要为你的行为买单的，只是你自己而已。一句网络流行语说：遇到这样的好男人，就赶紧嫁了吧。相同的道理，有公司欣赏你，可以考虑就是它了。做出了选择，如果感觉不适，就尽快抽身吧。不要像我，优柔寡断，反被其乱。&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;选择对，还是选择错？选择错，我也选择过。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;能力还是学历&quot;&gt;能力，还是学历？&lt;/h2&gt;
&lt;p&gt;上周末，我去参加了 某 W 公司的提前秋招。&lt;/p&gt;
&lt;p&gt;W 公司是我最向往的国内企业，几乎没有之一。我怀着忐忑的心情参加了面试。其实去面试之前，我已经为自己准备好了安慰的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果没能面试成功，那一定是我加入 W 公司的渴望还不够强烈。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;事实证明，真的不够强烈。那份失望，依旧痛彻心扉。&lt;/p&gt;
&lt;p&gt;由于已经在 H 公司实习，我周四下午收到面试通知，周六下午参加面试，周五没请假，中间几乎没有复习准备的时间。而且，我以为我会面操作系统，结果面的是云计算。这个误会是因为我在 W 公司的线下与线上 2 次报名选择了不同的方向。这真是&lt;em&gt;不作死就不会死&lt;/em&gt;。结果我在仅有的时间里复习的全是操作系统级的知识，全白搭了。&lt;code&gt;所以，准备是必要的。但没头脑的准备，是白费力气的。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;再来说说面试。面试过程中，我&lt;code&gt;充分发挥主观能动性和主动性，努力扬长避短&lt;/code&gt;。但考官不按常理出牌，他看到我简历上“数据结构和算法”能力一般，就问我：*深度优先搜索和广度优先搜索的实现，用栈还是队列，遍历的顺序。*大二上的知识了，我回忆了大概半分钟，给出了答案。然后他又问：&lt;em&gt;TCP/IP 协议族的结构，IP 层的协议有哪些&lt;/em&gt;。前一问，完美回答，后一问，我把路由协议记成了路由转发的算法，只说出了 IP 和 ICMP 协议，不过之后有补充。最后，在我感觉自己算是交出了一份满意的答卷的时候。考官跟我说：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;你旁边参加面试的都是研究生，跟他们比起来，本科生的你，各方面经验还比较欠缺。你先回去，跟你的导师多做项目多学习。我们后续还有许多招聘。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;当时我很难过，面试结束，走在西子湖畔，茫茫然不知所往。&lt;/p&gt;
&lt;p&gt;W 公司口口声声，不止一次地说，看能力不看学历，结果还是看学历不看能力。&lt;code&gt;关于能力，比赛获奖，也许是很好的证明。但不能证明不参加比赛不获奖的就没能力，不是吗？我一直觉得，这涉及一个证实与证伪的问题。我见过太多水比赛的，也见过太多靠关系获奖的，我不愿如此，所以我几乎不参加比赛,只愿呆在自己的一方天地里。但是，我建议各位多参加比赛。比赛获奖，其实是一条终南捷径。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在我考虑是不是要考研读研，以加入 W 公司的时候，室友的看法是：&lt;code&gt;没必要，3 年时间，你在公司里也可以学到很多，一样是成长。&lt;/code&gt;他的话，同意一半，我觉得：&lt;code&gt;虽说殊途同归，读研的最终目的还是工作，但读研和工作，是 2 条路，路上的风景不一样，读研与不读研的人，眼光和看待问题的方式会有不同。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这两天，我还在思考：&lt;code&gt;既然 W 公司没有选择还未成长起来的我。他日，不论是读研还是工作，等我羽翼渐丰，我真的还应该选择 W 公司吗？&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我没有答案。也许，只要有机会加入 W 公司，我都会毫不犹豫，撞破南墙也不回头，因为加入 W 公司，算是我成长的动力；也许，人老心凉，我会选择懂得欣赏我的那一家，因为士为知己者死。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;以上便是我 3 波从开始到结束都相当失败的面试经历了，句句肺腑。&lt;/p&gt;
&lt;p&gt;如果你赞同，姑且听之信之；不赞同，权且当小说看吧。&lt;/p&gt;
</content:encoded><category>文章</category><category>杂</category><category>面经</category></item><item><title>[译]Python之装饰器知必会</title><link>https://lifelonglove.cn/articles/translation-about-python-decorator/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/translation-about-python-decorator/</guid><description>从函数是一等对象和闭包讲起，逐步解释装饰器语法、带参数装饰器、类装饰器与多个装饰器的执行顺序，澄清看似魔法的包装机制。</description><pubDate>Sat, 16 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文作者: e-satis(Kevin Samuel)\
原文连接: &lt;a href=&quot;http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python/1594484#1594484&quot;&gt;stackoverflow - How can I make a chain of function decorators in Python&lt;/a&gt;\
译者: kissg(赵喧典)\
本译文已征得原作者同意, 转载请同时注明原文. 如果能注明译文出处就更好啦~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;装饰器基础&quot;&gt;装饰器基础&lt;/h2&gt;
&lt;h3 id=&quot;python的函数是对象&quot;&gt;Python的函数是对象&lt;/h3&gt;
&lt;p&gt;为了理解装饰器, 你必须先理解: 在Python的世界, 函数是对象. 这一点很重要. 让我们通过一个简单的例子来看看为什么:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; shout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(word&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word.capitalize()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shout()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出 : &apos;Yes!&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 作为一个对象, 你可以像任何其他对象一样, 将函数赋值给一个变量&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scream &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 注意, 我们并没有使用括号:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 我们没有调用函数, 我们是将&quot;shout&quot;函数装入到&quot;scream&quot;变量中&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 这意味着你可以像从&quot;scream&quot;调用&quot;shout&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; scream()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出 : &apos;Yes!&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 不仅如此, 它还意味着你可以删除旧的名字&quot;shout&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 而仍然可以通过&quot;scream&quot;访问函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;del&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shout()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NameError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 输出: &quot;name &apos;shout&apos; is not defined&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; scream()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出: &apos;Yes!&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay! 在脑海中保留这个概念. 我们之后会用到它.&lt;/p&gt;
&lt;p&gt;Python函数的另一个有趣的属性是: 它们可以在另一个函数的内部被定义…&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; talk&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 你可以在运行时, 在&quot;talk&quot;函数内部定义一个函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; whisper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(word&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word.lower()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # ... 然后立马使用它&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; whisper()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 当调用&quot;talk&quot;时, 你的每一次调用都会定义&quot;whisper&quot;一次,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 再调用&quot;whisper&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;talk()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &quot;yes...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 但是, &quot;whisper&quot;在&quot;talk&quot;函数之外实际是不存在的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; whisper()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NameError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, e:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        #输出 : &quot;name &apos;whisper&apos; is not defined&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        #Python的函数是对象&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;函数引用&quot;&gt;函数引用&lt;/h3&gt;
&lt;p&gt;还跟得上吗? 现在开始讲有趣的部分…&lt;/p&gt;
&lt;p&gt;你已经见识过了函数是对象. 因此,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;函数可以赋值给一个变量&lt;/li&gt;
&lt;li&gt;函数可以在另一个函数中被定义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着&lt;strong&gt;函数可以返回(&lt;code&gt;return&lt;/code&gt;)另一个函数&lt;/strong&gt;. 看仔细了!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getTalk&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(kind&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;shout&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 我们在运行时定义函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; shout&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(word&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word.capitalize()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; whisper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(word&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) :&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word.lower()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 然后我们返回其中的一个&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kind &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;shout&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 不适用括号, 我们并不是在调用函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 我们是在返回一个函数对象&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; shout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; whisper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 你要如何使用这洪荒巨兽呢?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 取得函数, 并将其赋给一个变量&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;talk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getTalk()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 此处, 你可以看见&quot;talk&quot;是一个函数对象&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; talk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出 : &amp;lt;function shout at 0xb7ea817c&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 以下对象是函数&quot;talk&quot;的一项返回&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; talk()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出 : Yes!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 你甚至可以直接使用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# And you can even use it directly if you feel wild:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; getTalk(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;whisper&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出 : yes...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等等…不止如此!&lt;/p&gt;
&lt;p&gt;如果你要&lt;code&gt;return&lt;/code&gt;一个函数, 可以传入一个函数作为参数:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; doSomethingBefore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I do something before then I call the function you gave me&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;doSomethingBefore(scream)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I do something before then I call the function you gave me&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Yes!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在, 你已经具备了理解装饰器所需的一切知识. 如你所见, 装饰器就是“包装纸(wrapper)”, 也就是说, &lt;strong&gt;装饰器允许你在被装饰的函数前后执行代码&lt;/strong&gt;, 而不对函数本身做任何修改.&lt;/p&gt;
&lt;h3 id=&quot;动手写装饰器&quot;&gt;动手写装饰器&lt;/h3&gt;
&lt;p&gt;你最好动手写一写(装饰器):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 装饰器是函数, 它以另一个函数作为参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; my_shiny_new_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a_function_to_decorate):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 在装饰器的内部, 定义一个&quot;wrapper&quot;函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # wrapper函数用于包装原函数, 以在原函数前后执行代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; the_wrapper_around_the_original_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 在此处输入你想在调用原函数之前执行的代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Before the function runs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 调用被装饰的函数(是函数调用, 需要括号)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        a_function_to_decorate()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 在此处输入你想在调用原函数之后执行的代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;After the function runs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 此处, 被装饰的函数并没有被执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 返回刚刚创建的包装函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 包装函数包括被装饰的函数, 在函数前后执行的代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; the_wrapper_around_the_original_function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 现在, 假设你创建了一个可能用不到的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; a_stand_alone_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am a stand alone function, don&apos;t you dare modify me&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a_stand_alone_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: I am a stand alone function, don&apos;t you dare modify me&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 然而, 你可以装饰它, 扩展它&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 只需要将它传递给一个装饰器, 装饰器将动态地将函数包装进任何你想要的代码中&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 并返回一个可用的新函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a_stand_alone_function_decorated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_shiny_new_decorator(a_stand_alone_function)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a_stand_alone_function_decorated()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Before the function runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am a stand alone function, don&apos;t you dare modify me&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#After the function runs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在, 你可能想要在每次调用&lt;code&gt;a_stand_alone_function&lt;/code&gt;时, 替代地调用&lt;code&gt;a_stand_alone_function_decorated&lt;/code&gt;. 很简单, 用装饰器&lt;code&gt;my_shiny_new_decorator&lt;/code&gt;返回的函数覆盖原函数&lt;code&gt;a_stand_alone_function&lt;/code&gt;即可.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a_stand_alone_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_shiny_new_decorator(a_stand_alone_function)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;a_stand_alone_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Before the function runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am a stand alone function, don&apos;t you dare modify me&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#After the function runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 这就是装饰器的作用了&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;装饰器揭秘&quot;&gt;装饰器揭秘&lt;/h3&gt;
&lt;p&gt;用装饰器语法重现先前的例子, 是这样的:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@my_shiny_new_decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; another_stand_alone_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    print &quot;Leave me alone&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;another_stand_alone_function(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#outputs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Before the function runs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Leave me alone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#After the function runs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是的, 就是这样, 这么简单. &lt;code&gt;@decorator&lt;/code&gt;是以下形式的缩写:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;another_stand_alone_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_shiny_new_decorator(another_stand_alone_function)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处所讲的装饰器是&lt;a href=&quot;https://en.wikipedia.org/wiki/Decorator_pattern&quot;&gt;装饰器设计模式&lt;/a&gt;的python变种. 在python中有一些经典的设计模式, 可使开发更加容易(比如迭代器(iterator)).&lt;/p&gt;
&lt;p&gt;当然, 你可以叠加装饰器:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bread&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;/&apos;&apos;&apos;&apos;&apos;&apos;\&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;\______/&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ingredients&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;#tomatoes#&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;~salad~&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sandwich&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(food&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;--ham--&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; food&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sandwich()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: --ham--&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sandwich &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bread(ingredients(sandwich))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sandwich()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;/&apos;&apos;&apos;&apos;&apos;&apos;\&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# #tomatoes#&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# --ham--&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ~salad~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;\______/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;python装饰器的语法:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@bread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@ingredients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sandwich&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(food&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;--ham--&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; food&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sandwich()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;/&apos;&apos;&apos;&apos;&apos;&apos;\&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# #tomatoes#&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# --ham--&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ~salad~&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;\______/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你设置装饰器的顺序很重要:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@ingredients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@bread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; strange_sandwich&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(food&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;--ham--&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; food&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;strange_sandwich()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;##tomatoes#&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;/&apos;&apos;&apos;&apos;&apos;&apos;\&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# --ham--&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&amp;lt;\______/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ~salad~&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;现在-针对你的问题在python中-如何实现串级装饰器&quot;&gt;现在: 针对你的问题(在python中, 如何实现串级装饰器)…&lt;/h3&gt;
&lt;p&gt;(译者注: 本文是stackoverflow上一个回答)&lt;/p&gt;
&lt;p&gt;结论是, 如你所见, 很容易就可以实现串级装饰器:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 加粗装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; makebold&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fn):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 装饰器返回的新的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 在前后插入一些代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;b&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fn() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;/b&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 斜体装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; makeitalic&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(fn):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 装饰器返回的新的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 在前后插入一些代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;i&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fn() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&amp;lt;/i&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@makebold&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@makeitalic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; say&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hello&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; say()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: &amp;lt;b&amp;gt;&amp;lt;i&amp;gt;hello&amp;lt;/i&amp;gt;&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 等价于&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; say&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;hello&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;say &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; makebold(makeitalic(say))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; say()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: &amp;lt;b&amp;gt;&amp;lt;i&amp;gt;hello&amp;lt;/i&amp;gt;&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在, 你可以愉快地离开了(译者注: 这是作者对提问者说的). 或者接着烧脑, 看看装饰器的一些高级用法.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;装饰器进阶&quot;&gt;装饰器进阶&lt;/h2&gt;
&lt;h3 id=&quot;向被装饰函数传递参数&quot;&gt;向被装饰函数传递参数&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 这并不是黑魔法, 你只需要在包装过程中传入参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; a_decorator_passing_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_to_decorate):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; a_wrapper_accepting_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(arg1, arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I got args! Look:&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, arg1, arg2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        function_to_decorate(arg1, arg2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a_wrapper_accepting_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 当你调用装饰器返回的函数时, 你调用了包装纸(wrapper)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 向包装纸传递的参数将可继续传递给被装饰函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@a_decorator_passing_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; print_full_name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(first_name, last_name):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;My name is&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, first_name, last_name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;print_full_name(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Peter&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Venkman&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I got args! Look: Peter Venkman&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#My name is Peter Venkman&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;装饰方法&quot;&gt;装饰方法&lt;/h3&gt;
&lt;p&gt;python好的一点是, 方法(method)和函数(function)实际是一样的. 唯一的区别在于, 方法的第一个参数需要是当前对象(&lt;code&gt;self&lt;/code&gt;)的引用.&lt;/p&gt;
&lt;p&gt;这意味着, 你可以用同样的方式创建方法的装饰器! 不过, 别忘了&lt;code&gt;self&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; method_friendly_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(method_to_decorate):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, lie):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        lie &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lie &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # 真好, 年龄更小了 :-)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; method_to_decorate(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, lie)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Lucy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __init__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.age &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 32&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    @method_friendly_decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sayYourAge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, lie):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, what did you think?&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.age &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lie)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;l &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Lucy()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;l.sayYourAge(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: I am 26, what did you think?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果要写一个通用的装饰器–可用于任何函数或方法, 而不必考虑其参数–那么, 用&lt;code&gt;*args, **kwargs&lt;/code&gt;就好了:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; a_decorator_passing_arbitrary_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_to_decorate):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 包装纸接收任意参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; a_wrapper_accepting_arbitrary_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Do I have args?:&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; args&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; kwargs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 将得到的参数解压(unpack), 此处为*args, **kwargs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 如果对解压不熟悉, 可参考&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        function_to_decorate(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a_wrapper_accepting_arbitrary_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@a_decorator_passing_arbitrary_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; function_with_no_argument&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Python is cool, no argument here.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;function_with_no_argument()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Do I have args?:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#{}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#python就是这么酷, 此地无参.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@a_decorator_passing_arbitrary_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; function_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, b, c):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a, b, c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;function_with_arguments(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Do I have args?:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#(1, 2, 3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#{}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#1 2 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@a_decorator_passing_arbitrary_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; function_with_named_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, b, c, platypus&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Why not ?&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Do &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; like platypus? &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    (a, b, c, platypus)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;function_with_named_arguments(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bill&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Linus&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Steve&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;platypus&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Indeed!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Do I have args ? :&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#(&apos;Bill&apos;, &apos;Linus&apos;, &apos;Steve&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#{&apos;platypus&apos;: &apos;Indeed!&apos;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Bill, Linus and Steve喜欢鸭嘴兽吗? 千真万确!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Mary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __init__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.age &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 31&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    @a_decorator_passing_arbitrary_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sayYourAge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, lie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 现在你可以添加一个默认值&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;%s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;, what did you think ?&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.age &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; lie)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;m &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Mary()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;m.sayYourAge()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Do I have args?:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#(&amp;lt;__main__.Mary object at 0xb7d303ac&amp;gt;,)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#{}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am 28, what did you think?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;向装饰器传递参数&quot;&gt;向装饰器传递参数&lt;/h3&gt;
&lt;p&gt;好吧, 现在你觉得如何向装饰器本身传递参数呢?&lt;/p&gt;
&lt;p&gt;这有点绕, 因为装饰器必须接收一个函数作为参数. 因此, 你不能向装饰器直接传递被装饰的函数的参数.&lt;/p&gt;
&lt;p&gt;在讲解解决方法之前, 让我们先做一个小小的回顾:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 装饰器是 普通 函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; my_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am an ordinary function&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am function returned by the decorator&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 所以, 你可以不使用&quot;@&quot;调用它&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; lazy_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;zzzzzzzz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_decorator(lazy_function)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: I am an ordinary function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 它将会输出 &quot;I am an ordinary function&quot;, 因为这就是你所做的:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 调用一个函. 没有一点魔法在里面&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@my_decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; lazy_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;zzzzzzzz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: I am an ordinary function&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述两者完全相同, 都是调用了&lt;code&gt;my_decorator&lt;/code&gt;. 所以当你&lt;code&gt;@my_decorator&lt;/code&gt;, 你是在告诉Python去调用被变量&lt;code&gt;my_decorator&lt;/code&gt;标记的函数.&lt;/p&gt;
&lt;p&gt;这一点很重要! 你可以用标签直接指明装饰器, 或者不这样.&lt;/p&gt;
&lt;p&gt;让我们再深入一点.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorator_maker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I make decorators! I am executed only once: &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;          &quot;when you make me create a decorator.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; my_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am a decorator! I am executed only when you decorate a function.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapped&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;I am the wrapper around the decorated function. &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;I am called when you call the decorated function. &quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;As the wrapper, I return the RESULT of the decorated function.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;As the decorator, I return the wrapped function.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;As a decorator maker, I return a decorator&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 让我们创建一个装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;new_decorator &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decorator_maker()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I make decorators! I am executed only once: when you make me create a decorator.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As a decorator maker, I return a decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 接下来, 我们来装饰函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am the decorated function.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; new_decorator(decorated_function)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am a decorator! I am executed only when you decorate a function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the decorator, I return the wrapped function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 调用函数:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the wrapper around the decorated function. I am called when you call the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the wrapper, I return the RESULT of the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorated function.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里没有什么可惊奇的.&lt;/p&gt;
&lt;p&gt;跳过所有繁琐的中间变量, 以下与上述方法完全一样.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am the decorated function.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decorator_maker()(decorated_function)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I make decorators! I am executed only once: when you make me create a decorator.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As a decorator maker, I return a decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am a decorator! I am executed only when you decorate a function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the decorator, I return the wrapped function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 最后:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the wrapper around the decorated function. I am called when you call the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the wrapper, I return the RESULT of the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorated function.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以更精简一些:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@decorator_maker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am the decorated function.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#outputs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I make decorators! I am executed only once: when you make me create a decorator.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As a decorator maker, I return a decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am a decorator! I am executed only when you decorate a function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the decorator, I return the wrapped function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Eventually:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#outputs:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the wrapper around the decorated function. I am called when you call the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#As the wrapper, I return the RESULT of the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorated function.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;嘿, 你看到了吗? 我们通过“&lt;code&gt;@&lt;/code&gt;“语法使用了函数.&lt;/p&gt;
&lt;p&gt;所以, 回到带参的装饰器. 如果我们可以用函数在运行时生成装饰器, 我们就能够向那个函数传递参数了, 对吧?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorator_maker_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(decorator_arg1, decorator_arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I make decorators! And I accept arguments:&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, decorator_arg1, decorator_arg2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; my_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 此处传递参数的能力, 得益于闭包的性质&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 如果你不习惯闭包, 你可以假设它是ok的, 或者看一下这篇文章:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am the decorator. Somehow you passed me arguments:&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, decorator_arg1, decorator_arg2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 不要混淆了装饰器参数和函数参数!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapped&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_arg1, function_arg2) :&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;I am the wrapper around the decorated function.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;I can access all the variables&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\t&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;- from the decorator: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{0}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; {1}\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\t&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;- from the function call: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{2}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; {3}\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                  &quot;Then I can pass them to the decorated function&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                  .format(decorator_arg1, decorator_arg2,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                          function_arg1, function_arg2))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func(function_arg1, function_arg2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; my_decorator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@decorator_maker_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Leonard&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Sheldon&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_arg1, function_arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;I am the decorated function and only knows about my arguments: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{0}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;           &quot; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{1}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(function_arg1, function_arg2))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function_with_arguments(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Rajesh&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Howard&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I make decorators! And I accept arguments: Leonard Sheldon&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorator. Somehow you passed me arguments: Leonard Sheldon&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the wrapper around the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I can access all the variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   - from the decorator: Leonard Sheldon&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   - from the function call: Rajesh Howard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Then I can pass them to the decorated function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorated function and only knows about my arguments: Rajesh Howard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是带参的装饰器了.参数也可以设置成变量:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Penny&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;c2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Leslie&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@decorator_maker_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Leonard&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, c1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function_with_arguments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_arg1, function_arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;I am the decorated function and only knows about my arguments:&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;           &quot; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{0}&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; {1}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(function_arg1, function_arg2))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function_with_arguments(c2, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Howard&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I make decorators! And I accept arguments: Leonard Penny&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorator. Somehow you passed me arguments: Leonard Penny&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the wrapper around the decorated function.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I can access all the variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   - from the decorator: Leonard Penny&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   - from the function call: Leslie Howard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Then I can pass them to the decorated function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#I am the decorated function and only knows about my arguments: Leslie Howard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如你所见, 使用该技巧, 你可以像任何(普通)函数一样向装饰器传递参数. 如果你愿意, 你甚至可以使用&lt;code&gt;*args, **kwargs&lt;/code&gt;. 但是, 请记住装饰器只能被调用&lt;strong&gt;1次&lt;/strong&gt;, 即在导入脚本的时候. 之后你就不能动态地设置参数了. 当你“import x”时, &lt;strong&gt;函数已经被装饰了&lt;/strong&gt;, 你不能再做任何修改.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;练习-装饰装饰器&quot;&gt;练习: 装饰装饰器&lt;/h3&gt;
&lt;p&gt;作为福利, 我将给你一个能接收任意参数的装饰器代码片段. 然后, 我们用另一个函数来创建我们的装饰器, 以接收参数.&lt;/p&gt;
&lt;p&gt;我们对装饰器进行了包装.&lt;/p&gt;
&lt;p&gt;如前所见, 我们是用什么包装了函数?&lt;/p&gt;
&lt;p&gt;是的, 就是装饰器!&lt;/p&gt;
&lt;p&gt;让我们愉快地写一个装饰器的装饰器吧:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorator_with_args&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(decorator_to_enhance):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    这个函数被用作一个装饰器.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    它必须装饰另一个函数, 那个函数也是一个装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    它允许任何装饰器接收任意数量的参数, 这样就不用每次写(装饰器)的时候都回忆一遍.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 使用了与上述一样的技巧传递参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorator_maker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 我们在运行时创建了一个装饰器, 它接收一个函数作为参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 但它可以持有maker传来的参数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorator_wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 我们返回原始装饰器的结果, 一个普通函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # 唯一的异错的地方在于: 装饰器必须这样才能正常工作&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decorator_to_enhance(func, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decorator_wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; decorator_maker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它可以像下述一样使用:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 你可以创建一个用作装饰器的函数. 再用一个装饰器来装饰它&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 不要忘了, 用法是&quot;decorator(func, *args, **kwargs)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@decorator_with_args&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_arg1, function_arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Decorated with&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, args, kwargs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func(function_arg1, function_arg2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 现在, 你可以用刚刚创建的被装饰的装饰器来装饰函数了&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@decorated_decorator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;404&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1024&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decorated_function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(function_arg1, function_arg2):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Hello&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, function_arg1, function_arg2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;decorated_function(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Universe and&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;everything&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Decorated with (42, 404, 1024) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Hello Universe and everything&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 哇!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我知道, 你最近一次有这种感觉, 是听某人说了: “要理解递归, 你必须先理解递归”. 现在, 对于学会了(装饰器), 你不感觉很棒码?&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;最佳实践-装饰器&quot;&gt;最佳实践: 装饰器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;装饰器最早出现于Python2.4, 所以请确保你的代码能在2.4之后的版本上运行&lt;/li&gt;
&lt;li&gt;装饰器减慢了函数调用的速度. 谨记.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;你无法对函数去装饰&lt;/strong&gt;. (确实有技巧可以创建可移除的装饰器, 但没有人会使用它们.) 因此, 一旦函数被装饰了, 在整个代码中, 它都是被装饰的.&lt;/li&gt;
&lt;li&gt;装饰器包装了函数, 这使得调试变得困难. (这一点在Pyhton2.5之后的版本得到缓解; 请看下面的内容)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python2.5引入了&lt;code&gt;functools&lt;/code&gt;模块, 该模块包含的&lt;code&gt;funtools.wraps()&lt;/code&gt;函数, 可以将被装饰函数名称, 模块, 文档字符串(docstring)拷贝到它的包装纸中.&lt;/p&gt;
&lt;p&gt;(有趣的事实是: &lt;code&gt;funtools.wraps()&lt;/code&gt;还是一个装饰器!)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 为了调试方便, 栈回溯(stacktrace)打印函数的__name__属性&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;foo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; foo.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 当使用装饰器时, 就显得混乱了&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;bar&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@bar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;foo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; foo.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &quot;functools&quot;`模块能解决这个问题&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; functools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 我们说&quot;wrapper&quot;包装了&quot;func&quot;, 然后魔法就发生了&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    @functools.wraps&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;bar&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@bar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;foo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; foo.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出: foo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;如何使用装饰器&quot;&gt;如何使用装饰器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;一个大问题&lt;/strong&gt;: 我能用装饰器做什么?&lt;/p&gt;
&lt;p&gt;(装饰器)看上去很酷很强大, 但是用一个实际例子来说明可能会更好. 装饰器的用法很多. 经典的用法是, 拓展一个外部库函数(你无法修改它)的功能, 或者用于调试(因为它是临时的, 你并不想进行修改).&lt;/p&gt;
&lt;p&gt;你可以用装饰器不重复地拓展多个函数, 像这样:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; benchmark&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    打印函数被执行的时间的装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        t &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time.clock()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, time.clock()&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; logging&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    记录脚本活动的装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    (事实上, 它只是进行了打印, 但可以进行日志打印)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, args, kwargs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; counter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(func):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    统计并打印函数被执行次数的装饰器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; wrapper&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        wrapper.count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper.count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        res &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; func(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kwargs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{0}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; has been used: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{1}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;x&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(func.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__name__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, wrapper.count)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; res&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    wrapper.count &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wrapper&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@counter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@benchmark&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; reverse_string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(string):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;reversed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(string))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reverse_string(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Able was I ere I saw Elba&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; reverse_string(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#reverse_string (&apos;Able was I ere I saw Elba&apos;,) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper has been used: 1x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#ablE was I ere I saw elbA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#reverse_string (&apos;A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!&apos;,) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper has been used: 2x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然, 装饰器的好处是, 对于几乎所有东西, 在不重写的情况, 马上可用. 我说过, 不重复(D.R.Y, Don’t Repeat Yourself):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@counter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@benchmark&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;@logging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; get_random_futurama_quote&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://subfusion.net/cgi-bin/quote.pl?quote=futurama&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).read()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result.split(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;lt;br&amp;gt;&amp;lt;b&amp;gt;&amp;lt;hr&amp;gt;&amp;lt;br&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].split(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;hr&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; value.strip()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    except&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;No, I&apos;m ... doesn&apos;t!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; get_random_futurama_quote()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; get_random_futurama_quote()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#输出:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#get_random_futurama_quote () {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper 0.02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper has been used: 1x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#The laws of science be a harsh mistress.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#get_random_futurama_quote () {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper 0.01&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#wrapper has been used: 2x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Curse you, merciful Poseidon!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python本身提供了多个装饰器: &lt;code&gt;property&lt;/code&gt;, &lt;code&gt;staticmethod&lt;/code&gt;等等.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Django用装饰器管理缓存(caching)和视图权限(view permissions).&lt;/li&gt;
&lt;li&gt;内联异步函数调用的变向实现.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;装饰器确实大有用途.&lt;/p&gt;
</content:encoded><category>文章</category><category>Python</category><category>decorator</category></item><item><title>[笔记]C程序设计语言</title><link>https://lifelonglove.cn/notes/notes-on-the-c-programming-language/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/notes-on-the-c-programming-language/</guid><description>按章节记录 C 的函数、变量、指针、数组、结构体、输入输出与预处理规则。</description><pubDate>Sun, 10 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;失踪了一个多月, 我赵喧典kissg又回来啦!\
想参加下学期的秋招, 补了C语言的知识. 以下是著名的“C程序设计语言”的一些记录, 字字珠玑, 恨不得把整本书都给敲一遍.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;chapter-1-导言&quot;&gt;Chapter 1 导言&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个c语言程序, 无论大小, 都是由&lt;code&gt;函数&lt;/code&gt;和&lt;code&gt;变量&lt;/code&gt;组成的. 函数中含有一些&lt;code&gt;语句&lt;/code&gt;, 以指定要执行的计算操作; 变量则用于存储计算过程中使用的值.&lt;/li&gt;
&lt;li&gt;每个程序都从&lt;code&gt;main&lt;/code&gt;函数开始, 这意味着每个程序都必须在某个位置包含一个main函数&lt;/li&gt;
&lt;li&gt;函数之间进行数据交换的一种方式是调用函数向被调用函数提供一个值(成为&lt;code&gt;参数&lt;/code&gt;)列表, 函数名后的一对圆括号()将参数列表括起来. 函数的语句用一对花括号{}括起来.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/* ... */&lt;/code&gt; - (多行)注释, 程序中允许出现空格, 制表符, 换行符的地方, 都允许使用注释&lt;/li&gt;
&lt;li&gt;在c语言中, 所有变量都必须先声明后使用&lt;/li&gt;
&lt;li&gt;缩进, 是为了凸显程序的逻辑结构; 每行只写一条语句, 并在运算符两边各加一个空格字符, 使运算的结合关系更清楚明了&lt;/li&gt;
&lt;li&gt;c语言的整数除法操作将进行&lt;em&gt;&lt;strong&gt;舍位&lt;/strong&gt;&lt;/em&gt;\
(python中, &lt;code&gt;//&lt;/code&gt;才是整除)&lt;/li&gt;
&lt;li&gt;格式化字符串, &lt;code&gt;%&lt;/code&gt;用于占位, %之后是描述, 表示之后会用何种类型的数据补位, 还可以添加具体的格式, 比如数字宽. 因此叫&lt;code&gt;格式化字符串&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;c语言本身并没有定义IO功能, &lt;code&gt;printf&lt;/code&gt;是标准库函数中的一个函数&lt;/li&gt;
&lt;li&gt;如果某个算术运算符的所有操作数均为整型, 则执行整型运算; 如果至少有一个操作数是浮点数, 则运算开始之前, 整型数将被转换为浮点数&lt;/li&gt;
&lt;li&gt;即使浮点常量可以用整数的形式表示, 在书写时最好为其显式地加上小数点&lt;/li&gt;
&lt;li&gt;举例说明: &lt;code&gt;%6.1f&lt;/code&gt; - 表示待打印的浮点数至少占6个字符宽, 且小数点后有1位数字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%d&lt;/code&gt; - d是&lt;strong&gt;decimal&lt;/strong&gt;的缩写, 表示十进制整型数; &lt;code&gt;%ld&lt;/code&gt; - 表示long; &lt;code&gt;%o&lt;/code&gt; - 表示八进制数; &lt;code&gt;%x&lt;/code&gt; - 表示十六进制数; &lt;code&gt;%c&lt;/code&gt; - 表示字符; &lt;code&gt;%s&lt;/code&gt; - 表示字符串; &lt;code&gt;%%&lt;/code&gt; - 百分号本身&lt;/li&gt;
&lt;li&gt;在允许使用某种类型变量值的任何场合, 都可以使用该类型的更复杂的表达式&lt;/li&gt;
&lt;li&gt;for语句比较适合初始化和增长步长都是单条语句并且逻辑相关的情形, 它将循环控制语句集中放在一起, 比while更紧凑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#define 符号常量 替换文本&lt;/code&gt; - 将符号常量定义为一个特定的字符串, 之后所有程序中出现的符号常量都用替换文本替换. &lt;strong&gt;指明行末没有分号&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;无论文本从何处输入, 输出到何处, 其输入/输出都是按字符流的方式处理. &lt;code&gt;文本流&lt;/code&gt;是由多行字符构成的字符序列, 而每行字符则由0个或多个字符组成, 行末是一个换行符&lt;/li&gt;
&lt;li&gt;字符在键盘, 屏幕或其他任何地方无论以什么形式表现, 在机器内部都以&lt;code&gt;位模式&lt;/code&gt;存储&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EOF&lt;/code&gt;定义在&amp;lt;stdio.h&amp;gt;中, 是一个整型数(-1)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;赋值操作是一个表达式, 并且具有一个值, 即赋值后左边变量保存的值. 因此, 赋值可以作为更大的表达式的一部分出现&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;c语言的语法规则要求for语句必须有一个循环体, 可用单独的分号表示&lt;code&gt;空语句&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用单引号表示字符, 可以表示一个整型值, 如&lt;code&gt;&apos;A&apos;&lt;/code&gt;表示65; 用双引号表示字符串, &lt;code&gt;&quot;A&quot;&lt;/code&gt;是一个仅包含一个字符的字符串变量&lt;/li&gt;
&lt;li&gt;通常将函数定义中圆括号内列表中出现的变量成为&lt;code&gt;形式参数&lt;/code&gt;, 而将函数调用中与形式参数对应的值称为&lt;code&gt;实际参数&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;函数调用, 将&lt;strong&gt;控制权&lt;/strong&gt;交给被调用的函数; 返回时, 将控制权返回给调用者, 返回值或不带返回值&lt;/li&gt;
&lt;li&gt;main函数本身也是函数, 也可以向其调用者返回一个值, 该调用者实际上就是&lt;strong&gt;程序的执行环境&lt;/strong&gt;. 返回值为0, 表示程序正常终止, 返回值非0, 表示出现异常情况或出错结束条件.&lt;/li&gt;
&lt;li&gt;在c语言中, 所有函数参数都“通过值”传递, 也就是说, 传递给被调用函数的参数值存放在临时变量中, 而不是存放在原来的变量中. 这意味着, 在c语言中, 被调用函数不能直接修改主调函数中变量的值, 而只能修改其私有的临时副本的值&lt;/li&gt;
&lt;li&gt;传递值的一个优点是: 在被调用函数中, 参数可以看作便于初始化的局部变量(在函数内部可任意修改, 而不影响原来的变量), 从而使得额外使用的变量更少, 使程序更紧凑简洁.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void&lt;/code&gt;关键字, 显式地说明函数不返回任何值&lt;/li&gt;
&lt;li&gt;c语言中, 形如“hello\n”的字符串常量, 以字符数组的形式存储时, 数组的各元素分别存储字符串的各个字符, 并以’\0’标志字符串的结束&lt;/li&gt;
&lt;li&gt;初始化变量时, 不为其赋值, 其中存放的是无效值&lt;/li&gt;
&lt;li&gt;函数中的每个局部变量只在函数被调用时存在, 在函数执行完毕退出时销毁.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;外部变量&lt;/code&gt;可以在全局范围内访问. 函数间可通过外部变量交换数据, 而不必使用参数表. 外部变量在程序执行期间一直存在.&lt;/li&gt;
&lt;li&gt;外部变量必须定义在所有函数之外, 且只能定义一次, 定义后编译程序时将为它分配存储单元. 在每个需要访问外部变量的函数中, 必须声明相应的外部变量, 声明时用&lt;code&gt;extern&lt;/code&gt;显式声明, 也可以通过上下文隐式声明.&lt;/li&gt;
&lt;li&gt;通常的做法是, 所有外部变量的定义都放在源文件的开始处, 这样可以省略extern声明. 如果程序包含多个源文件, 需要使用别的源文件中的外部变量时, 要用extern声明来建立该变量与其定义之间的联系&lt;/li&gt;
&lt;li&gt;通常将变量和函数的extern声明放在一个单独的文件中, 这个文件就称为&lt;code&gt;头文件&lt;/code&gt;, 并在每个源文件的开头使用#include语句将所要用的文件包含进来&lt;/li&gt;
&lt;li&gt;在ANSI C中, 如果要声明空参数列表, 必须使用关键字void进行显式声明. 空圆括号对是旧版c语言的做法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;表示创建变量或分配存储单元, 而&lt;strong&gt;声明&lt;/strong&gt;指的是说明变量的性质, 但不分配存储单元&lt;/li&gt;
&lt;li&gt;过分依赖外部变量会导致一定的风险, 因为它会使程序中的数据关系模糊&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-2-类型-运算符与表达式&quot;&gt;Chapter 2 类型, 运算符与表达式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在传统c语言用法中, 变量名使用小写字母, 符号常量名全部使用大写字母&lt;/li&gt;
&lt;li&gt;&lt;code&gt;不理解的一段话: &lt;/code&gt;对于内部名而言, 至少前面31个字符是有效的. 函数名与外部变量名包含的字符数目可能小于31, 因为汇编程序和加载程序可能会使用这些外部名, 而语言本身无法控制加载和汇编程序. 对于外部名, ANSI标准仅保证前6个字符的唯一性, 且不区分大小写.&lt;/li&gt;
&lt;li&gt;选择的变量名要能够尽量从字面上表达变量的用途. 局部变量一般使用较短的变量名(尤其是循环控制变量), 外部变量使用较长的名字&lt;/li&gt;
&lt;li&gt;c语言仅提供了下列几种基本数据类型:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;char 字符型, 占用一个字节(8位), 可以存放本地字符集的一个字符&lt;/li&gt;
&lt;li&gt;int 整型, 通常反映了所用机器中整数的最自然长度&lt;/li&gt;
&lt;li&gt;float&lt;/li&gt;
&lt;li&gt;double&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;类型限定符&lt;code&gt;signed&lt;/code&gt;与&lt;code&gt;unsigned&lt;/code&gt;用于限定char类型或任何整型. unsigned类型的数总是非负值, 并遵守算术模2^n定律. 举例说明, char对象占8位, 那么unsigned char类型变量的取值范围是0&lt;del&gt;255, 而signed char类型变量的取值范围是-128&lt;/del&gt;127&lt;/li&gt;
&lt;li&gt;类似&lt;em&gt;1234&lt;/em&gt;的整数常量属性int类型, long类型的常量以字母l或L结尾. 如果一个整数太大以致无法用int表示时, 将被当作long类型处理. 无符号常量以字母u或U结尾. ul或UL表明unsigned long类型&lt;/li&gt;
&lt;li&gt;没有后缀的浮点数常量为double类型, 用后缀f或F表示float类型, 后缀l或L表示long double&lt;/li&gt;
&lt;li&gt;用前缀0表示八进制整数, 0x或0X表示十六进制整数. 八进制与十六进制同样可以使用u或l后缀&lt;/li&gt;
&lt;li&gt;一个&lt;code&gt;字符常量&lt;/code&gt;是一个整数, 书写时将一个字符括在单引号中表示, 如’0’, 字符在机器字符集中的数值就是字符常量的值, 如’0’的值就是48&lt;/li&gt;
&lt;li&gt;使用字符常量的方便之处在于无需关心字符对应的具体值, 且增加了程序可读性. 字符常量一般用来与其他字符进行比较, 比如将一个字符与’0’和’9’进行比较, 以判断其是否是数字字符&lt;/li&gt;
&lt;li&gt;某些字符可通过&lt;code&gt;转义字符序列&lt;/code&gt;表示为字符和字符串常量.&lt;/li&gt;
&lt;li&gt;用&lt;code&gt;\ooo&lt;/code&gt;表示任意字节大小(0~255)的位模式, 其中ooo代表3个八进制数字; 或用&lt;code&gt;\xhh&lt;/code&gt;表示, hh是一个或多个十六进制数字. 举例说明, &lt;code&gt;#define BELL &apos;\007&apos;&lt;/code&gt;, &lt;code&gt;#define BELL &apos;\x7&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;ANSI C的全部转义字符:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\a&lt;/code&gt; - 响铃符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\b&lt;/code&gt; - 回退符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\f&lt;/code&gt; - 换页符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\n&lt;/code&gt; - 换行符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\r&lt;/code&gt; - 回车符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\t&lt;/code&gt; - 横向制表符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\v&lt;/code&gt; - 纵向制表符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\\&lt;/code&gt; - 反斜杆&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\?&lt;/code&gt; - 问号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&apos;&lt;/code&gt; - 单引号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&quot;&lt;/code&gt; - 双引号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\ooo&lt;/code&gt; - 八进制数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\xhh&lt;/code&gt; - 十六进制数&lt;/li&gt;
&lt;li&gt;字符常量&lt;code&gt;&apos;\0&apos;&lt;/code&gt;表示值为0的字符, 即空字符(null). 通常用&lt;code&gt;&apos;\0&apos;&lt;/code&gt;的形式代表0, 以强调某些表达式的字符属性, 但其数字值为0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;常量表达式&lt;/code&gt;是仅仅只包含常量的表达式, 在编译时求值, 而不是在运行时求值, 可以出现在常量可以出现的任何位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;字符串常量&lt;/code&gt;也叫&lt;code&gt;字符串字面值&lt;/code&gt;, 是用双引号括起来的0或多个字符组成的字符序列.&lt;/li&gt;
&lt;li&gt;从技术角度看, 字符串常量就是字符数组, 字符串的内部表示使用一个空字符’\0’作为串的结尾, 因此, 存储字符串的物理存储单元数比括在双引号内的字符数多一个. 这种表示方法也说明, &lt;strong&gt;c语言对字符串长度没有限制&lt;/strong&gt;, 但程序必须扫描完整个字符串后才能确定字符串的长度.&lt;/li&gt;
&lt;li&gt;字符常量与仅包含一个字符的字符串是有区别的, ‘x’与“x”是不同的: 前者是一个整数, 其值是字母x在机器字符集中对应的数值; 后者是一个包含一个字符以及一个结束符’\0’的字符数组&lt;/li&gt;
&lt;li&gt;枚举是一个常量整型值的列表, 例如&lt;code&gt;enum boolean {NO = 0, YES};&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;没有显式说明的情况下, enum类型第一个枚举名的值为0, 第二个为1, 依此类推. 如果只指定了部分枚举名的值, 那么未指定值的枚举名的值将依着最后一个指定值向后递增&lt;/li&gt;
&lt;li&gt;枚举为建立常量值与名字值之间的关联提供了一种便利的方式. 相对于#define语句, 优势在于常量值可以自动生成&lt;/li&gt;
&lt;li&gt;如果变量不是局部变量, 则只能进行一次初始化操作, 从概念上讲, 应该在程序开始执行之前进行, 并且初始化表达式必须为常量表达式&lt;/li&gt;
&lt;li&gt;每次进入函数或程序块时, 显式初始化的局部变量都将被初始化一次, 其初始化表达式可以是任何表达式&lt;/li&gt;
&lt;li&gt;默认情况下, 全局变量与静态变量都将被初始化为0; 未经显式初始化的局部变量的值为无效值&lt;/li&gt;
&lt;li&gt;任何变量的声明都可以使用&lt;code&gt;const&lt;/code&gt;限定符限定, 其指定变量的值不能被修改. 都与数组而言, const限定符指定数组的所有元素的值都不能被修改. const限定符也可配合参数使用, 表明函数不能修改参数的值&lt;/li&gt;
&lt;li&gt;在有负操作数的情况下, 整数除法截取的方向以及取模运算结果符号取决于具体机器的实现&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if (!valid)&lt;/code&gt; vs. `if(valid == 0), 很难判断哪种形式更好, 前者读起来更直观(如果不是有效的话), 但对于以下更复杂的结构可能会难于理解&lt;/li&gt;
&lt;li&gt;一般来说, 自动转换是指将“比较窄的”操作数转换为“比较宽的”操作数, 并不丢失信息的转换&lt;/li&gt;
&lt;li&gt;char类型就是较小的整型, 无论是否进行符号扩展, char类型的变量都被转换为整型变量&lt;/li&gt;
&lt;li&gt;由于c语言没有指定char类型的变量是无符号数变量, 还是带符号数变量, 当将一个char类型的值转换为int类型的值时, 其结果会由于机器的不同而不同&lt;/li&gt;
&lt;li&gt;c语言的定义保证了机器的标准打印字符集中的字符不会是负值, 因此, 在表达式中这些字符总是正值. 但存储在字符变量中的位模式在某些机器中可能是负的, 而在另一些机器中可能是正的. 为了保证程序的可移植性, 如果要在char类型的变量中存储非字符数据, 最好指定signed或unsigned限定符&lt;/li&gt;
&lt;li&gt;c语言中, 很多情况下会进行隐式的算术类型转换, 将“较低”的类型转换为“较高”的类型, 运算结果为“较高”的类型&lt;/li&gt;
&lt;li&gt;表达式中float类型的操作数不会自动转换为double类型. 一般, 数学函数使用双精度类型的变量. 使用float类型主要是为了在使用较大的数组时节省存储空间, 有时也是为了节省机器执行时间&lt;/li&gt;
&lt;li&gt;赋值时也要进行类型转换, 赋值运算符右边的值需要转换为左边变量的类型, 左边变量的类型即赋值表达式结果的类型&lt;/li&gt;
&lt;li&gt;当较长的类型转换为较短的类型, 超出的高位部分将被丢弃: int转char, 丢失信息; float转int, 小数部分被截断; double转float, 四舍五入还是截断取决于具体实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;函数调用的参数是表达式&lt;/strong&gt;, 因此在将参数传递给函数时也能进行类型转换. 在没有函数原型的情况下, char与short类型都被转换为int型, float被转换为double. 因此, 即使调用函数的参数为char或float类型, 也将函数参数声明为int或double类型&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(类型名) 表达式&lt;/code&gt; - &lt;code&gt;强制类型转换&lt;/code&gt;, 准确含义: 表达式首先被赋给类型名指定的类型的某个变量, 然后再用该变量替换原表达式&lt;/li&gt;
&lt;li&gt;直到看到源码, 我才明白为什么伪随机叫伪随机, 为什么需要初始化种子来提高伪随机的随机性:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; long&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; rand&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1103515245&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 12345&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)(next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 65536&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 32768&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; srand&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; seed&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; seed;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;++n&lt;/code&gt;, 先自增, 再使用变量n的值; &lt;code&gt;n++&lt;/code&gt;, 先使用变量n的值, 再将n的值加1&lt;/li&gt;
&lt;li&gt;位操作符只能用于整型操作数, 包括&lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt;(异或), &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;(按位取反)&lt;/li&gt;
&lt;li&gt;按位与运算经常用于屏蔽某些二进制位&lt;/li&gt;
&lt;li&gt;按位或运算常用于将某些二进制位置1&lt;/li&gt;
&lt;li&gt;对无符号数进行右移, 左边空出的部分用0填补; 对带符号数进行右移, 某些机器将对左边空出的部分用符号位填补(&lt;code&gt;算术移位&lt;/code&gt;), 另一些机器则对左边空出的部分用0填补(&lt;code&gt;逻辑移位&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;表达式&lt;code&gt;x &amp;amp; ~077&lt;/code&gt;比表达式&lt;code&gt;x &amp;amp; 0177700&lt;/code&gt;要好, 因为前者与机器字长无关, 而后者假定x是16位的数值. 前者可移植性更强, 并且没有增加额外的开销, ~077是常量表达式, 在编译时求值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expr1 op= expr2&lt;/code&gt;等价于&lt;code&gt;expr1 = (expr1) op (expr2)&lt;/code&gt;. 前者&lt;em&gt;expr1&lt;/em&gt;只计算一次, 后者&lt;em&gt;expr2&lt;/em&gt;两边的圆括号必不可少&lt;/li&gt;
&lt;li&gt;赋值运算符的其他优点: 表示方式与人们的思维习惯更接近, 使代码更易读, 还有助于编译器产生高效代码&lt;/li&gt;
&lt;li&gt;赋值表达式的类型就是它左边操作数的类型, 其值是赋值操作完成后的值&lt;/li&gt;
&lt;li&gt;在使用条件表达式&lt;code&gt;expr1 ? expr2 : expr3&lt;/code&gt;, 建议使用圆括号包裹expr1, 使表达式的条件部分更易读&lt;/li&gt;
&lt;li&gt;c语言没有指定同一运算符中多个操作数的计算顺序(即a+b等价于b+a), 也没有指定函数各参数的求值顺序. 因此&lt;code&gt;printf(&quot;%d %d\n&quot;, ++n, power(2, n));&lt;/code&gt;在不同的编译器中可能产生不同的结果, 取决于++n与power函数的执行顺序&lt;/li&gt;
&lt;li&gt;函数调用, 嵌套赋值语句, 自增与自减运算符都可能产生“副作用”, 在对表达式求值的同时, 修改了某些变量的值. 表达式何时会产生副作用将由编译器决定, 最佳的求值顺序同机器结构有关&lt;/li&gt;
&lt;li&gt;ANSI C标准明确规定了所有对参数的副作用都必须在函数调用之前生效&lt;/li&gt;
&lt;li&gt;在任何一种编程语言中, 如果代码的执行结果与求值顺序相关, 则都不是好的程序设计风格.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-3-控制流&quot;&gt;Chapter 3 控制流&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在c语言中, 分号是语句结束符; 用花括号将一组声明和语句括在一起构成了&lt;code&gt;复合语句&lt;/code&gt;(或称&lt;code&gt;程序块&lt;/code&gt;), 复合语句在语法上等价于单条语句&lt;/li&gt;
&lt;li&gt;程序的缩进结构明确地表明了设计意图, 但编译器无法获知. 因此在有if语句嵌套的情况下使用花括号, 使代码可读性更强&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*二分搜索*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; binsearch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; low, high, mid;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    high &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; high){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; high) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[mid])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            high &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[mid])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mid;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;switch语句是一种多路判定语句, 它测试表达式是否与一些常量整数值中的某一个匹配, 并执行相应的分支操作. 若没有哪个分支能匹配表达式, 执行标记为default的分支, default分支是可选的&lt;/li&gt;
&lt;li&gt;switch语句中, &lt;strong&gt;case的作用只是一个标号&lt;/strong&gt;. 某个分支中的代码执行完后, 程序将进入下一个分支继续执行, 付费在程序中显式地跳转, 常用的方法是使用break语句或return语句&lt;/li&gt;
&lt;li&gt;除了一个计算需要多个标号的情况下, 应尽量减少从一个分支直接进入下一个分支执行的用法, 在不得不使用的情况下应该加上适当的注释&lt;/li&gt;
&lt;li&gt;作为一种良好的程序设计风格, 在switch语句最后一个分支(default)后也加上一个break语句. 这样做在逻辑上没有必要, 但当需要向该switch语句后添加其他分支时, 这种防范措施会降低犯错的可能性&lt;/li&gt;
&lt;li&gt;如果省略for语句的测试条件, 则认为其值永远是真值. &lt;code&gt;for(;;){ ... }&lt;/code&gt;是一个无限循环&lt;/li&gt;
&lt;li&gt;&lt;code&gt;while&lt;/code&gt; vs. &lt;code&gt;for&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果没有初始化或重新初始化的操作, 使用while循环语句更自然&lt;/li&gt;
&lt;li&gt;如果语句中需要执行简单的初始化和变量递增, 使用for语句更适合, 它将循环控制语句集中放在循环的开头, 结构更紧凑, 更清晰&lt;/li&gt;
&lt;li&gt;将循环控制部分集中在一起, 对于多重嵌套循环, 优势更明显&lt;/li&gt;
&lt;li&gt;Shell(希尔)排序: 先比较距离远的元素, 从而快速减少大量的无序情况, 减轻后续的工作. 被比较的元素之间的距离逐步减少, 直到减少为1, 排序就变成了相邻元素的互换&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*Shell*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;最外层的for语句控制两个被比较元素之间的距离, 从n/2开始, 逐步进行对折, 直到距离为0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;中间层的for循环语句用于在元素间移动位置&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;最内层的for语句用于比较各对相距gap个位置的元素, 当两个元素逆序时将它们互换&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;由于gap的值最终要递减到1, 因此所有元素最终都会位于正确的排序位置上&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; shellsort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap, i , j, temp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; gap &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;gap]; j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                temp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;                v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gap] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;逗号运算符是c语言优先级最低的运算符, 被逗号分隔的一对表达式将按照从左到右的顺序进行求值, 各表达式右边的操作数的类型和值即为其结果的类型和值&lt;/li&gt;
&lt;li&gt;逗号运算符最适用于关系紧密的结构.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;do { ... } while (表达式)&lt;/code&gt; - &lt;code&gt;do-while&lt;/code&gt;循环体至少会被执行一次&lt;/li&gt;
&lt;li&gt;break - 从switch语句或循环中提前退出&lt;/li&gt;
&lt;li&gt;continue - 执行下一次循环. 在while或do-while语句中, 意味着立即执行测试部分; 在for循环中, 意味着将控制转移到递增循环变量环节&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-4-函数与程序结构&quot;&gt;Chapter 4 函数与程序结构&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个设计得当的函数可以将程序中不需要了解的具体操作细节隐藏起来, 从而使整个程序结构更加清晰, 并降低修改程序的难度&lt;/li&gt;
&lt;li&gt;c语言在设计中考虑了函数的高效性和易用性两个因素, c语言一般由许多小的函数组成, 而不是由少量较大的函数组成&lt;/li&gt;
&lt;li&gt;分别处理几个小的部分, 比处理一个大的整体更容易. 这样可以将不相关的细节隐藏在函数中, 从而减少不必要的相互影响的机会, 并且函数也可以在其他函数中使用&lt;/li&gt;
&lt;li&gt;函数定义中的各构成部分都可以省略, 最简单的函数: &lt;code&gt;dummy() {}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果函数定义中省略了返回值类型, 默认为int类型&lt;/li&gt;
&lt;li&gt;程序可以看成变量定义和函数定义的集合. 函数之间通信可以通过参数, 函数返回值以及外部变量进行&lt;/li&gt;
&lt;li&gt;return语句的后面可以跟任何表达式, 在必要时, 表达式将被转换为函数的返回值类型. 表达式两边通常加一对圆括号&lt;/li&gt;
&lt;li&gt;如果某个函数从一个地方返回时有返回值, 而从另一个地方返回时没有返回值, 该函数&lt;strong&gt;并不非法&lt;/strong&gt;, 但可能是一种问题的征兆&lt;/li&gt;
&lt;li&gt;在任何情况下, 如果函数没有成功返回一个值, 则它的“值”肯定是无用的&lt;/li&gt;
&lt;li&gt;cc命令用&lt;code&gt;.c&lt;/code&gt;和&lt;code&gt;.o&lt;/code&gt;两种扩展名区分源文件和目标文件&lt;/li&gt;
&lt;li&gt;如果没有函数原型, 则函数将在第一次出现的表达式中被隐式声明, 该函数的返回值被假定为int类型, 但上下文并不对其参数做任何假设.&lt;/li&gt;
&lt;li&gt;如果函数声明中不包含参数, 编译程序不会对函数的参数作任何假设, 并会关闭所有的参数检查. 如果函数带有参数, 要声明它们; 如果不带参数, 则使用void进行声明&lt;/li&gt;
&lt;li&gt;由于c语言不允许在一个函数中定义其他函数, 因此函数本身是“外部的”. 默认情况下, 外部变量与函数具有下列性质: 通过同一个名字对外部变量的所有引用实际上都是引用同一个对象&lt;/li&gt;
&lt;li&gt;外部变量可以在全局范围内访问, 这为函数之间的数据交换提供了一种可以代替函数参数与返回值的方式.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果函数之间需要共享大量的变量, 使用外部变量要比使用一个长的参数表更方便, 有效&lt;/strong&gt;, 但这种方式可能对程序结构产生不良影响, 而且可能会导致程序中各个函数之间具有太多的数据关系&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果两个函数必须共享某些数据, 而它们互不调用对方, 这种情况下, 最简单的方式是将这些共享数据定义为外部变量, 而不是作为函数参数传递&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在函数中重复出现的代码段, 最好设计成独立的函数&lt;/li&gt;
&lt;li&gt;逆波兰表达式, &lt;code&gt;push&lt;/code&gt;和&lt;code&gt;pop&lt;/code&gt;函数必须共享栈和栈顶指针, 因此定义在函数外部, 作为外部变量. 由于main函数本身没有引用栈或栈顶指针, 因此, 对于main函数就是将它们隐藏起来了&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &amp;lt;ctype.h&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ungetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 获取下一个运算符或数值操作数 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, c;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos; &apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\t&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isdigit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 不是数 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isdigit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isdigit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 整数部分 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;.&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;isdigit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 小数部分 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; EOF)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        ungetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(c);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; NUMBER;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BUFSIZE&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[BUFSIZE];&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 用于ungetch函数的缓冲区 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bufp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       /* buf中下一个空闲位置 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /* 取一个字符, 可能是ungetch写回的字符 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (bufp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bufp] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; getchar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ungetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; c&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; /* 写回字符 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (bufp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BUFSIZE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        printf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ungetch: too many characters&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;        buf&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[bufp&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;程序中经常会出现这样的情况: 程序不能确定它已经读入的输入是否已经足够, 除非超前多读入一些输入. 读入一些字符以合成一个数字的情况便是一例(上述): 在看到第一个非数字字符之前, 已经读入的数的完整性是不能确定的. 由于程序要超前读入一个字符, 这样就导致最后有一个字符不属于当前所要读入的数&lt;/li&gt;
&lt;li&gt;上述, ungetch函数将要压回的字符放到一个共享缓冲区中, 当该缓冲区不空时, getch函数从缓冲区中读取字符; 当缓冲区为空时, getch函数调用getchar函数直接从输入读取字符&lt;/li&gt;
&lt;li&gt;如果要在外部变量的定义之前使用该变量, 或者外部变量的定义与变量的使用不在同一个源文件中, 则必须在相应的变量&lt;code&gt;声明&lt;/code&gt;中强制性地使用关键字 extern&lt;/li&gt;
&lt;li&gt;外部变量的&lt;code&gt;声明&lt;/code&gt;和&lt;code&gt;定义&lt;/code&gt;严格区分是重要的: &lt;code&gt;变量声明&lt;/code&gt;用于说明变量的属性(主要是类型), &lt;code&gt;变量定义&lt;/code&gt;除此之外还将引起存储器的分配.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 定义外部变量, 并为止分配存储单元, 并可以作为该源文件其余部分的声明 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;double&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; val&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[MAXVAL];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 为源文件的其余部分声明了外部变量, 但这2个声明并没有建立变量或为它们分配存储单元 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;extern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;extern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; double&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在一个源程序的所有源文件中, 一个外部变量只能在某个文件中定义1次, 而其他文件可以通过extern声明来访问它\
(定义外部变量的源文件中也可以包含对该外部变量的extern声明)&lt;/li&gt;
&lt;li&gt;外部变量的初始化只能出现在其定义中&lt;/li&gt;
&lt;li&gt;尽可能将共享部分集中在一起, 这样就只需一个副本, 改进程序时也容易保证程序的正确性&lt;/li&gt;
&lt;li&gt;一方面期望每个文件只能访问它完成任务所需的信息; 另一方面是现实中维护较多的头文件比较困难. 折中的做法是: 对于某些中等规模的程序, 最好只用一个头文件存放程序中各部分共享的对象; 较大的程序需要使用更多的头文件, 需要精心组织&lt;/li&gt;
&lt;li&gt;用&lt;code&gt;static&lt;/code&gt;声明限定外部变量和函数, 将其后声明的对象的作用域限定为被编译源文件的剩余部分(作用域限定在本源文件内)&lt;/li&gt;
&lt;li&gt;static类型的内部变量是某种特定函数的局部变量, 只能在该函数中使用, 但不管其所在的函数是否被调用, 它一直存在. 换言之, static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间(一直存在)的变量&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt;声明告诉编译器, 它所声明的变量在程序中使用频率较高. 其思想是, 将register变量放在机器的寄存器中, 是程序更小, 执行速度更快&lt;/li&gt;
&lt;li&gt;register声明只适用于局部变量以及函数的形式参数&lt;/li&gt;
&lt;li&gt;每个函数中只有很少的变量可以保存在寄存器中, 且只允许某些类型的变量.&lt;/li&gt;
&lt;li&gt;过量的寄存器声明并没有害处, 因为编译器可以忽略过量的或不支持的寄存器变量声明. 无论寄存器变量实际上是否存放在寄存器中, 它的地址都不能访问&lt;/li&gt;
&lt;li&gt;c语言不允许在函数中定义函数, 但在函数中可以通过&lt;strong&gt;程序块&lt;/strong&gt;结构的形式定义变量. 一对花括号包裹的, 即为一个复合语句程序块, 在程序块内声明的变量可以隐藏程序块之外的同名变量, 它们之间没有任何联系&lt;/li&gt;
&lt;li&gt;每次进入程序块, 在程序块中声明以及初始化的自动变量都将被初始化, 但静态变量(static关键字修饰的)只在第一次进入程序块时被初始化一次&lt;/li&gt;
&lt;li&gt;在一个好的程序设计风格中, 应该避免出现变量名隐藏外部作用域中相同名字的情况, 否则, 很可能引起混乱和错误&lt;/li&gt;
&lt;li&gt;在不显式初始化的情况下, 外部变量和静态变量都将被初始化为0; 局部变量和寄存器变量的初值没有定义, 为无效值&lt;/li&gt;
&lt;li&gt;对于外部变量与静态变量, 初始化表达式必须是常数表达式, 且只初始化一次; 对于局部变量和寄存器变量, 在每次进入函数或程序块时都将被初始化&lt;/li&gt;
&lt;li&gt;局部变量的初始化等效于简写的赋值语句. 但考虑到变量声明中的初始化表达式容易被忽略, 且距离使用的位置较远, 一般使用显式的赋值语句&lt;/li&gt;
&lt;li&gt;数组的初始化可以在声明的后面紧跟一个初始化&lt;strong&gt;表达式列表&lt;/strong&gt;, 初始化表达式列表用花括号括起来, 各初始化表达式之间用逗号分隔&lt;/li&gt;
&lt;li&gt;当省略数组长度时, 编译器将把花括号中初始化表达式的个数作为数组的长度; 若初始化表达式的个数比数组元素少, 在对外部变量, 静态变量和局部变量, 没有初始化表达式的元素用0进行初始化&lt;/li&gt;
&lt;li&gt;字符数组的初始化可用一个字符串代替花括号形式的初始化表达式序列. &lt;code&gt;char a = &quot;kissg&quot;;&lt;/code&gt; 等价于 &lt;code&gt;char a = {&apos;k&apos;, &apos;i&apos;, &apos;s&apos;, &apos;s&apos;, &apos;g&apos;, &apos;\0&apos;}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;数字是以反序生成的: 低位数字先于高位数字生成, 但它们必须以与此相反的次序打印&lt;/li&gt;
&lt;li&gt;函数递归调用自身时, 每次调用的都会得到一个与之前的局部变量几乎不同的新的局部变量集合&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 快速排序(递增)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   对一个给定的数组, 从中选择一个元素, 以该元素为界将其余元素划分为两个子集,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   一个子集中的所有元素都小于该元素, 另一个子集中的所有元素都大于或等于该元素.*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; qsort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; left&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; right&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, last;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; j);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (left &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; right)&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 数组包含的元素少于2个, 不执行任何操作 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, left, (left &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; right)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  /* 将划分子集的元素 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    last &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; left;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                      /* 移动到v[0] */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; left&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; right; i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 划分子集 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[left])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;            swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;last, i);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, left, last);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              /* 恢复划分子集的元素 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    qsort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, left, last&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    qsort&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(v, last&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, right);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; swap&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; j&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    temp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;    v&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[j] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; temp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;递归并不节省存储的开销, 递归调用过程中必须在某个地方维护一个存储处理值的栈; 递归的执行速度也不快, 但递归代码比较紧凑, 并且相比于非递归代码更易编写与理解&lt;/li&gt;
&lt;li&gt;C预处理:&lt;/li&gt;
&lt;li&gt;文件包含:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#include &quot;filename&quot;&lt;/code&gt;或&lt;code&gt;#include &amp;lt;filename&amp;gt;&lt;/code&gt;行都将被替换为filename指定的文件内容. 引号格式, 将在源文件所在位置查找目标文件; 若在源文件所在位置没有找到指定文件, 或使用&amp;lt;&amp;gt;格式, 将根据相应的规则查找该文件. 被包含的文件本身也可以包含#include指令&lt;/li&gt;
&lt;li&gt;在大的程序中, #include指令是将所有声明捆绑在一起的较好的方法, 它保证了所有的源文件都具有相同的定义与变量声明, 可以避免出现一些不必要的错误&lt;/li&gt;
&lt;li&gt;宏替换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#define name subtext&lt;/code&gt; - 后续所有出现name记号的地方都被替换为subtext&lt;/li&gt;
&lt;li&gt;较长的宏定义可以分成若干行, 需要在行末加上反斜杆.&lt;/li&gt;
&lt;li&gt;#define指令定义的name的作用域从其定义点开始, 到被编译的源文件的末尾处结束&lt;/li&gt;
&lt;li&gt;替换只对记号进行, 对括在引号中的字符串不起作用&lt;/li&gt;
&lt;li&gt;subtext是任意的, &lt;code&gt;#define forever for(;;)&lt;/code&gt;定义了一个名为forever的无限循环&lt;/li&gt;
&lt;li&gt;宏定义可以使用参数, 这样可以对不同的宏调用使用不同的替换文本\
&lt;code&gt;#define max(A, B) ((A) &amp;gt; (B) ? (A) : (B))&lt;/code&gt; - 使用时看起来像函数调用, 但其直接将替换文本插入到代码中(跟宏汇编很像)&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;#undef&lt;/code&gt;指令取消名字的宏定义, 这样可以保证后续的调用是函数调用, 而不是宏调用&lt;/li&gt;
&lt;li&gt;形式参数不能用带引号的字符串替换? 如果在替换文本中, 参数名以&lt;code&gt;#&lt;/code&gt;作为前缀, 则结果将被扩展为由实际参数替换该参数的带引号的字符串\
#define dpirnt(expr) printf(#expr “= %g\n”, expr)\
dprint(x/y) -&amp;gt; printf(“x/y” “= %g \n”, x/y)&lt;/li&gt;
&lt;li&gt;预处理器的&lt;code&gt;##&lt;/code&gt;运算符为宏扩展提供了一种连接实际参数的手段. 如果subtext中的参数与##相邻, 则该参数将被实际参数替换, ##与前后的空白字符将被删除, 并对替换后的结果重新扫描:\
#define paste(front, back) front ## back\
paste(name, 1) - 建立记号为name1 (我不懂)&lt;/li&gt;
&lt;li&gt;条件包含
&lt;ul&gt;
&lt;li&gt;使用条件语句对预处理本身进行控制, 条件语句的值在预处理执行的过程中进行计算. 为编译过程中根据计算所得的条件值选择性地包含不同代码提供了一种手段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#if&lt;/code&gt;语句对其中的常量整型表达式进行求值, 若该表达式的值不等于0, 则包含其后的各行, 直到遇到&lt;code&gt;#endif&lt;/code&gt;, &lt;code&gt;#elif&lt;/code&gt;或&lt;code&gt;#else&lt;/code&gt;语句&lt;/li&gt;
&lt;li&gt;c语句专门定义了&lt;code&gt;#ifdef&lt;/code&gt;和&lt;code&gt;ifndef&lt;/code&gt;语句, 用于测试某个名字是否已经定义&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-5-指针与数组&quot;&gt;Chapter 5 指针与数组&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ANSI C使用类型&lt;code&gt;void*&lt;/code&gt;作为通用指针类型, 它可以存放指向任何类型的指针, 但不能间接引用自身&lt;/li&gt;
&lt;li&gt;指针是一种保存变量地址的变量, 使用指针通常可以生成更高效的, 更紧凑的代码&lt;/li&gt;
&lt;li&gt;一元运算符&lt;code&gt;&amp;amp;&lt;/code&gt;可用于取一个对象的地址, 如&lt;code&gt;p = &amp;amp;c&lt;/code&gt;. &amp;amp;只能应用于内存中的对象, 即变量与数组元素, 不能作用于表达式, 常量或寄存器变量&lt;/li&gt;
&lt;li&gt;一元运算法&lt;code&gt;*&lt;/code&gt;是间接寻址或间接引用运算符. 当它作用于指针时, 将访问指针所指向的的对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int *ip;&lt;/code&gt; - ip是指向int类型的指针. 该声明语句表明&lt;code&gt;表达式*ip&lt;/code&gt;的结果是int类型\
&lt;code&gt;ip&lt;/code&gt;与&lt;code&gt;*ip&lt;/code&gt;的区别: &lt;code&gt;ip&lt;/code&gt;是指针, 是一个变量, 存放的是内存地址; &lt;code&gt;*ip&lt;/code&gt;是一个表达式, 其结果是存放在指针指向地址的值\
通俗地讲: 在指针ip所指向的内存地址, 给我一个int类型的变量&lt;/li&gt;
&lt;li&gt;指针只能指向某种特定类型的对象, 即每个指针都必须指向某种特定的数据类型&lt;/li&gt;
&lt;li&gt;c语言以传值的方式将参数值传递给被调用函数, 因此, 被调用函数不能直接修改主调函数中变量的值; 若要实现对变量的修改, 可通过传地址的方式实现, 通过指针间接访问指向它们指向的操作数&lt;/li&gt;
&lt;li&gt;指针参数使得被调函数能够访问和修改主调函数中对象的值, 就好像给一个房间的地址, 让被调函数自己去房间里做修改, 改成什么样就是什么样&lt;/li&gt;
&lt;li&gt;scanf函数的大致实现原理: 将标识是否到达文件结尾的状态作为函数的返回值, 同时使用一个指针参数实现修改, 并传回给主调函数\
(利用指针作修改, 利用函数返回来返回状态量)&lt;/li&gt;
&lt;li&gt;通过数组下标所能完成的任何操作都可以通过指针来实现. 一般来说, 用指针编写的程序比用数组下标编写的程序执行速度更快, 但理解起来会稍微困难一点&lt;/li&gt;
&lt;li&gt;无论数组中的元素是何种类型或数组长度是多少, “指针+1”的操作都成立, 它意味着, pa+1指向pa所指向的对象的下一个对象. 对pa+i, 类似.\
在计算pa+i时, i将根据pa指向的对象的长度按比例缩放, 而pa指向的对象的长度取决于pa的声明&lt;/li&gt;
&lt;li&gt;数组名所代表的就是该数组第一个元素的地址.&lt;/li&gt;
&lt;li&gt;在计算数值元素a[i]的值时, c语言实际上先将其转换为&lt;code&gt;*(a+i)&lt;/code&gt;的形式, 再进行求值. 在程序中, 这两种形式是等价的. 同理, &amp;amp;a[i]与a+i的含义也是相同的.换言之, 一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现&lt;/li&gt;
&lt;li&gt;数组名与指针仍有不同, 指针是一个变量, 但数组名不是. 因此, pa=a, pa++是合法的, a=pa, a++是非法的(a为数组名, pa是指针)&lt;/li&gt;
&lt;li&gt;当把数组名传递给一个函数时, 实际上传递的是该数组第一个元素的地址, 在被调用函数中, 该参数是一个局部变量, 因此, 数组名参数必须是指针, 即一个存储地址值的变量&lt;/li&gt;
&lt;li&gt;在函数定义中, 形参&lt;code&gt;char s[]&lt;/code&gt;和&lt;code&gt;char *s&lt;/code&gt;是等价的, 但通常更习惯于使用后者, 它比前者更直观地表明了该参数是一个指针&lt;/li&gt;
&lt;li&gt;如果确信相应的元素存在, 可通过下标访问数组第一个元素之前的元素, 但引用数组边界之外的对象是非法的&lt;/li&gt;
&lt;li&gt;c语言中的地址算术运算方法是一致且有规律的, 将指针, 数组和地址的算术运算集成在一起是c语言的一大特点&lt;/li&gt;
&lt;li&gt;c语言保证0永远不是有效的数据地址&lt;/li&gt;
&lt;li&gt;指针和整数之间不能相互转换, 但0是唯一的例外: 常量0可以赋值给指针, 指针也可以和常量0进行比较. 程序中经常用符号常量&lt;code&gt;NULL&lt;/code&gt;代替常量0, 这样便于更清晰地说明常量0是指针的一个特殊值&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;某些&lt;/strong&gt;情况下, 对指针可以进行比较运算. 例如, 如果指针p和q指向同一个数组的成员, 那么它们之间就可以进行类似于==, !=, &amp;lt;的关系比较运算. 任何指针与0进行相等或不等的比较运算都是有意义的. 指向不同数组的元素的指针之间的算术或比较运算没有定义, 通常是没有意义的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;指针的减法运算也是有意义的&lt;/strong&gt;: 如果p和q指向相同数组中的元素, 且p&amp;lt;q, 那么q-p+1就是位于p和q指向的元素之间的元素数目(长度)&lt;/li&gt;
&lt;li&gt;指针的算术运算具有一致性: 如果处理的数据类型是比字符类型占据更多存储空间的浮点类型, 并且p是一个指向浮点类型的指针, 那么在执行p++之后, p指向下一个浮点数的地址. 所有指针运算都会自动考虑它所指向的对象的长度&lt;/li&gt;
&lt;li&gt;有效的指针运算包括:&lt;/li&gt;
&lt;li&gt;相同类型指针之间的赋值运算&lt;/li&gt;
&lt;li&gt;指针同整数之间的加法或减法运算&lt;/li&gt;
&lt;li&gt;指向相同数组中元素的两个指针间的减法或比较运算&lt;/li&gt;
&lt;li&gt;将指针赋值为0或指针与0之间的比较运算&lt;/li&gt;
&lt;li&gt;&lt;em&gt;字符串常量&lt;/em&gt;是一个字符数组, “I am kissg”在字符串内部表示中, 字符串数组以空字符’\0’结尾, 程序可以通过检查空字符找到字符串数组的结尾. 字符串常量占据的存储单元数也因此比双引号内的字符数大1&lt;/li&gt;
&lt;li&gt;字符串常量作为函数参数, 实际上是通过字符指针访问该字符串的. 换言之, 字符串常量可通过一个指向其第一个元素的指针访问&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;c语言没有提供将整个字符串作为一个整体进行处理的操作符&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; amessage&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;[]&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am kissg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;pmessage &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;I am kissg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*           _      ______________&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  pmessage: | | --&amp;gt; |I am kissg\0|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            ______________&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  amessage: |I am kissg\0|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;上述声明中, amessage是一个仅仅足以存放初始化字符串以及空字符’\0’的一维数组, 数组中的单个字符可以进行修改, 但amessage始终指向同一个存储位置. pmessage是一个指针, 其初始值指向一个字符串常量, 之后它可以被修改为以指向其他地址, 但如果试图修改字符串的内容, 结果无定义.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 代码进化史 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; strcpy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ((&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[i]) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        i&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; strcpy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ((&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;t) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        s&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; strcpy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ((&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\0&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; strcpy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;进栈与出栈的标准写法:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*p++ = val;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;val = *--p;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char *lineptr[MAXLINES]&lt;/code&gt; - 表示lineptr是一个具有MAXLINES个元素的一维数组, 其中数组的每个元素都是一个指向字符类型对象的指针. 即, lineptr[i]是一个字符指针, *lineptr[i]是该指针指向的第i个文本行的首字符&lt;/li&gt;
&lt;li&gt;在c语言中, 二维数组实际上是一种特殊的一维数组, 它的每个元素也是一个一维数组&lt;/li&gt;
&lt;li&gt;如果将二维数组作为参数传递给函数, 那么在函数的参数声明中, 必须指明数组的&lt;strong&gt;列数&lt;/strong&gt;. 数组的行数没有太大的关系, 因为函数调用时传递的是指针, 它指向由行向量构成的一维数组&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* 二维数组 vs. 指针数组 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;从语法角度讲, a[3][4]和b[3][4]都是对一个int对象的合法引用. 但a是一个真正的二维数组, 它分配了200个int类型长度的存储空间, 并且通过常规的矩阵下标计算公式20*row+col计算得到a[row][col]的位置; 但对b来说, 该定义仅仅分配了10个指针, 并且没有初始化, 它们的初始化必须以显式的方式进行, 比如静态初始化或通过代码初始化, 假定b的每个元素指向一个具有20个元素的数组, 那么编译器就要为它分配200个int类型长度的存储空间以及10个指针的存储空间&lt;/li&gt;
&lt;li&gt;指针数组的一个重要优点在于, &lt;strong&gt;数组的每一行长度可以不同&lt;/strong&gt;. 指针数组最频繁的用户是存放具有不同长度的字符串, 如&lt;code&gt;char *name[] = {&quot;kissg&quot;, &quot;Engine&quot;, &quot;Treasure&quot;};&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在支持c语言的环境中, 可以在程序开始执行时将命令行参数传递给程序. 调用主函数main时, 它带有2个参数, 第一个参数, 通常称为argc, 用于参数计数, 第二个参数, 称为argv, 用于参数向量, 是一个指向字符串数组的指针, 其中每个字符串对应一个参数&lt;/li&gt;
&lt;li&gt;按照c语言的约定, argv[0]的值是启动该程序的程序名, 因此argc至少为1. ANSI标准要求argv[argc]的值必须为一个空指针.&lt;/li&gt;
&lt;li&gt;标准库函数&lt;code&gt;strstr(s, t)&lt;/code&gt;返回一个指针, 该指针指向字符串t在字符串s中第一次出现的位置; 如字符串t没有在s中出现, 函数返回NULL&lt;/li&gt;
&lt;li&gt;Unix系统中的C语言程序有一个公共的约定: 以负号开头的参数表示可选标志或参数. 可选参数应该允许以任意次序出现, 同时程序的其余部分应该与命令行中参数的数目无关. 此外, 如果可选参数能够组合使用, 将为使用者带来更大的方便&lt;/li&gt;
&lt;li&gt;在c语言中, &lt;strong&gt;函数本身不是指针&lt;/strong&gt;, 但可以定义指向函数的指针. 这种类型的指针可以被赋值, 存放在数组中, 传递给函数以及作为函数的返回值等等&lt;/li&gt;
&lt;li&gt;一个例子: &lt;code&gt;void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任何类型的指针都可以转换为void *类型, 并且在将它转换回原来的类型时不会丢失&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int (*comp)(void *, void*)&lt;/code&gt; - 表明comp是一个指向函数的指针, *代表一个函数, 该函数具有2个void *类型的参数, 其返回值类型是int&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int \*comp(void *, void *)&lt;/code&gt; - 表明comp是一个函数, 该函数返回一个指向int类型的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;char **argv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    argv: pointer to pointer to char, 指针的指针&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;int (*daytab)[13]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    daytab: pointer to array[13] of int, 数组指针&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;int *daytab[13]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    daytab: array[13] of pointer to int, 指针数组&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;void *comp()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    comp: function returning pointer to void, 返回值为void *型的函数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;void (*comp)()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    comp: pointer to function returning void, 函数指针&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;char (*(*x())[])()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    x: function returning pointer to array[] of pointer to function returning char, 这...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;char (*(*x[3])())[5]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    x: array[3] of pointer to function returning pointer to array[5] of char, zhe ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如上所述, c语言的声明不能从左至右阅读, 而且使用了太多的圆括号. 前缀运算符*优先级低于(), 因此, 加不加(), 相差巨大.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-6-结构&quot;&gt;Chapter 6 结构&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;结构是一个或多个变量的集合, 这些变量可能为不同的类型, 为了处理的方便而将这些变量组织在一个名字下. 由于结构将一组相关的变量看作一个单元而不是各自独立的实体, 结构有助于组织复杂的数据&lt;/li&gt;
&lt;li&gt;结构可以拷贝, 赋值, 传递给函数, 作为函数的返回值&lt;/li&gt;
&lt;li&gt;关键字&lt;code&gt;struct&lt;/code&gt;引入结构声明, 结构声明由包含在花括号内的一系列声明组成. 关键字&lt;code&gt;struct&lt;/code&gt;后的名字是&lt;em&gt;可选的&lt;/em&gt;, 称为&lt;code&gt;结构标记&lt;/code&gt;, 用于为结构命名, 在定义之后, 结构标记就代表花括号内的声明&lt;/li&gt;
&lt;li&gt;struct声明定义了一种数据结构, 在标志结构成员表结束的右花括号之后可以跟一个变量表, 这与其他基本类型的变量声明相同&lt;/li&gt;
&lt;li&gt;如果结构声明之后不带变量表, 则不需要为其分配存储空间, 它仅仅描述了一个结构的模板或轮廓.&lt;/li&gt;
&lt;li&gt;通过点标记法(a.b)来引用某个特定结构的成员&lt;/li&gt;
&lt;li&gt;结构可以嵌套&lt;/li&gt;
&lt;li&gt;结构的合法操作只有:&lt;/li&gt;
&lt;li&gt;作为一个整体复制与赋值, 包括向函数传递参数以及从函数返回&lt;/li&gt;
&lt;li&gt;通过&amp;amp;运算符取地址&lt;/li&gt;
&lt;li&gt;访问其成员&lt;/li&gt;
&lt;li&gt;结构之间不能进行比较&lt;/li&gt;
&lt;li&gt;传递结构的方法:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;分别传递结构成员&lt;/li&gt;
&lt;li&gt;传递整个结构&lt;/li&gt;
&lt;li&gt;传递指向结构的指针…&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;参数名和结构成员同名不会引起冲突. 事实上, 使用重名可以强调两者之间的关系&lt;/li&gt;
&lt;li&gt;如果传递给函数的结构很大, 使用指针方式的效率通常比复制整个结构的效率高&lt;/li&gt;
&lt;li&gt;结构成员运算符&lt;code&gt;.&lt;/code&gt;的优先级比&lt;code&gt;*&lt;/code&gt;的优先级高, 因此*pp.x等价*(pp.x)&lt;/li&gt;
&lt;li&gt;在所有运算符中, 结构运算符&lt;code&gt;.&lt;/code&gt;和&lt;code&gt;-&amp;gt;&lt;/code&gt;, 用于函数调用的&lt;code&gt;()&lt;/code&gt;以及用于下标的&lt;code&gt;[]&lt;/code&gt;优先级最高, 它们同操作数之间的结构也最紧密&lt;/li&gt;
&lt;li&gt;&lt;code&gt;++p-&amp;gt;len&lt;/code&gt;将增加len的值, 而不是p的值, 因为-&amp;gt;运算的优先级更高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一元运算符&lt;/strong&gt;`sizeof, 可用于计算任意对象的长度, 用整型值表示对象或类型占用的存储空间字节数, 其中对象可以是变量, 数组或结构; 类型可以是基本类型(int, double等), 也可以是结构类型或指针类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NKEYS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; keytab &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;#define&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NKEYS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;sizeof&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; keytab &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; sizeof&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; keytab&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;以上, 使用第二种方法, 即使类型改变了, 也不需要改动程序&lt;/li&gt;
&lt;li&gt;条件编译语句#if不能使用sizeof, 因为预处理器不对类型名进行分析&lt;/li&gt;
&lt;li&gt;但预处理器并不计算#define语句中的表达式, 因此在#define中使用sizeof是合法的&lt;/li&gt;
&lt;li&gt;两个指针之间的加法运算是非法的, 但指针的减法运算是合法的, 因此:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; high) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;mid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; low &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (high &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; low&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;c语言的定义保证数组末尾之后的第一个元素的指针运算是可以正常执行的(即&amp;amp;tab[n])&lt;/li&gt;
&lt;li&gt;结构的长度不等于各成员长度之和, 因为不同的对象有不同的&lt;strong&gt;对齐&lt;/strong&gt;要求, 故, 结构中可能会出现未命名的空穴&lt;/li&gt;
&lt;li&gt;一个包含其自身实例的结构是非法的, 但是声明&lt;code&gt;struct tnode \*left;&lt;/code&gt;是合法的, 它将left声明为指向tnode的指针, 而不是tnode实例本身&lt;/li&gt;
&lt;li&gt;&lt;code&gt;散列查找&lt;/code&gt; - 将输入的名字转换为一个小的非负整数, 该整数随后将作为一个指针数组的下标. 数组的每个元素指向某个链表的表头, 链表中的各个块用于描述具有该散列值的名字. 若没有名字散列到该值, 则数组元素的值为NULL&lt;/li&gt;
&lt;li&gt;c语言提供了一个称为&lt;code&gt;typedef&lt;/code&gt;的功能, 用来建立新的数据类型名.&lt;/li&gt;
&lt;li&gt;从任何意义上讲, typedef声明没有创建一个新的类型, 只是为某个已存在的类型增加一个新的名称而已. typedef声明也没有增加任何新的语义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typedef int (\*PEI)(char \*, char \*)&lt;/code&gt;该语句定义了类型PEI是“一个指向函数的指针, 该函数具有两个char *类型的参数, 返回值类型为int”&lt;/li&gt;
&lt;li&gt;除了表达方式更简洁外名, 使用typedef还有另外两个重要原因. 首先, 它可以使程序参数化, 以提高程序的可移植性, 如果typedef声明的数据类型与机器有关, 那么, 当程序移植到其他机器上时, 只需改变typedef类型定义即可. 一个经常用到的情况是, 对于各种不同大小的整型值来说, 都使用typedef定义的类型名, 然后分别为各个不同的宿主机选择一组合适的short, int和long类型大小. 其次, typedef为程序提供更好的说明性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;联合(union)&lt;/code&gt;是可以在(不同时刻)保存不同类型和长度的对象的变量, 编译器负责跟踪对象的长度和对齐要求. 联合提供了一种方法, 以在单块存储区中管理不同类型的数据, 而不需要在程序中嵌入任何同机器相关的信息&lt;/li&gt;
&lt;li&gt;联合的目的是: 一个变量可以合法地保存多种数据类型中任何一种类型的对象&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;union&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; u_tag{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ival;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    float&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fval;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    char&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sval;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} u;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;变量u必须足够大, 以保存这3种类型中最大的一种. 这些类型中的任何一种类型的对象都可赋给u, 且可使用在随后的表达式中, 但必须保证是一致的: 读取的类型必须是最近一次存入的类型&lt;/li&gt;
&lt;li&gt;联合可以用在结构和数组中, 反之亦然. 访问结构中的联合(或反之)的某一成员的表达法和嵌套结构相同&lt;/li&gt;
&lt;li&gt;实际上, 联合就是一个结构, 它的所有成员相对于基地址的偏移量都是0, 此结构空间要大到足够容纳最“宽”的成员, 且, 对齐方式要适合于联合中的所有类型的成员. 对联合允许的操作与对结构允许的操作相同: 作为一个整体单元进行赋值与复制, 取地址及访问其中一个成员&lt;/li&gt;
&lt;li&gt;联合只能用其第一个成员类型的值进行初始化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;位字段&lt;/code&gt;是“字”中相邻位的集合. “字”是单个的存储单元:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; is_keyword : &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; is_extern : &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    unsigned&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; is_static : &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;冒号后的数字表示字段的宽度(用二进制位数表示), 单个字段的引用方式与其他结构成员相同&lt;/li&gt;
&lt;li&gt;字段可以不命名, 无名字段(只有一个冒号和宽度)起填充作用, 特殊宽度0可以用来强制在下一个字边界上对齐&lt;/li&gt;
&lt;li&gt;为了移植方便, 需要显式声明类型是signed类型还是unsigned类型.&lt;/li&gt;
&lt;li&gt;字段不是数组, 并且没有地址, 因此不能对其使用&amp;amp;运算符&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-7-输入与输出&quot;&gt;Chapter 7 输入与输出&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ANSI标准精确地定义了库函数, 所以, 在任何可以使用c语言的系统中都有库函数的兼容形式. 如果程序的系统交互部分仅仅使用了标准库提供的功能, 则可以不经修改地从一个系统移植到另一个系统中&lt;/li&gt;
&lt;li&gt;文本流由一系列行组成, 每一行的结尾是一个换行符(\n)&lt;/li&gt;
&lt;li&gt;在许多环境中, 用符号&lt;code&gt;&amp;lt;&lt;/code&gt;来实现&lt;strong&gt;输入重定向&lt;/strong&gt;, 它将键盘输入替换为文件输入&lt;/li&gt;
&lt;li&gt;当文件名用&lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;括起来时, 预处理器将在由具体实现定义的有关位置中查找指定的文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getchar&lt;/code&gt;函数从标准输入中一次读取一个字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;putchar(c)&lt;/code&gt;函数将字符c送至标准输出, 在默认情况下, 标准输出为屏幕显示&lt;/li&gt;
&lt;li&gt;头文件&amp;lt;stdio.h&amp;gt;中的getchar和putchar以及&amp;lt;ctype.h&amp;gt;中的tolower一般都是宏, 这样就避免了对每个字符进行函数调用的开销&lt;/li&gt;
&lt;li&gt;输出函数printf将内部数值转换为字符的形式, 在输出格式的控制下, 将其参数进行转换和格式化, 并在标准输出设备上打印出来, 返回打印的字数&lt;/li&gt;
&lt;li&gt;格式字符串包含2种类型的对象: 普通字符和转换说明. 在输出时, 普通字符将原样复制到输出流中, 而转换说明并不直接输出到输出流中, 而是用于控制printf中参数的转换和打印. 每个转换说明都由一个百分号字符开始, 以一个转换字符结束&lt;/li&gt;
&lt;li&gt;在转换说明中, 宽度和精度可以用星号表示, 这时, 宽度和精度的值通过转换下一个参数来计算&lt;/li&gt;
&lt;li&gt;sprintf的执行和转换和printf相同, 但它将输出保存到一个字符串中, 而不是输出到标准输出中&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scanf&lt;/code&gt;从标准输入中读取字符序列, 按照format中的格式说明对字符序列进行解释, 并将结果保存到其余的参数中, 其它所有参数都必须是指针, 用于指定经格式转换后的相应输入保存的位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sscanf&lt;/code&gt;从一个字符串中读取字符序列&lt;/li&gt;
&lt;li&gt;scanf与sscanf的所有参数必须是指针&lt;/li&gt;
&lt;li&gt;转换说明控制下一个输入字段的转换. 一般而言, 转换结果存放在相应的参数指向的变量中. 但若转换说明中有赋值禁止字符*, 则跳过该输入字段, 不进行赋值. 输入字段定义为一个不包含空白符的字符串, 其边界定义为到下一个空白符或达到指定的字段宽度&lt;/li&gt;
&lt;li&gt;字符字面值也可以出现在scanf的格式串中, 它们必须与输入中相同的字符匹配, 如要读入形如mm/dd/yy的数据, 可用&lt;code&gt;scanf(&quot;%d/%d/%d&quot;, &amp;amp;month, &amp;amp;day, &amp;amp;year)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;scanf函数可以与其他输入函数混合使用, 无论调用哪个输入函数, 下一个输入函数的调用将从scanf没有读取的第一个字符处开始读取数据&lt;/li&gt;
&lt;li&gt;标准输入和输出是操作系统自动提供给程序访问的&lt;/li&gt;
&lt;li&gt;文件访问: 在读写一个文件之前, 必须通过库函数&lt;code&gt;fopen&lt;/code&gt;打开该文件, 该函数用外部文件名与操作系统进行某些必要的连接和通信, 并返回一个可用于文件读写操作的指针, 称为&lt;strong&gt;文件指针&lt;/strong&gt;, 它指向一个包含文件的结构, 包括: 缓冲区的位置, 缓冲区中当前字符的位置, 文件的读写状态, 是否出错, 是否已经到达文件结尾等等. 在&amp;lt;stdio.h&amp;gt;中, 用结构&lt;code&gt;FILE&lt;/code&gt;表示&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int getc(FILE *fp)&lt;/code&gt; - 从文件中返回一个字符, 需要知道文件指针, 以确定对哪个文件执行操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int putc(int c, FILE *fp)&lt;/code&gt; - 将字符c写入到fp指向的文件中. putc与getc是宏而不是函数&lt;/li&gt;
&lt;li&gt;启动c语言程序时, 操作系统环境负责打开3个文件, 并将这3个文件的指针提供给该程序. 这3个文件分别是&lt;strong&gt;标准输入&lt;/strong&gt;, &lt;strong&gt;标准输出&lt;/strong&gt;, &lt;strong&gt;标准错误&lt;/strong&gt;, 相应的文件指针分别是stdin, stdout, stderr, 均在&amp;lt;stdio.h&amp;gt;中声明. 在大多数环境中, stdin指向键盘, stdout和stderr指向显示器. stdin和stdout可以被重定向到文件或管道(&amp;lt;, &amp;gt;, |)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fclose&lt;/code&gt;执行与fopen相反的操作, 断开由fopen函数建立的文件指针和外部名之间的连接, 并释放文件指针以供其他文件使用. 此外, fclose会将缓冲区中由putc正在收集的输出写到文件中, 当程序正常终止时, 程序会自动为每个打开的文件调用fclose函数&lt;/li&gt;
&lt;li&gt;即使对标准输出stdout进行了重定向, 写到stderr中的输出通常也会显示在屏幕上&lt;/li&gt;
&lt;li&gt;使用标准库函数&lt;code&gt;exit&lt;/code&gt;, 当函数被调用时, 它将终止调用程序的运行. exit为每个已打开的输出调用fclose函数, 以将缓冲区的所有输出写到相应的文件中&lt;/li&gt;
&lt;li&gt;在主程序main中, 语句&lt;code&gt;return expr&lt;/code&gt;等价于&lt;code&gt;exit(expr)&lt;/code&gt;. 但使用exit有一个优点, 它可以从其他函数中调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;char *fgets(char *line, int maxline, FILE *fp)&lt;/code&gt; - 函数从fp指向的文件中读取下一个输入行(包括换行符), 将其存放在字符数组line中, 最多可读取maxline - 1个字符. 读取的行将以&lt;code&gt;\0&lt;/code&gt;结尾保存到数组中&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int fputs(char *line, FILE *fp)&lt;/code&gt; - 将一个字符串(不需要包含换符)写入到文件中&lt;/li&gt;
&lt;li&gt;库函数gets和puts的功能与fgets和fputs类似, 但它们对stdin和stdout进行操作. gets函数在读取字符串时将删除结尾的换行符, 而puts函数在写入字符串时将在结尾添加一个换行符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int ungetc(int c, FILE *fp)&lt;/code&gt; - 函数将字符c写回到文件中, 并返回c. 每个文件只能接收一个写回字符, ungetc可以与任何一个输入函数一起使用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system(char *s)&lt;/code&gt;执行包含在字符串s中的命令&lt;/li&gt;
&lt;li&gt;函数malloc和calloc用于动态地分配存储块. 用&lt;code&gt;free(p)&lt;/code&gt;函数释放p指向的存储空间, p是此前通过malloc或calloc函数得到的指针. 存储空间的释放顺序没有限制, 但是, 如果释放一个不是通过调用malloc或calloc函数得到的指针所指向的存储空间, 将是一个严重错误
.&lt;/li&gt;
&lt;li&gt;使用已经释放的存储空间是错误的, 正确的处理方法好似, 在释放项目之前先将一切必要的信息保存起来:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; head; p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;next)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    free&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p);&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                           /* 错误 */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; head; p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; NULL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; p &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; q){&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    q &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p-&amp;gt;next;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    free&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(p);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;函数rand()生成介于0和RAND_MAX之间的伪随机整数序列&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#define frand() ((double) rand() / (RAND_MAX + 1.0))&lt;/code&gt; - 生成[0, 1)的随机浮点数&lt;/li&gt;
&lt;li&gt;函数srand(unsigned)`设置rand函数的种子数&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;chapter-8-unix系统接口&quot;&gt;Chapter 8 UNIX系统接口&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;unix系统通过一系列&lt;strong&gt;系统调用&lt;/strong&gt;提供服务, 这些系统调用实际上是操作系统内的函数, 可被用户程序调用. 借助系统调用, 可获得最高的效率, 或者访问标准库没有的某些功能&lt;/li&gt;
&lt;li&gt;在unix系统中, 所有外围设备都被视为文件系统中的文件, 因此, 所有的输入输出都要通过读文件或写文件完成, 即通过一个单一接口就可以处理外围设备与程序之间的所有通信&lt;/li&gt;
&lt;li&gt;通常情况下, 在读写文件之前, 必须先将这个意图通知操作系统, 该过层称为&lt;strong&gt;打开文件&lt;/strong&gt;. 如果一切正常, OS将返回一个小的非负整数, 称为&lt;strong&gt;文件描述符&lt;/strong&gt;, 任何时候对文件的I/O都是通过文件描述符标识文件, 而不是通过文件名标识文件. 系统负责维护已打开的文件的所有信息, 用户程序只能通过文件描述符引用文件&lt;/li&gt;
&lt;li&gt;用shell运行一个程序时, 将打开3个文件, 对应的文件描述符为0, 1, 2, 依次表示stdin, stdout, stderr. 如果程序从文件0中读, 对1和2进行写, 就可以进行I/O而不必关心打开文件的问题&lt;/li&gt;
&lt;li&gt;通过&amp;lt;或&amp;gt;重定向程序的I/O时, shell将文件描述符的0和1的默认赋值改变为指定的文件. 在任何情况下, 文件赋值的改变都不是由程序完成的, 而是由shell完成的. 只要程序使用文件0作为输入, 1和2作为输出, 就不需要知道输入从何而来, 又输出到哪去&lt;/li&gt;
&lt;li&gt;I/O通过&lt;code&gt;read&lt;/code&gt;和&lt;code&gt;write&lt;/code&gt;系统调用实现. 在c语言程序中, 通过函数read和write访问这2个系统调用. 在一次调用中, 读出或写入的数据的字节数可以为任意大小, 最常用的值为1, 或是类似于4096这样的与外围设备的物理块大小相应的值&lt;/li&gt;
&lt;li&gt;除了默认的标准输入, 标准输出和标准错误文件外, 其他文件都必须在读写之前显式地打开. 系统调用open和creat实现该功能&lt;/li&gt;
&lt;li&gt;open与fopen相似, 但前者返回一个文件描述符, 仅仅是一个int类型的数值, 后者返回一个文件指针&lt;/li&gt;
&lt;li&gt;在unix文件系统中, 每个文件对应一个9比特的权限信息, 分别控制文件的所有者, 所有者组和其他成员对文件的读, 写和执行访问.&lt;/li&gt;
&lt;li&gt;一个程序同时打开的文件数是有限制的(通常为20). 相应的, 如果一个程序需要同时处理多个文件, 必须重用文件描述符. 函数close(int fd)用来断开文件描述符与已打开文件之间的连接, 并释放此文件的文件描述符, 以供其他文件使用. close函数与标准库中的fclose相对应, 但不需要清洗(flush)缓冲区. 若程序通过exit函数退出或从主程序中返回, 所有打开的程序将被关闭&lt;/li&gt;
&lt;li&gt;函数unlink(char *name)将文件name从文件系统中删除, 它对应的标准库函数为remove&lt;/li&gt;
&lt;li&gt;系统调用iseek可以在文件中任意移动位置而不实际读写任何数据.\
&lt;code&gt;long lseek(int fd, long offset, int origin)&lt;/code&gt; - 将文件描述符为fd的文件的当前位置设置为offset, offset是相对于origin指定的位置而言的. 随后进行的读写操作将从此位置开始. origin的值可以为0, 1, 2, 分别用于指定offset从文件开始, 从当前位置或从文件结束处开始&lt;/li&gt;
&lt;li&gt;使用lssek系统调用, 可以将文件视为一个大的数组, 其代价是访问速度会慢一些. lseek返回的long类型的值表示文件的新位置&lt;/li&gt;
&lt;li&gt;标准库中的文件不是通过文件描述符描述的, 而是使用文件指针描述的. 文件指针是一个指向包含文件各种信息的结构的指针, 该结构包括: 一个指向缓冲区的指针, 通过它可以一次读入文件中的一大块内容; 一个记录缓冲区中剩余的字符数的计数器; 一个指向缓冲区下一个字符的指针; 文件描述符; 描述读写模式的标识; 描述错误状态的标识等&lt;/li&gt;
&lt;li&gt;描述文件的数据结构包含在头文件&amp;lt;stdio.h&amp;gt;中, 任何需要使用标识输入/输出库中函数的程序都必须在源文件中包含这个头文件&lt;/li&gt;
&lt;li&gt;长语句可用反斜杆分成多行表示&lt;/li&gt;
&lt;li&gt;通常还需要对文件系统执行另一种操作, 以获得文件的有关信息, 而不是读取文件的具体内容. 目录列表程序便是一个例子(ls)&lt;/li&gt;
&lt;li&gt;在unix中, 目录就是文件, 它包含一个文件名列表和一些指示文件位置的信息. “位置”是一个指向其他表的索引. 文件的inode是存放除文件名意外的所有文件信息的地方. 目录项通常仅包含两个条目: 文件名和inode&lt;/li&gt;
&lt;li&gt;为使程序的可移植性更像, 有时候需要分离不可移植部分分别处理&lt;/li&gt;
&lt;li&gt;许多程序并不是“系统程序”, 它们仅仅使用由OS维护的信息. 对于这样的程序, 很重要的一点是, 信息的表示仅出现在标准头文件中, 使用它们的程序只需要在文件中包含这些头文件即可, 而不需要包含相应的声明. 其次, 有可能为系统相关的对象创建一个与系统无关的接口, 标准库中的函数就是一个很好的例子&lt;/li&gt;
&lt;li&gt;通过一种与系统无关的方式编写与系统有关的代码&lt;/li&gt;
&lt;li&gt;malloc并不是从一个编译时就确定的固定大小的数组中分配存储空间, 而是在需要时向OS申请空间. malloc管理的空间不一定是连续的, 空闲存储空间以空闲块链表的方式组织, 每个块包含一个长度, 一个指向下一块的指针, 一个指向自身的存储空间的指针. 这些块按照存储地址升序组织, 最后一块指向第一块&lt;/li&gt;
&lt;li&gt;当有申请时, malloc将扫描空闲块链表, 直到找到一个足够大的块为止, 称为“首次适应(first fit)”; 寻找满足条件的最小块, 称为“最佳适应(best fit)”&lt;/li&gt;
&lt;li&gt;释放的过程也是首先搜索空闲块链表, 以找到可以插入或被释放的合适位置. 空闲块链表以地址的递增顺序链接在一起, 很容易判断相邻的块是否空闲&lt;/li&gt;
&lt;li&gt;空闲块包含一个指向链表中下一个块的指针, 一个块大小的记录, 一个指向空闲空间本身的指针. 位于块开始处的控制信息称为“头部”, 为了简化块的对齐, 所有块的大小必须以头部大小的整数倍, 且头部已正确对齐&lt;/li&gt;
&lt;li&gt;在malloc函数中, 请求的长度(以字符为单位)将被舍入, 以保证它是头部大小的整数倍, 实际分配的块将多包含一个单元, 用于头部本身. 实际分配的块的大小将被记录在头部的size字段中, malloc函数返回的指针指向空闲空间, 而非块的头部, 用户可对获得的存储空间进行任何操作. 但, 如果在分配的存储空间之外写入数据, 可能会破坏块链表&lt;/li&gt;
&lt;li&gt;向系统请求存储空间是一个开销很大的操作&lt;/li&gt;
&lt;li&gt;类型的强制转换使得指针的转换是显式进行的, 这样甚至可以处理设计不够好的系统接口问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-end&quot;&gt;The End&lt;/h2&gt;
</content:encoded><category>笔记</category><category>C</category></item><item><title>这不是一篇爬虫教程</title><link>https://lifelonglove.cn/articles/not-a-tutorial-on-crawler/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/not-a-tutorial-on-crawler/</guid><description>从网络、请求、解析和数据保存的全局视角拆解爬虫，而不拘泥于某段示例代码；重点是先理解资源在哪里、怎样可靠取得，再选择合适工具。</description><pubDate>Thu, 02 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;白马非马&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;郑重声明: 这不是一篇爬虫教程! 但是, 目的是学习爬虫的看官, 不妨驻足, 也许开卷有益.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;既然不是爬虫的教程, 还能写些什么呢? 我的本意是给诸位理一理写爬虫的思路, 给你一个大局观.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;首先, 问一个问题: 爬虫(crawler)是什么, 或者说, 爬虫是做什么的?&lt;/p&gt;
&lt;p&gt;诸位可能想写或已经写过一些爬虫程序了, 应该有这样的感受: &lt;strong&gt;互联网, 就是一张巨大无比的“网”, 上面挂着无数的资源.&lt;/strong&gt; 而我们所谓的爬虫, 就是负责将我们想要的资源采集到本地的自动化程序, 由于采集的过程与蜘蛛在网上到处爬很像, 因此得名“网络蜘蛛”, 也称“网络爬虫”. 这里我用到一个词——&lt;strong&gt;自动化&lt;/strong&gt;, 自动是相对于手动而言的. 当我浏览网页时, 看到一张很喜欢的图片, 想把它保存到本地, 我可以简单地“右击”-“save image as…”; 但是有100张甚至更多这样的图片, 手动下载就不太合适了. 这种“脏活累活”就可以交给爬虫来做. “喂, 爬虫老弟, &lt;a href=&quot;https://xxx.xxx.xxx&quot;&gt;https://xxx.xxx.xxx&lt;/a&gt; 这个网页上的图片麻烦你给我全部下载到本地”, 然后你就可以美美地去看“权力的游戏”最新一集了. 因此, 爬虫有时也叫“网络机器人”.&lt;/p&gt;
&lt;p&gt;说了这么多, 讲白了, 爬虫就是一个自动化采集工具.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;插入语: 我越来越觉得, 编程的终极目标之一就是实现各种自动化.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再来讲一个很重要的概念——网络. 我在另一篇博客“Python之socket初见”中提到&lt;code&gt;网络通信, 归根结底是进程间通信&lt;/code&gt;. 同样地, 只要你愿意, 完全可以将网络看作是计算机的另一块无限容量的硬盘. 想想如今的各种网盘服务, 不就是最好的证明吗? 当然, 我所讲的“将网络看作硬盘”并不局限于网盘的概念. 试想一下, 用浏览器打开一张网页, 与用文本编辑器打开一个文本文件又有多大差别呢? 网速够快, 加载够快的话, 就仿佛页面上的所有资源都是从本地加载的一样. (如果你与我一样, 还在用着机械硬盘, 用word打开一个文本文档, 不一定比用Google Docs打开一个网盘上的文本文档更快.)&lt;/p&gt;
&lt;p&gt;同样地, 使用爬虫, 也可以将网络看作远端的硬盘来看待. 无怪乎, &lt;code&gt;urlopen()&lt;/code&gt;与&lt;code&gt;open()&lt;/code&gt;长得这么像, 用起来也这么像:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;a.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   f.read()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Hello, World&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://kissg.me&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; f:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   f.read()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&amp;lt;!DOCTYPE html&amp;gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;预备知识介绍完毕, 下面来看看“爬虫”的几种不同实现.&lt;/p&gt;
&lt;h3 id=&quot;api采集数据&quot;&gt;API采集数据&lt;/h3&gt;
&lt;p&gt;你可能不认同: 这也能算爬虫? 但是, 假设你要采集的是一个天气网站的某地天气, 而它恰好提供了API, 于是你通过调用API马上就获得了需要的数据, 这难道不算数据采集, 不算爬虫吗? 非要千辛万苦爬网页, 分析文档获取数据才叫爬虫? 爬虫的根本目的是采集数据或者爬取资源, 至于如何实现, 重要吗? 还是重要的(- -!), 有简单实用的方法, 没道理不用对吧?&lt;/p&gt;
&lt;p&gt;使用API来采集数据的好处很明显, 在上文已有所体现:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用简单&lt;/li&gt;
&lt;li&gt;数据使用标准格式(比如JSON), 不同开发者, 不同架构, 不同语言都可使用, 且易使用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是缺点也很明显:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不存在!&lt;/li&gt;
&lt;li&gt;请求内容和次数有限制, 无法满足需求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于通过API采集数据就讲这么多, 我就想告诉诸位: 原来还有一种这么简单的数据采集方式!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &quot;Python网络数据采集&quot;中的一个例子, 有删改&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; myIpAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;115.236.9.89&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://freegeoip.net/json/&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; myIpAddress)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://freegeoip.net/json/&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; myIpAddress).read().decode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; responseJson &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; json.loads(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(responseJson.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;country_code&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;CN&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;非api数据采集&quot;&gt;非API数据采集&lt;/h3&gt;
&lt;p&gt;我将所有不是通过API获取数据的爬虫统称为一类, 意味着想要从其中解析出目标数据, 还得下一番功夫.&lt;/p&gt;
&lt;p&gt;前文提到过, 爬虫的自动化是相对手动而言的. 这手动的过程就是我们在浏览器内的各种操作: 打开链接, 右击保存, 复制粘贴… 因此, 在我看来, 爬虫的终极目标就是模拟浏览器, 或者说模拟浏览器的行为. &lt;a href=&quot;https://zh.wikipedia.org/wiki/%E5%9B%BE%E7%81%B5%E6%B5%8B%E8%AF%95&quot;&gt;图灵测试&lt;/a&gt;的目的是测试计算机能否表现出与人等价的无法区分的智能; 而现在许多网站都有反爬虫的手段, 会判断接入的是爬虫, 还是网络浏览器, 此时, 如果用赤果果的爬虫去访问网站, 也许你的IP立马就被封了, 这就偷鸡不成蚀把米了. 因此, 写爬虫的第一步, 就是&lt;code&gt;爬虫浏览器化&lt;/code&gt;(这是我自己突发奇想的一个词, 能明白就行).&lt;/p&gt;
&lt;p&gt;当浏览器向服务器发起请求时, 都会带有请求头部(Request Headers). 请求头部字段&lt;code&gt;User-Agent&lt;/code&gt;会带有浏览器的信息, 比如我的是这样的:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;user-agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当爬虫使用urllib标准库, 却没有设置&lt;code&gt;User-Agent&lt;/code&gt;时, 默认使用&lt;code&gt;Python-urllib/3.X&lt;/code&gt;(X取决使用的Python版本), 这不一下子就暴露了嘛.&lt;/p&gt;
&lt;p&gt;因此&lt;code&gt;爬虫浏览器化&lt;/code&gt;, 一个简单有效的方法就是为请求设置头部, 而其中最重要的可能要数&lt;code&gt;User-Agent&lt;/code&gt;了. 某些网站可能还会检查&lt;code&gt;Referer&lt;/code&gt;是否指向网站本身, 如果不是, 网站可能不会响应. 当然, 你可以通过Chrome浏览器的开发者工具(或其他工具)查看&lt;code&gt;Request Headers&lt;/code&gt;, 根据实际情况设置需要的头部字段.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;爬虫浏览器化&lt;/code&gt;的另一个要点是模拟登录与处理cookie. 如果要采集的数据只有在登录之后才能看到(如知乎, 你只有登录了才能看到内容), 这一点就显得至关重要了.&lt;/p&gt;
&lt;p&gt;针对上述2点, 用第三库&lt;code&gt;Requests&lt;/code&gt;, 很容易就实现了请求头设置, 表单发送, cookie跟踪等, 下面是简单的使用方法:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 代码依旧来自&quot;Python网络数据采集&quot;, 有删改&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bs4 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BeautifulSoup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 创建一个会话对象, 可跨请求使用参数, 并能保持通过该会话实例创建的请求的cookies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;session &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.Session()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;headers &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Users-Agent&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;kissg@engine&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 胡乱设的User-Agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# www.whatismybrowser.com 是个不错的开发者网站,值得收藏, 可查看详细浏览器信息&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;url1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://www.whatismybrowser.com/developers/what-http-headers-is-my-browser-sending&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response1 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session.get(url1, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;headers)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bsObj &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BeautifulSoup(response1.text)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(bsObj.find(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;table&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,{&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;table-striped&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).get_text)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &amp;lt;th&amp;gt;USER_AGENT&amp;lt;/th&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &amp;lt;td&amp;gt;kissg@engine&amp;lt;/td&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; session.post(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://pythonscraping.com/pages/cookies/welcome.php&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(response2.cookies.get_dict())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# {&apos;loggedin&apos;: &apos;1&apos;, &apos;username&apos;: &apos;username&apos;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有时候, 过快的(非人的)爬取速度, 可能也会招致网站的怀疑. 因此, 如果对于采集速度不是要求太高, 可适当放缓(比如加个time.sleep()), 不仅更“人性化”了, 更减小了目标网站的负担, 何乐而不为? 我很喜欢“Python网络数据采集”书中的一句话:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;真的没有理由去伤害别人的网站&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在真正进行采集之前, 还有一个可选的步骤是&lt;code&gt;设置代理&lt;/code&gt;, 一般这样做的目的是避免IP地址被封杀, 而一个小小的副作用是, 网速会变慢, 这似乎挺符合上述放缓采集速度的要求的, 一举两得? 推荐配合&lt;a href=&quot;https://www.torproject.org/&quot;&gt;Tor&lt;/a&gt;使用, 以下是&lt;code&gt;Tor&lt;/code&gt;与&lt;code&gt;PysSocks&lt;/code&gt;搭配的一个例子:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socks  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 导入了PySocks模块&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;socks.set_default_proxy(socks.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCKS5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9150&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# tor 服务必须运行在9150端口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;socket.socket &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socks.socksocket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://icanhazip.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).read())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 77.247.181.162, 通过 freegeoip.net 验证, 这是荷兰(Netherlands)的一个IP地址.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# freegeoio.net 也是一个值得收藏的网站&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我测试了下, 打开Tor浏览器(启用Tor服务)之后, 不管运行多少次爬虫, IP地址都没有再发生变化. 重启Tor浏览器(重启服务)或点击&lt;code&gt;New Identity&lt;/code&gt;之后, 再运行爬虫, IP地址就刷新了. 如果希望隔几分钟就重置代理, 写一段代码重启Tor浏览器即可. 对此我没有更好的解决办法, 如果你有更好的办法, 请不吝赐教.&lt;/p&gt;
&lt;p&gt;关于爬虫的前期准备, 我知道的就这么多了. 下面, 是时候让爬虫跑(爬)起来了!&lt;/p&gt;
&lt;p&gt;我一直觉得写爬虫, 是有套路的, 无非就是: &lt;strong&gt;给定入口, 给定路线, 给定一些规则, 然后爬虫从入口爬入互联网, 沿着路线爬行, 将符合规则的资源采集到本地, 并按规则发现新的路线, 采集新的符合规则的资源…&lt;/strong&gt; 我突然觉得, 爬虫就跟&lt;strong&gt;红警的采矿车&lt;/strong&gt;很像——采矿车踩到矿之后, 运回精炼厂, 之后接着出去采矿, 当一处矿采完之后, 还会自己探索未知领域发现新矿.&lt;/p&gt;
&lt;p&gt;你可能听说过&lt;code&gt;分布式爬虫&lt;/code&gt;, 可以想象, 那就是在一张网上, 撒了更多的爬虫, 或者一张地图上, 同时有多辆采矿车在采矿.&lt;/p&gt;
&lt;p&gt;下面是爬虫的一般性框架(伪代码), 根据&lt;a href=&quot;https://www.zhihu.com/question/20899988&quot;&gt;知乎上的回答&lt;/a&gt;, 有删改:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;entrypoint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://kissg.me&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 入口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;seen &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 已知的网页, 控制爬虫不重复采集, 一般用set, 利用其互异与速查的特性&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wait_to_crawl &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; queue()   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 等待爬取的网页, 对应上述的&quot;路线&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;seen.insert(entrypoint)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wait_to_crawl.put(entrypoint)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wait_to_crawl.size &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        current_url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wait_to_crawl.get()  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# get 方法从队列中取出头元素, 带删除效果&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        store(current_url)                 &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 储存页面&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; next_url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; extract_urls(current_url):  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 发现新的链接&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; next_url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;not&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; seen:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                seen.insert(next_url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                wait_to_crawl.put(next_url)         &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 用新发现的链接扩展路线&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于简单的爬虫, &lt;code&gt;urlopen()&lt;/code&gt;函数就够用了. 用它打开一个url(可以是str, 或&lt;code&gt;Request&lt;/code&gt;对象), 返回一个&lt;code&gt;Response&lt;/code&gt;对象, 这是一个类文本(file-like)对象, 可读取, 在此基础上就可以进行数据采集了. 若还对&lt;code&gt;urlopen()&lt;/code&gt;函数使用了&lt;code&gt;data&lt;/code&gt;关键字参数, 请求方法将从默认的&lt;code&gt;GET&lt;/code&gt;变为&lt;code&gt;POST&lt;/code&gt;, 也就是说, 用&lt;code&gt;urlopen()&lt;/code&gt;也能实现表单提交. 另, 若使用&lt;code&gt;Request&lt;/code&gt;对象, 还能设置请求头部与代理. 此处不再多说, 具体使用还请参考&lt;a href=&quot;https://docs.python.org/3/library/urllib.request.html&quot;&gt;文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;既然标准&lt;code&gt;urllib&lt;/code&gt;库已经能做这么多事情了, 为什么还需要&lt;code&gt;Requests&lt;/code&gt;呢? 因为相对于&lt;code&gt;urllib&lt;/code&gt;, &lt;code&gt;Requests&lt;/code&gt;更高级, &lt;code&gt;Requests&lt;/code&gt;之于&lt;code&gt;urllib&lt;/code&gt;就好比&lt;code&gt;Python&lt;/code&gt;之于&lt;code&gt;C语言&lt;/code&gt;一样, 抽象层级更高, 更易用, 上文的cookie处理就是很好的证明. 三言两语说不尽, 详情还请看文档&lt;a href=&quot;https://requests.readthedocs.io/en/master/&quot;&gt;Requests: HTTP for Humans&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;虽然&lt;code&gt;urllib&lt;/code&gt;很强大, &lt;code&gt;Requests&lt;/code&gt;更强大, 但&lt;strong&gt;道高一尺, 魔高一丈&lt;/strong&gt;, 面对动态网页的时候, 它们也只能低下高贵的头颅, 表示无能为力. 所谓动态网页, 就是显示的内容(页面元素)会随着时间, 环境或用户操作而发生变化, 可分为服务器端与客户端的, 此处专指客户端动态网页, 因为服务器端的行为, 我们控制不了. 客户端动态网页使用以&lt;code&gt;JavaScript&lt;/code&gt;为代表的客户端脚本语言来控制网页的展示, 具体表现就是&lt;strong&gt;你在浏览器中看到的网内容, 与用爬虫采集的内容不一样&lt;/strong&gt;. 这是因为爬虫并不能执行&lt;code&gt;JavaScript&lt;/code&gt;代码, 也就不能使页面产生变化. 解决办法是, 使用&lt;code&gt;selenium&lt;/code&gt; + &lt;code&gt;phantomjs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;selenium&lt;/code&gt;最初是一个为网站自动化测试开发的工具, 它能直接控制浏览器的行为, 可模拟用户的各种操作, 比如键盘输入, 鼠标点击等. 这就为在爬虫里执行&lt;code&gt;JavaScript&lt;/code&gt;代码提供了可能. 更多selenium的说明, 请看&lt;a href=&quot;http://www.seleniumhq.org/&quot;&gt;官网&lt;/a&gt;; python的selenium库文档, 请看&lt;a href=&quot;http://seleniumhq.github.io/selenium/docs/api/py/index.html&quot;&gt;这里&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;phantomjs&lt;/code&gt;则是无头的(headless)浏览器, 它会将网站加载到内存并执行页面上的&lt;code&gt;JavaScript&lt;/code&gt;代码, 但不会向用户展示图形界面, 即你看不到实体的浏览器. &lt;a href=&quot;http://phantomjs.org/&quot;&gt;官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;因此, &lt;code&gt;selenium&lt;/code&gt; + &lt;code&gt;phantomjs&lt;/code&gt;, 实际就是&lt;strong&gt;将一个可控的浏览器装入了爬虫程序&lt;/strong&gt;. 与浏览器无异, 它可以设置&lt;code&gt;request headers&lt;/code&gt;, 处理表单, 跟踪cookie, 执行JavaScript… 你可以想象, 这是一只变种的自带浏览器的巨型爬虫!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 沿用&quot;Python网络数据采集&quot;一书的代码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selenium &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; webdriver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 将webdriver.PhantomJS()改成webdriver.Chrome(), 就打开了一个Chrome浏览器&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;driver &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; webdriver.PhantomJS(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;executable_path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/home/kissg/Tools/phantomjs/bin/phantomjs&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;driver.get(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://pythonscraping.com/pages/javascript/ajaxDemo.html&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 打开url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;time.sleep(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 延迟3s. 该动态网页使用ajax设置了一个2s的延迟, 2s之后页面会发生变化&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(driver.find_element_by_id(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;content&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).text)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;driver.close()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前面所做的这一切, 只是采集了网页, 为了获得需求的数据, 还需要进行数据提取. 当然, 如果爬虫只是用来下载图片等资源, 就没这个必要了.&lt;/p&gt;
&lt;p&gt;一般情况下, 采集到的都是html文档, 或者xml文档. 因此, 可以用&lt;code&gt;BeautifulSoup&lt;/code&gt;和&lt;code&gt;正则表达式(regex)&lt;/code&gt;进行数据提取.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BeautifulSoup&lt;/code&gt;是一个可以从html或xml文档中提取数据的Python第三方库. 它的强大之处在于将html或xml文档解析成一个树, 可以很方便地进行导航和搜索, 从而可以从原始文档中提取出原始数据. 使用&lt;code&gt;BeautifulSoup&lt;/code&gt;, 最好是有一点html(xml)和css知识, 不用多, 会导航和搜索就可以了. &lt;a href=&quot;https://www.crummy.com/software/BeautifulSoup/bs4/doc/&quot;&gt;文档详情&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;regex&lt;/code&gt;就不说了吧, 一两句真的说不清. 我之前试过, 分别用&lt;code&gt;regex&lt;/code&gt;和&lt;code&gt;BeautifulSoup&lt;/code&gt;提取数据, &lt;code&gt;regex&lt;/code&gt;效率更高, 但&lt;code&gt;BeautifulSoup&lt;/code&gt;胜在简单. 学习&lt;code&gt;regex&lt;/code&gt;的话, 建议看一遍&lt;a href=&quot;https://docs.python.org/3/library/re.html&quot;&gt;re标准库&lt;/a&gt;, 很详细.&lt;/p&gt;
&lt;p&gt;至于如何对已获得的原始数据进行加工处理, 我只能说, 请便~ 对于不同的数据需求, 可以做统计, 或数据融合, 或者万事休提, 先存到数据库.&lt;/p&gt;
&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;
&lt;p&gt;虽然本文以python为例讲解了写爬虫程序的一般思路, 但个人认为许多内容是放之四海皆准的, 我很努力地往这个方向上靠了.&lt;/p&gt;
&lt;p&gt;由于本文并不是一篇真正意义上的爬虫教程, 许多内容都是点到即止, 目的就是为了告诉各位: 诶, 有这个东西, 可以这样处理. 至于如何使用, 看看文档, 或者看看网上其他人的教程吧.(建议过一遍文档)&lt;/p&gt;
&lt;p&gt;简单地回顾下.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本文先是介绍了我所理解的爬虫和网络的概念;&lt;/li&gt;
&lt;li&gt;然后提供了利用API进行数据采集的思路;&lt;/li&gt;
&lt;li&gt;再然后按编写爬虫程序的一般过程分别讲了前期准备, 采集方式和后期处理的一些内容:&lt;/li&gt;
&lt;li&gt;前期准备: 伪装(浏览器化), 处理登录和cookie, 设置代理&lt;/li&gt;
&lt;li&gt;采集方式: &lt;code&gt;urllib&lt;/code&gt;标准库, &lt;code&gt;Requests&lt;/code&gt;库采集静态网页; &lt;code&gt;selenium&lt;/code&gt; + &lt;code&gt;phantomjs&lt;/code&gt;采集动态网页&lt;/li&gt;
&lt;li&gt;后期处理: 原始数据采集的2大利器: &lt;code&gt;BeautifulSoup&lt;/code&gt;和&lt;code&gt;regex&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;就这么多了, 之前承诺的对[先睹为快]放出来笔记再进行梳理总结, 我做到了.&lt;/p&gt;
&lt;p&gt;不过这篇得算6.10那一档的, 期末了, 我要闭关了.&lt;/p&gt;
</content:encoded><category>文章</category><category>Python</category></item><item><title>[译]基于异步协程的网络爬虫</title><link>https://lifelonglove.cn/articles/a-web-crawler-with-asyncio-coroutines/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/a-web-crawler-with-asyncio-coroutines/</guid><description>通过事件循环协调大量网络等待，可以在单线程中高效抓取网页；内容从回调写法过渡到生成器协程与 asyncio，展示异步爬虫的完整实现思路。</description><pubDate>Wed, 01 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;译者的话&quot;&gt;译者的话&lt;/h2&gt;
&lt;p&gt;最近英文资料看多了, 手很痒, 很想翻译一篇高质量的英文文章. 这不, 马上就去跟原作者要了翻译权. 下面开始吧~\
(我是不会告诉你们, 期末将至, 我要落地, 要忙着应付各种实验, 课程设计, 报告, 期末考了. 之后的几篇如无意外, 都会以翻译的形式出现, 我尽量挑高质量的文章, 并尽量使译文能有原文80%的好.)&lt;/p&gt;
&lt;h3 id=&quot;简介&quot;&gt;简介&lt;/h3&gt;
&lt;p&gt;经典的计算机科学理论强调算法的高效性, 即计算应当尽可能快地完成. 但对于许多网络程序而言, 耗时的并非计算, 而是打开过多网络连接带来的时延, 或者突发事件造成的网络延迟. 这些程序引出的一个问题是: 对大量网络事件的等待. 解决此问题的时新方法是&lt;code&gt;异步I/O&lt;/code&gt;, 或者称为&lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;本章用一个网络爬虫程序进行演示说明. 该爬虫是一个典型的异步程序, 它等待大量的响应, 却几乎不做计算. 爬虫一次性爬到的页面越多, 程序就能越快地完成. 如果采用一个线程发起一个请求的方式来实现快速爬取, 当并发数量上升时, 在套接字耗尽之前, 内存或其他线程相关的资源就已经耗尽了. 而采用异步I/O的方式, 就避免了对多线程的需求.&lt;/p&gt;
&lt;p&gt;我们用三步来实现这个例子. 首先, 我们将展示一个异步消息循环(async event loop)的例子, 并写出爬虫的大概样子, 它以回调的方式使用消息循环(这种方式的效率很高, 但若对其扩展以运用于更复杂的问题, 将会导致代码不可收拾). 其次, 我们将展示Python的协程的高效性与可扩展性. 我们通过生成器函数来实现简单的协程. 最后, 我们用Python标准库“asyncio”[^foot1], 实现全功能的协程, 并用一个异步队列来这些协程.&lt;/p&gt;
&lt;h3 id=&quot;任务描述&quot;&gt;任务描述&lt;/h3&gt;
&lt;p&gt;网络爬虫发现并下载一个网站上的所有网页, 可能会对这些网页进行归档或建立索引. 以一个URL的根路径为起点, 爬虫抓取每张页面, 解析得到新的链接, 并将它们加入一个链接的队列. 当抓取的页面没有任何链接, 并且链接队列为空时, 爬虫程序也就停止了.&lt;/p&gt;
&lt;p&gt;我们可以通过同时下载多张页面来加快爬取的速度, 当爬虫找到新的链接时, 它用分离的套接字实现对新页面的同步获取. 爬虫会解析收到的响应, 并将新的链接加入链接队列. 过高的并发性可能会降低性能, 因此需要为并发请求的数量设一个上限, 等到请求完成, 才从链接队列中取链接, 发起新的请求.&lt;/p&gt;
&lt;h3 id=&quot;传统方法&quot;&gt;传统方法&lt;/h3&gt;
&lt;p&gt;我们如何让爬虫并发执行? 传统的方法是, 创建一个线程池. 每个线程通过一个套接字, 每次下载一张页面. 举个例子, 从&lt;code&gt;xkcd.com&lt;/code&gt;下载一张页面:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;def fetch(url):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sock = socket.socket()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sock.connect((&apos;xkcd.com&apos;, 80))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    request = &apos;GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n&apos;.format(url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sock.send(request.encode(&apos;ascii&apos;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    response = b&apos;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    chunk = sock.recv(4096)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    while chunk:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        response += chunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        chunk = sock.recv(4096)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Page is now downloaded.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    links = parse_links(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    q.add(links)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认地, 套接字的操作是阻塞的: 当线程调用一个方法时, 比如&lt;code&gt;connect&lt;/code&gt;或&lt;code&gt;recv&lt;/code&gt;, 该线程将被暂停, 直到调用的操作完成[^foot2]. 因此, 为了一次性下载多个页面, 我们需要多个线程. 一个成熟的应用程序, 通过维持一个线程池, 复用线程的方式来抵消频繁创建进程的代价; 同样地, 它通过维持一个连接池, 实现套接字的复用.&lt;/p&gt;
&lt;p&gt;然而, 线程的代价是高昂的, 操作系统通常会强制为进程, 用户或机器设置不同的线程上限. 在我的系统中(Jesse’s system), 一个Python线程占用大约50k的内存资源, 并且启动成千上万的线程将导致故障. 如果我们将并发的套接字上的同步操作也按比例增加到上万的数量级, 在套接字耗尽之前, 就线程就已经耗尽了. 单个线程的开销或者系统对于线程数量的限制是瓶颈所在.&lt;/p&gt;
&lt;p&gt;Dan Kegel在他的著作“The C10K problem”[^foot3]中, 概括了多线程I/O并发的局限性. 开篇, 他写到:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s time for web servers to handle ten thousand clients simultaneously, don’t you think? After all, the web is a big place now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Kegel在1999年的时候, 创造了“C10K”一词. 一万个连接, 现在听起来似乎挺不足道的, 但到今天, C10K问题也仅仅只是量变, 并没有本质的改变. 在当时, 用一个线性一条连接的方法解决C10K问题是不切实际的, 而现在, 也仅仅是天花板更高了而已. 讲真, 我们的爬虫能与线程配合得相当好. 然而, 对于那些维持着成千上万的连接的超大规模应用, 天花板依然存在: 大多数系统在耗尽线程之后, 仍然能创建套接字. 怎么才能解决这个问题呢?&lt;/p&gt;
&lt;h2 id=&quot;异步&quot;&gt;异步&lt;/h2&gt;
&lt;p&gt;异步I/O框架在单线程中使用非阻塞(non-blocking)套接字, 从而实现并发操作. 我们的异步爬虫, 在连接到服务器之前, 先设置套接字为非阻塞的:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock.setblocking(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sock.connect((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xkcd.com&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; BlockingIOError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;令人恼火的是, 即使是正常工作时, 非阻塞的套接字也会在&lt;code&gt;connect&lt;/code&gt;时抛出异常. 这个异常会反复调用底层的C函数, 为&lt;code&gt;EINPROGRESS&lt;/code&gt;设置&lt;code&gt;errno&lt;/code&gt;, 以此来提醒你, 连接已经开始.&lt;/p&gt;
&lt;p&gt;现在, 我们的爬虫需要一种方式来获知连接何时建立, 以便它发送HTTP请求. 可以简单地用一个紧密循环(tight loop)来实现:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;GET &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; HTTP/1.0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host: xkcd.com&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;encoded &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; request.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ascii&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sock.send(encoded)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Done.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; OSError&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;sent&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而, 这个方法不仅浪费电(囧), 还不能实现基于多套接字(multuple socket)事件的有效异步等待. 在“远古时代”, BSD Unix对此的解决方法是用&lt;code&gt;select&lt;/code&gt;, 一个C函数, 用于等待一个非阻塞套接字事件的发生, 或者也可以是几个套接字的数组. 如今, 为了应付互联网应用超大连接数的需求, 采用如&lt;code&gt;poll&lt;/code&gt;的代替方案, BSD上采用&lt;code&gt;kqueue&lt;/code&gt;, Linux上采用&lt;code&gt;epoll&lt;/code&gt;. 这些API与&lt;code&gt;select&lt;/code&gt;类似, 但能更好地应付超大数量的连接数.&lt;/p&gt;
&lt;p&gt;Python 3.4的&lt;code&gt;DefaultSelector&lt;/code&gt;使用基于系统的最优的类&lt;code&gt;select&lt;/code&gt;函数. 为了实现网络I/O的通知, 我们创建一个非阻塞套接字, 并用&lt;code&gt;DefaultSelector&lt;/code&gt;注册:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selectors &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DefaultSelector, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;EVENT_WRITE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;selector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DefaultSelector()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sock.setblocking(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sock.connect((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xkcd.com&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; BlockingIOError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; connected&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    selector.unregister(sock.fileno())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;connected!&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;selector.register(sock.fileno(), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;EVENT_WRITE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, connected)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们忽略伪错误, 并调用&lt;code&gt;selector.register&lt;/code&gt;, 传入套接字的文件描述符和一个表示我们正在等待的事件的常数. 为了在连接建立时得到通知, 我们需要传入一个&lt;code&gt;EVENT_WRITE&lt;/code&gt;: 也就是说, 我们希望获知何时套接字是“可写的”. 我们还需要传入一个Python函数, &lt;code&gt;connected&lt;/code&gt;. 当事件发生时, 执行该函数. 这样的函数就被称为&lt;em&gt;回调函数(callback)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;当选择器(selector)收到I/O通知时, 我们用一个循环来处理它:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selector.select()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key, event_mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            callback &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key.data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            callback()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回调函数&lt;code&gt;connected&lt;/code&gt;被存储为&lt;code&gt;event_key.data&lt;/code&gt;. 因此, 一旦非阻塞套接字连通, 我们可以立即检索数据并回调函数.&lt;/p&gt;
&lt;p&gt;不同与之前的紧密循环, 此处对&lt;code&gt;select&lt;/code&gt;的调用会暂停, 以异步等待下一个I/O事件. 接着, 在循环中执行对于发生的事件的回调. 未完成的操作会被挂起, 直到下一个时间循环时钟的到来, 才继续执行.&lt;/p&gt;
&lt;p&gt;所以到目前为止, 我们演示了什么呢? 我们展示了如何启动一个操作, 以及当操作准备就绪时执行回调函数. 异步框架就是建立在我们之前提到的&lt;strong&gt;非阻塞套接字&lt;/strong&gt;和&lt;strong&gt;事件循环&lt;/strong&gt;两个特征之上的, 这就在单线程中实现了并发操作.&lt;/p&gt;
&lt;p&gt;此处, 我们实现了“并发”, 但不是传统意义上所谓的“并行”. 也就是说, 我们创建了一个能重叠I/O的微型系统. 这意味着, 可以在其他操作期间创建并执行新的操作. 这并不是利用多核实现并行操作. 也就是说, 该系统是为I/O密集型问题设计的, 而不是CPU密集型问题.&lt;/p&gt;
&lt;p&gt;我们的事件循环是高效并发I/O的, 因为并没有为每一条连接都分配线程资源. 在真正开始之前, 有必要纠正一个普遍的误解——异步比多线程更快. 坦白说, 通常并不是这样的. 在Python中, 像我们所创建的事件循环, 在处理数量不大但交互频繁的连接时, 要比多线程方式慢. 在没有全局解释器锁(Global Interpreter Lock)的运行时环境下, 多线程同样表现得更出色. 而异步I/O的真正用武之处在于, 充满大量慢连接和不频繁事件的应用场景.&lt;/p&gt;
&lt;h3 id=&quot;回调编程&quot;&gt;回调编程&lt;/h3&gt;
&lt;p&gt;基于我们刚刚建立的简短的异步框架, 我们该如何创建一个网络爬虫呢? 即使是一个简单的URL捕捉器难于编写.&lt;/p&gt;
&lt;p&gt;我们从已经获取的全局URL集合开始, 如下所示:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;urls_todo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;seen_urls &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;集合&lt;code&gt;seen_urls&lt;/code&gt;包含了&lt;code&gt;urls_todo&lt;/code&gt;以及访问过的URL. 两个集合都用根URL“/“进行初始化.&lt;/p&gt;
&lt;p&gt;抓取一张页面需要一系列的回调. 当一个套接字连接成功时, &lt;code&gt;connected&lt;/code&gt;回调函数被执行, 用于向服务器发送一个GET请求. 但之后它就必须异步地等待响应, 因此它会回调另一个函数. 当回调函数被执行后, 只有再次被调用, 它才能读取完整的响应.&lt;/p&gt;
&lt;p&gt;我们将这些回调函数封装进一个&lt;code&gt;Fetcher&lt;/code&gt;对象. 因此, 它需要一个URL, 一个套接字对象, 和一个累积存储响应字节的地方:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Fetcher&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __init__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, url):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Empty array of bytes.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.url &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们以&lt;code&gt;Fetcher.fetch&lt;/code&gt;开始:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Method on Fetcher class.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock.setblocking(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        try&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock.connect((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xkcd.com&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        except&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; BlockingIOError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # Register next callback.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        selector.register(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock.fileno(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;                          EVENT_WRITE&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;                          self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.connected)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fetch&lt;/code&gt;方法启动一个连接. 但请注意, 在连接建立之前, 该方法就返回了. 它必须将控制权返还给事件循环, 以等待连接的建立. 为了理解, 请想象完整的应用结构, 是这样的:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Begin fetching http://xkcd.com/353/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fetcher &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Fetcher(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/353/&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;fetcher.fetch()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selector.select()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key, event_mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        callback &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key.data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        callback(event_key, event_mask)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有的事件通知都是通过调用&lt;code&gt;select&lt;/code&gt;函数, 然后在事件循环中进行处理. 因此, &lt;code&gt;fetch&lt;/code&gt;方法必须将控制权交由事件循环, 如此, 程序才能知晓套接字已经建立. 然后, 在循环中回调&lt;code&gt;connected&lt;/code&gt;函数, 在上述&lt;code&gt;fetch&lt;/code&gt;方法的最后已经完成该回调函数的注册.&lt;/p&gt;
&lt;p&gt;以下是&lt;code&gt;connected&lt;/code&gt;的实现:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Method on Fetcher class.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; connected&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, key, mask):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;connected!&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        selector.unregister(key.fd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;GET &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; HTTP/1.0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host: xkcd.com&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock.send(request.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ascii&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # Register the next callback.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        selector.register(key.fd,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;                          EVENT_READ&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;                          self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.read_response)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该方法发送一个GET请求. 一个真实的应用会检查&lt;code&gt;send&lt;/code&gt;的返回值, 以防完整的消息不能一次性发送完毕. 但是我们的请求很简短, 我们的应用也很简单. 因此, 就“轻率”地调用&lt;code&gt;send&lt;/code&gt;, 并等待响应. 当然, 必须注册另一个回调函数, 从而将控制权上交给事件循环. 下一个也是最后一个回调函数是&lt;code&gt;read_response&lt;/code&gt;, 用于处理服务器的响应:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Method on Fetcher class.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; read_response&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self, key, mask):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        global&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        chunk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.sock.recv(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4096&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 4k chunk size.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chunk:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            selector.unregister(key.fd)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Done reading.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            links &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.parse_links()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            # Python set-logic:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; link &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; links.difference(seen_urls):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                urls_todo.add(link)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Fetcher(link).fetch()  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# &amp;lt;- New Fetcher.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            seen_urls.update(links)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            urls_todo.remove(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urls_todo:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                stopped &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每当选择器发现套接字是“可读的”, 就会执行回调函数. 此处“可读”的意思是, 套接字是有数据的, 或它已经被关闭.&lt;/p&gt;
&lt;p&gt;回调函数一次最多从套接字读取4kb的数据. 若数据少于4kb, &lt;code&gt;chunk&lt;/code&gt;将读取所有数据; 若数据量超过4kb, &lt;code&gt;chunk&lt;/code&gt;读取4kb的数据, 并保持套接字依旧可读, 下一个时钟到来时, 事件循环将再次调用回调函数读取数据. 响应结束时, 服务器将关闭套接字, &lt;code&gt;chunk&lt;/code&gt;变空.&lt;/p&gt;
&lt;p&gt;上述代码中出现的&lt;code&gt;parse_links&lt;/code&gt;方法, 返回一个URL的集合. 我们为每一个新的URL都启动一个新的抓取器, 并且不设置并发上限. 使用回调函数的异步编程的一个显著特征就是: 不需要为共享数据设置排他锁, 向&lt;code&gt;seen_urls&lt;/code&gt;集合添加链接就是一个例子. 因为没有抢占式多任务, 我们的代码并不能随意地打断.&lt;/p&gt;
&lt;p&gt;我们用一个全局变量&lt;code&gt;stopped&lt;/code&gt;来控制循环:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;stopped &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; stopped:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selector.select()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key, event_mask &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            callback &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; event_key.data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            callback()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当所有页面都下载完毕, 抓取器停止全局事件循环, 程序结束.&lt;/p&gt;
&lt;p&gt;This example makes async’s problem plain: spaghetti code. 我们需要某种方式去实现一系列的计算与I/O操作, 并调度这一系列的操作并发地执行. 然后, 不适用多线程, 这一系列的操作就不能用一个单一函数完成: 启动I/O操作, 存储状态, 返回结果. 因此, 需要再思考并写一段状态存储代码.&lt;/p&gt;
&lt;p&gt;让我们来解释. 考虑下我们是如何在一个线程里通过常规阻塞套接字抓取URL的:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Blocking version.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(url):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sock &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sock.connect((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;xkcd.com&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;GET &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;{}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; HTTP/1.0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Host: xkcd.com&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.format(url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sock.send(request.encode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ascii&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    chunk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sock.recv(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4096&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chunk:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; chunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        chunk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sock.recv(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4096&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Page is now downloaded.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    links &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; parse_links(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    q.add(links)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数是如何记住一个套接字操作与下一个操作之间的状态的呢? 它包含了套接字, 一个URL, 可累积的响应对象. 单线程的函数通过使用编程语言提供的基本特征存储局部变量的临时状态, 通过一个局部栈. 该函数又有一点“附加部分”——即代码将在I/O完成时被执行. 通过线程的指令指针, 运行时会记得这个附加部分. 因此, I/O完成后, 你不需要考虑重新存储局部变量和附加部分. 而这一切都是建立在语言基础上的.&lt;/p&gt;
&lt;p&gt;但是使用基于回调的异步框架, 这些语言特征毫无帮助. 当等待I/O时, 函数必须显式地保存其状态, 因为在I/O完成前, 函数返回会丢失栈帧. 为了替代局部变量, 基于回调的例子将&lt;code&gt;sock&lt;/code&gt;和&lt;code&gt;response&lt;/code&gt;存储为Fetcher实例的实例属性. 为了替代指令指针, 通过注册回调函数&lt;code&gt;connected&lt;/code&gt;和&lt;code&gt;read_response&lt;/code&gt;来存储附加部分. 随着应用特征的增长, 我们通过手动回调的方式存储的状态复杂性也增加. 程序员并不乐意从事这样繁重的工作.&lt;/p&gt;
&lt;p&gt;更坏的情况是, 在执行下一个回调函数前, 当前的回调函数抛出了异常. 比如, 我们写了一个差劲的&lt;code&gt;parse_links&lt;/code&gt;方法, 它在解析HTML文档时会抛出异常:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Traceback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (most &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;recent&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; call&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; last&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  File&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loop-with-callbacks.py&quot;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 111,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;modul&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    loop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  File&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loop-with-callbacks.py&quot;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 106,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; loop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    callback(event_key,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; event_mask&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  File&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loop-with-callbacks.py&quot;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 51,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; read_response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    links&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; self.parse_links&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  File&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;loop-with-callbacks.py&quot;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 67,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; parse_links&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    raise&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Exception&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;&apos;parse error&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Exception:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; parse&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上栈回溯只是显示了事件循环正在执行一个回调函数. 我们并不能知道是错误的原因. 有2种可能会导致出错: 我们忘了将要去哪, 以及何时来. 这种上下文信息的丢失称为“stack ripping”, 它容易引发混淆. Stack ripping还会妨碍我们为回调函数链建立异常处理器, 采用“try/except”块包裹函数调用及其子树.&lt;/p&gt;
&lt;p&gt;因此, 暂且不说多线程与异步的相对效率, 两者在易错性上&lt;/p&gt;
</content:encoded><category>文章</category><category>Python</category></item><item><title>[笔记]Python网络数据采集</title><link>https://lifelonglove.cn/notes/note-on-web-scraping-with-python/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/note-on-web-scraping-with-python/</guid><description>整理请求网页、解析 HTML、处理异常与保存数据的 Python 网络采集基础。</description><pubDate>Wed, 01 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h1 id=&quot;写在前面的话&quot;&gt;写在前面的话&lt;/h1&gt;
&lt;p&gt;接下来的这个月要忙着应付期末的各种事情了, 可能不太有时间写博客了. 看过我博客的, 对于我博客的“又长又臭”可能有所了解, 平均一篇都要花费我2.5天时间. 这次, 我先把之前做的笔记放出来, 美其名曰: &lt;code&gt;先睹为快&lt;/code&gt;, 算作以后的一个系列吧. 稍后应该可能也许大概会有修订版, 对笔记的内容作进一步的梳理总结.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;笔记之python网络数据采集&quot;&gt;笔记之Python网络数据采集&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;非原创即采集&lt;/li&gt;
&lt;li&gt;一念清净, 烈焰成池, 一念觉醒, 方登彼岸&lt;/li&gt;
&lt;li&gt;网络数据采集, 无非就是写一个自动化程序向网络服务器请求数据, 再对数据进行解析, 提取需要的信息&lt;/li&gt;
&lt;li&gt;通常, 有api可用, api会比写网络爬虫程序来获取数据更加方便.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;part1-创建爬虫&quot;&gt;Part1 创建爬虫&lt;/h2&gt;
&lt;h3 id=&quot;chapter1-初建网络爬虫&quot;&gt;Chapter1 初建网络爬虫&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一旦你开始采集网络数据, 就会感受到浏览器为我们所做的所有细节, 它解释了所有的html, css, JavaScript&lt;/li&gt;
&lt;li&gt;网络浏览器是一个非常有用的应用, 它创建信息的数据包, 发送, 并把获取的数据解释成图像, 声音, 视频, 或文字. 但网络浏览器就是代码, 而代码是可以分解的, 可以分解成许多基本组件, 可重写, 重用, 以及做成我们想要的任何东西&lt;/li&gt;
&lt;li&gt;“域名为kissg.me的服务器上&amp;lt;网络应用根地址&amp;gt;/pages目录下的html文件page1.html的源代码”&lt;/li&gt;
&lt;li&gt;网络浏览器与爬虫程序的区别:&lt;/li&gt;
&lt;li&gt;浏览器遇到html标签时, 会向服务器再发起对该资源的请求, 再用请求得到的资源渲染页面&lt;/li&gt;
&lt;li&gt;爬虫程序并没有返回向服务器请求多个文件的逻辑, 它只能读取已经请求的单个html文件&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BeautifulSoup&lt;/code&gt;通过定位html标签来格式化和组织复杂的网络信息, 以python对象展示xml结构信息&lt;/li&gt;
&lt;li&gt;先调用&lt;code&gt;response.read()&lt;/code&gt;获取网页的内容, 再将html内容传给BeautifulSoup对象, 形成的结构如下所示:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;html → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; head → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A Useful Page&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; title → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A Useful Page&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; body → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;An Int&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Lorem ip&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; h1 → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;An Interesting Title&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; div → &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Lorem Ipsum dolor&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;因此可以通过&lt;code&gt;bsObj.html.body.h1&lt;/code&gt;的方式提取标签&lt;/li&gt;
&lt;li&gt;请求的网页在服务器上不存在时, 程序会返回http错误, urlopen函数会抛出&lt;code&gt;HTTPError&lt;/code&gt;异常, 对此, 可使用&lt;code&gt;try ... except ...&lt;/code&gt;语句来处理&lt;/li&gt;
&lt;li&gt;服务器不存在, urlopen返回一个None对象, 可通过条件判断简单地处理&lt;/li&gt;
&lt;li&gt;当调用的标签不存在时, BeautifulSoup会返回None对象, 请求None对象的子标签, 会发生&lt;code&gt;AttributeError&lt;/code&gt;错误, 同样地, 也用&lt;code&gt;try ... except ... &lt;/code&gt;语句来处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;一个良好的爬虫程序首先要具备强大而周密的异常处理能力, 随时应对网络中可能存在的异常&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在写爬虫的时候, 思考代码的总体格局, 让代码既可以&lt;strong&gt;捕捉异常&lt;/strong&gt;又&lt;strong&gt;容易阅读&lt;/strong&gt;, 很重要. 考虑代码的重用性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter2-复杂html解析&quot;&gt;Chapter2 复杂html解析&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当米开朗基罗被问及如何完成“大卫”这样匠心独具的雕刻作品时, 他有一段著名的回答: “很简单, 你只要用锤子把石头上不像大卫的地方敲掉就行了.” (大道至简)&lt;/li&gt;
&lt;li&gt;解析html的前戏, 磨刀不勿砍柴功:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;寻找“打印此页”, 或看看网站有没有html样式更友好的移动版(将请求头设置成移动设备的状态, 然后接收网站移动版)&lt;/li&gt;
&lt;li&gt;需找隐藏在js文件里的信息,&lt;/li&gt;
&lt;li&gt;网页信息也许可以从网页的url链接中获取&lt;/li&gt;
&lt;li&gt;寻找其他数据源, 有没有其他网站显示了同样的数据? 该网站的数据是不是来自其他网站&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;css(cascading style sheet, 层叠样式表)为爬虫提供了便利, 它是html元素差异化, 使那些具有相同修饰的元素呈现不同的样式, 从而是爬虫的目标更明确&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findAll()&lt;/code&gt;的基础用法: &lt;code&gt;findAll(tag_name, dict_of_attributes)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;bsObj.tagName&lt;/code&gt;只能获取页面中第一个指定的标签&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_text()&lt;/code&gt;方法正在处理的html文档中的所有标签都清楚掉, 返回只包含文字的字符串&lt;/li&gt;
&lt;li&gt;&lt;code&gt;findAll(tag, attributes, recursive, text, limit, **keywords)&lt;/code&gt; &amp;amp; &lt;code&gt;find(tag, attributes, recursive, text, **keywords)&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;tag - 可以是单个标签名, 也可以是多个标签名组成的列表(集合)&lt;/li&gt;
&lt;li&gt;attributes - 用字段封装的标签属性与属性值, 属性值可以是一个集合&lt;/li&gt;
&lt;li&gt;recursive - 递归? 默认为True. 若recursive=False, findAll只会查文档的一级标签. 若对速度要求非常高, 可设置递归参数&lt;/li&gt;
&lt;li&gt;text - 用标签的内容去匹配, 而不是标签名或属性,&lt;/li&gt;
&lt;li&gt;limit - 限制findAll的搜索结果项&lt;/li&gt;
&lt;li&gt;关键字参数 - 允许用户自己指定属性的标签. 是BeautifulSoup在技术上做的冗余功能, 任何关键字参数能完成的任务, 都可以其他技术解决&lt;/li&gt;
&lt;li&gt;通过标签参数&lt;code&gt;tag&lt;/code&gt;把标签列表传到&lt;code&gt;.findAll()&lt;/code&gt;里获取一列标签, 其实是一个“或”关系的过滤器. 而关键字参数可以增加一个“与”关系的过滤器来简化工作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tag&lt;/code&gt;对象 - &lt;code&gt;BeautifulSoup&lt;/code&gt;对象通过&lt;code&gt;find&lt;/code&gt;或&lt;code&gt;findAll&lt;/code&gt;, 或直接调用子标签获取的一些列对象或单个对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NavigableString&lt;/code&gt;对象 - 用于表示标签里的文字&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Comment&lt;/code&gt;对象 - html文档的注释标签&lt;/li&gt;
&lt;li&gt;html页面可以映射成一棵树&lt;/li&gt;
&lt;li&gt;子标签, 即一个父标签的下一级; 后代标签, 指一个父标签下面所有级别的标签. 所有子标签都是后代标签, 但不是所有的后代标签都是子标签&lt;/li&gt;
&lt;li&gt;一般, bs函数总是处理当前标签的后代标签, 比如&lt;code&gt;.findAll&lt;/code&gt;就是递归地在所有后代标签中查找&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;.children&lt;/code&gt;将只获取当前标签的子标签,  使用&lt;code&gt;descendants&lt;/code&gt;将获取所有后代标签&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;.next_siblings&lt;/code&gt;将获得当前标签之后,所有的兄弟标签. 注意, 对象本身不能作为自己的兄弟标签; 从名字也可以看出, 是返回之后的兄弟标签. 相应的有&lt;code&gt;previous_siblings&lt;/code&gt;,&lt;code&gt;next_sibling&lt;/code&gt;和&lt;code&gt;previous_sibling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;bsObj.find(&quot;table&quot;, {&quot;id&quot;: &quot;giftList&quot;}).tr&lt;/code&gt;而不是简单的&lt;code&gt;bsObj.table.tr&lt;/code&gt;或&lt;code&gt;bsObj.tr&lt;/code&gt;是为了让对象的选择更具体, 而不丢失标签的细节&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent&lt;/code&gt;和&lt;code&gt;parents&lt;/code&gt;分别用于获取标签对象的直接父标签和所有父辈标签(包括爷爷, 但不包括叔叔, 好吧, 通俗易懂)&lt;/li&gt;
&lt;li&gt;在动手写正则表达式之前, 写一个步骤列表描述出目标字符串的结构&lt;/li&gt;
&lt;li&gt;regex:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; - 重复任意次, 包括0次&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt; - 表示或&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; - 重复至少1次&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[]&lt;/code&gt; - 匹配其中的任意一个字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; - 表达式编组, 在regex的规则里编组会优先运行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{m,n}&lt;/code&gt; - 重复m到n次&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[^]&lt;/code&gt; - 匹配一个任意不在方括号中的字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; - 匹配任意单个字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;^&lt;/code&gt; - 标识字符串的开始&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\&lt;/code&gt; - 转义字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$&lt;/code&gt; - 标识字符串的结尾&lt;/li&gt;
&lt;li&gt;在BeautifulSoup中使用regex, 提高效率, 如&lt;code&gt;images = bsObj.findAll(&quot;img&quot;,{&quot;src&quot;:re.compile(&quot;\.\.imggifts/img.*\.jpg&quot;)})&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;regex可以作为BeautifulSoup语句的任意一个参数&lt;/li&gt;
&lt;li&gt;对于标签对象, 可采用&lt;code&gt;.attrs&lt;/code&gt;获取全部属性, 返回字典对象. &lt;code&gt;imgTag.attrs[&quot;src&quot;]&lt;/code&gt;就可以获取图片的资源位置&lt;/li&gt;
&lt;li&gt;BeautifulSoup允许将特定函数类型作为&lt;code&gt;findAll&lt;/code&gt;函数的参数, 唯一的限制条件是这些函数必须将一个&lt;em&gt;&lt;strong&gt;标签作为参数&lt;/strong&gt;&lt;/em&gt;且&lt;em&gt;&lt;strong&gt;返回结果是布尔类型&lt;/strong&gt;&lt;/em&gt;. BeautifulSoup用这个函数评估遇到的每个标签对象, 将评估结果为“True”的标签保留下来, 将其他标签剔除. 如&lt;code&gt;soup.findAll(lambda tag: len(tag.attrs) == 2)&lt;/code&gt;将返回具有2个属性的标签&lt;/li&gt;
&lt;li&gt;BeautifulSoup与regex与lambda的联合使用, 想想都无敌&lt;/li&gt;
&lt;li&gt;其他的html解析模块:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;lxml - 底层, 大部分源代码用c写成, 因此处理速度会非常快&lt;/li&gt;
&lt;li&gt;HTML parser - 自带的&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;chapter3-开始采集&quot;&gt;Chapter3 开始采集&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;之所以叫网络爬虫, 是因为他们可以沿着网络爬行, 本质是一种&lt;em&gt;递归方式&lt;/em&gt;: 为了找到url链接, 必须首先获取网页内容, 检查页面内容, 再寻找另一个url, 获取页面内容, 不断循环&lt;/li&gt;
&lt;li&gt;使用网络爬虫的时候, 应谨慎地考虑需要消耗多少网络流量, 还要尽量思考能否让采集目标的服务器负载更低&lt;/li&gt;
&lt;li&gt;&lt;code&gt;维基百科六度分隔理论&lt;/code&gt; - 任何2个不相干的词条, 都可以通过总数不超过6条的词条链接起来(包括原来的2个词条).&lt;/li&gt;
&lt;li&gt;由此, 写爬虫时, 如何高效地通过最少的链接点击次数到达目的站点, 不仅使爬虫工作效率更高, 且对服务器的负载影响也越小&lt;/li&gt;
&lt;li&gt;爬取的内容往往携带许多无用的信息, 在处理之前, 应根据实际剔除无用信息&lt;/li&gt;
&lt;li&gt;随机算法都努力创造一种均匀分布且难以预测的数据序列, 但在算法初始阶段都需要提供一个随机数种子(random seed). 完全相同的种子每次将产生相同的“随机”数序列. 可以用系统当前时间作为随机数种子, 而使程序运行更具随机性.&lt;/li&gt;
&lt;li&gt;python的伪随机数生成器用的是梅森旋转算法(&lt;a href=&quot;https://en.wikipedia.org/wiki/Mersenne_Twister&quot;&gt;https://en.wikipedia.org/wiki/Mersenne_Twister&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;浅网(surface web)是搜索引擎可以抓取的网络; 暗网(dark web)或深网(deep web)则是另一部分. 据不完全统计,互联网中其实约 90% 的网络都是深网.&lt;/li&gt;
&lt;li&gt;暗网,也被称为 Darknet 或 dark Internet,完全是另一种“怪兽”。它们也建立在已有的网络基础上,但是使用 Tor 客户端,带有运行在 HTTP 之上的新协议,提供了一个信息交换的安全隧道。这类暗网页面也是可以采集的。&lt;/li&gt;
&lt;li&gt;遍历整个网站的数据采集的好处:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;生成网站地图(脉络)&lt;/li&gt;
&lt;li&gt;收集数据&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;为了避免重复采集页面, 使用&lt;code&gt;set&lt;/code&gt;来保存已采集的页面&lt;/li&gt;
&lt;li&gt;如果递归运行的次数过多, 递归程序可能会崩溃. python的默认递归限制是1000次.&lt;/li&gt;
&lt;li&gt;在开始写爬虫程序之前, 应充分分析待爬取网站的html文档的格式&lt;/li&gt;
&lt;li&gt;在一个异常处理语句中包裹多行语句显然是有点危险的. 首先无法识别出究竟哪行代码出现了异常, 其次前面的语句出现异常, 将直接导致后面语句的执行&lt;/li&gt;
&lt;li&gt;网络爬虫位于许多新式的网络技术领域彼此交叉的中心地带. 要实现跨站的数据分析, 只要构建出可以从互联网上的网页里解析和存储数据的爬虫就可以了&lt;/li&gt;
&lt;li&gt;一个网站内部的爬虫, 只需要爬取以&lt;code&gt;/&lt;/code&gt;开始的资源就可以了&lt;/li&gt;
&lt;li&gt;在开始写爬虫跟随外链随意跳转之前, 该思考的问题:&lt;/li&gt;
&lt;li&gt;我要收集哪些数据?这些数据可以通过采集几个已经确定的网站(永远是最简单的做法)完成吗?或者我的爬虫需要发现那些我可能不知道的网站吗?&lt;/li&gt;
&lt;li&gt;当我的爬虫到了某个网站,它是立即顺着下一个出站链接跳到一个新网站,还是在网站上呆一会儿,深入采集网站的内容?&lt;/li&gt;
&lt;li&gt;有没有我不想采集的一类网站?我对非英文网站的内容感兴趣吗?&lt;/li&gt;
&lt;li&gt;如果我的网络爬虫引起了某个网站网管的怀疑,我如何避免法律责任?&lt;/li&gt;
&lt;li&gt;在以任何正式目的运行代码之前, 确保已经在可能出现问题的地方都放置了检查语句&lt;/li&gt;
&lt;li&gt;写代码之前拟个大纲或画个流程图, 是一个很好的编程习惯, 不仅可以为后期处理节省时间, 更重要的是可以防止自己在爬虫变得越来越复杂时乱了分寸&lt;/li&gt;
&lt;li&gt;&lt;code&gt;重定向&lt;/code&gt; - 允许一个网页在不同的域名下显示, 有2种形式:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;服务器端重定向, 网页在加载之前先改变了url&lt;/li&gt;
&lt;li&gt;客户端重定向, 跳转到新url之前网页需要加载内容&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;python3版本的urllib库会自动处理重定向. 不过要注意,有时候要采集的页面的 URL 可能并不是当前所在页面的url&lt;/li&gt;
&lt;li&gt;爬虫的一些基本模式: 找出页面上的所有链接, 区分内外链, 跳转到新的页面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter4-使用api&quot;&gt;Chapter4 使用API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;API的用处:为不同的应用提供了方便友好的接口。不同的开发者用不同的架构,甚至不同的语言编写软件都没问题——因为API设计的目的就是要成为一种通用语言,让不同的软件进行信息共享。&lt;/li&gt;
&lt;li&gt;API 可以通过 HTTP 协议下载文件,和 URL 访问网站获取数据的协议一样,它几乎可以实现所有在网上干的事情。API 之所以叫 API 而不是叫网站的原因,其实是首先 API 请求使用非常严谨的语法,其次 API 用 JSON 或 XML 格式表示数据,而不是HTML 格式。&lt;/li&gt;
&lt;li&gt;通常api的验证方法都是用类似令牌(token)的方式调用, 每次api调用将令牌传递给服务器. token除了在url链接中传递, 还会通过请求头里的cookie将用户信息传递给服务器:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;token = token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;webRequest = urllib.request.Request(&quot;http://xxx&quot;, headers={&quot;token&quot;: token})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;api一个重要的特征是反馈格式友好的数据, xml或json. 目前json比xml更受欢迎, 因为json文件比完整的xml文件小, 另一个原因是网络技术的改变.&lt;/li&gt;
&lt;li&gt;使用get请求获取数据时, 用url路径描述获取的数据范围, 查询参数可以作为过滤器或附加请求使用.&lt;/li&gt;
&lt;li&gt;一些api使用文件路径形式指定api版本, 数据和其他属性:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://socialmediasite.com/api/v4/json/users/1234/posts?from=08012014&amp;amp;to=08312014&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;一些api通过请求参数的形式指定数据格式和api版本:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://socialmediasite.com/users/1234/posts?format=json&amp;amp;from=08012014&amp;amp;to=08312014&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;response.read()&lt;/code&gt; -&amp;gt; bytes&lt;/li&gt;
&lt;li&gt;python将json转换成字典, json数组转换成列表, json字符串转换成pyton字符串&lt;/li&gt;
&lt;li&gt;如果你用API作为唯一的数据源,那么你最多就是复制别人数据库里的数据,不过都是些已经公布过的“黄花菜”。真正有意思的事情,是把多个数据源组合成新的形式,或者把 API 作为一种工具,从全新的视角对采集到的数据进行解释&lt;/li&gt;
&lt;li&gt;虽然列表迭代速度更快, 但集合查找速度更快(确定一个对象是否在集合中).&lt;/li&gt;
&lt;li&gt;python的集合就是值为None的字典, 用到是hash表结构, 查询速度为O(1)&lt;/li&gt;
&lt;li&gt;多种技术的融合, 多种数据的融合, 将得到更有用的信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter5-存储数据&quot;&gt;Chapter5 存储数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;大数据存储与数据交互能力, 在新式的程序开发中已经是重中之重了.&lt;/li&gt;
&lt;li&gt;存储媒体文件的2种主要方式: 只获取url链接, 或直接将源文件下载下来&lt;/li&gt;
&lt;li&gt;直接引用url链接的优点:&lt;/li&gt;
&lt;li&gt;爬虫运行得更快,耗费的流量更少,因为只要链接,不需要下载文件。&lt;/li&gt;
&lt;li&gt;可以节省很多存储空间,因为只需要存储 URL 链接就可以。&lt;/li&gt;
&lt;li&gt;存储 URL 的代码更容易写,也不需要实现文件下载代码。&lt;/li&gt;
&lt;li&gt;不下载文件能够降低目标主机服务器的负载。&lt;/li&gt;
&lt;li&gt;直接引用url链接的缺点:&lt;/li&gt;
&lt;li&gt;这些内嵌在网站或应用中的外站 URL 链接被称为盗链(hotlinking), 每个网站都会实施防盗链措施。&lt;/li&gt;
&lt;li&gt;因为链接文件在别人的服务器上,所以应用就要跟着别人的节奏运行了。&lt;/li&gt;
&lt;li&gt;盗链是很容易改变的。如果盗链图片放在博客上,要是被对方服务器发现,很可能被恶搞。如果 URL 链接存起来准备以后再用,可能用的时候链接已经失效了,或者是变成了完全无关的内容。&lt;/li&gt;
&lt;li&gt;python3的urllib.request.&lt;code&gt;urlretrieve&lt;/code&gt;可以根据文件的url下载文件:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlretrieve&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bs4 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BeautifulSoup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;html &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://www.pythonscraping.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bsObj &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BeautifulSoup(html)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;imageLocation &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; bsObj.find(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;logo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).find(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;img&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;urlretrieve (imageLocation, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;logo.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;csv(comma-separated values, 逗号分隔值)是存储表格数据的常用文件格式&lt;/li&gt;
&lt;li&gt;网络数据采集的一个常用功能就是获取html表格并写入csv&lt;/li&gt;
&lt;li&gt;除了用户定义的变量名,mysql是不区分大小写的, 习惯上mysql关键字用大写表示&lt;/li&gt;
&lt;li&gt;连接与游标(connection/cursor)是数据库编程的2种模式:&lt;/li&gt;
&lt;li&gt;连接模式除了要连接数据库之外, 还要发送数据库信息, 处理回滚操作, 创建游标对象等&lt;/li&gt;
&lt;li&gt;一个连接可以创建多个游标, 一个游标跟踪一种状态信息, 比如数据库的使用状态. 游标还会包含最后一次查询执行的结果. 通过调用游标函数, 如&lt;code&gt;fetchall&lt;/code&gt;获取查询结果&lt;/li&gt;
&lt;li&gt;游标与连接使用完毕之后,务必要关闭, 否则会导致连接泄漏, 会一直消耗数据库资源&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;try ... finally&lt;/code&gt;语句保证数据库连接与游标的关闭&lt;/li&gt;
&lt;li&gt;让数据库更高效的几种方法:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;给每张表都增加id字段. 通常数据库很难智能地选择主键&lt;/li&gt;
&lt;li&gt;用智能索引, &lt;code&gt;CREATE INDEX definition ON dictionary (id, definition(16));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;选择合适的范式&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;发送Email, 通过爬虫或api获取信息, 设置条件自动发送Email! 那些订阅邮件, 肯定就是这么来的!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter6-读取文档&quot;&gt;Chapter6 读取文档&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;互联网的最基本特征: 作为不同类型文件的传输媒体&lt;/li&gt;
&lt;li&gt;互联网不是一个html页面的集合, 它是一个信息的集合, 而html文件只是展示信息的一个框架&lt;/li&gt;
&lt;li&gt;从最底层的角度看, 所有文档都是01的编码, 而任意类型的文件的唯一区别就在于,它们的01在面向用户的转换方式不同&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utf-8&lt;/code&gt;的全称: Universal Character Set - Transformation Format 8 bit&lt;/li&gt;
&lt;li&gt;utf-8的每个字符开头有一个标记,表示该字符用几个字节表示.一个字符最多可以是4字节, 但字节信息里还包括一部分设置信息, 因此全部32位不会都用, 最多使用21位&lt;/li&gt;
&lt;li&gt;utf8利用ascii的填充位让所有以“0”开头的字节表示该字符占用1个字节. 因此, 在英文字符在ascii和uft8两个编码方式下表示一样&lt;/li&gt;
&lt;li&gt;python默认将文本读成ascii编码格式&lt;/li&gt;
&lt;li&gt;utf8不能处理iso编码格式. 因此做数据采集工作时,尤其对国际网咱, 最好先看看meta标签内容, 用网站推荐的编码方式读取页面内容&lt;/li&gt;
&lt;li&gt;几种读取在线文件的方法:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;下载读取&lt;/li&gt;
&lt;li&gt;从网上直接将文件读成一个字符串, 然后将其转换成一个StringIO对象, 使其具有文件的属性:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;dataFile &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; io.StringIO(data)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;csv.reader&lt;/code&gt;的返回对象是可迭代的, 而且由list对象构成.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;csv.DictReader&lt;/code&gt;将csv文件的每一行转换成python字典返回, 并将字段列表(标题栏)保存在dictReader.fieldnames里&lt;/li&gt;
&lt;li&gt;pdf的文字提取&lt;/li&gt;
&lt;li&gt;.docx的文字提取：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;从文件读取xml, 将文档读成一个二进制对象(BytesIO), 再用zipfile解压(所有.docx文件为了节省空间都进行过压缩), 再读取解压文件, 就变成了xml&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; zipfile &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ZipFile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urllib.request &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; io &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BytesIO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wordFile &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; urlopen(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://pythonscraping.com/pages/AWordDocument.docx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).read()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;wordFile &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; BytesIO(wordFile)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;document &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ZipFile(wordFile)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;xml_content &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; document.read(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;word/document.xml&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(xml_content.decode(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;part2-高阶数据采集&quot;&gt;Part2 高阶数据采集&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;网站的真实故事其实都隐藏在js, 登录表单和网站反爬取措施的背后&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter7-数据清洗&quot;&gt;Chapter7 数据清洗&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用regex移除不想要的字符, 如换行符(\n). 剔除字符的过程, 合理的先后顺序能省很多力&lt;/li&gt;
&lt;li&gt;用&lt;em&gt;&lt;strong&gt;先以utf8格式编码,再以ascii方法解码&lt;/strong&gt;&lt;/em&gt;的方式,可以一定程度上剔除unicode字符&lt;/li&gt;
&lt;li&gt;数据标准化要确保清洗后的数据在语言学或逻辑上是等价的&lt;/li&gt;
&lt;li&gt;python的&lt;code&gt;OrderedDict&lt;/code&gt;,能解决字典无序排列的问题&lt;/li&gt;
&lt;li&gt;数据标准化时,根据投入计算能力的多少,还可以再考虑大小写(python与Python),单词等价(1st与first),连字符的使用(co-ordinated与coordinated),拼写错误,语病等因素&lt;/li&gt;
&lt;li&gt;对连字符的一个处理是,将其去掉或转换成其他字符,比如空格&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter8-自然语言处理&quot;&gt;Chapter8 自然语言处理&lt;/h3&gt;
&lt;h4 id=&quot;概括数据&quot;&gt;概括数据&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;n-gram模型可用于词频分析, 很厉害!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;马尔可夫模型&quot;&gt;马尔可夫模型&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;马尔可夫文字生成器(markov text generator)&lt;/code&gt; - 基于一种常用于分析大量随机事件的马尔可夫模型. 随机事件的特点是一个离散事件发生之后, 另一个离散事件将在前一个事件的条件下以一定概率发生&lt;/li&gt;
&lt;li&gt;在马尔可夫模型描述的天气系统中,如果今天是晴天,那么明天有70%的可能是晴天,20%的可能多云,10% 的可能下雨。如果今天是下雨天,那么明天有 50% 的可能也下雨,25% 的可能是晴天,25% 的可能是多云&lt;/li&gt;
&lt;li&gt;马尔可夫模型需要注意的点:&lt;/li&gt;
&lt;li&gt;任何一个节点引出的所有可能的总和必须等于 100%。无论是多么复杂的系统,必然会在下一步发生若干事件中的一个事件。&lt;/li&gt;
&lt;li&gt;只有当前节点的状态会影响下一个状态。&lt;/li&gt;
&lt;li&gt;有些节点可能比其他节点较难到达&lt;/li&gt;
&lt;li&gt;google的pagerank算法也是基于马尔可夫模型的, 将网站看作节点, 入站/出站链接作为节点的连线. 连接某个节点的可能性(linklihood)表示一个网站的相对关注度&lt;/li&gt;
&lt;li&gt;马尔可夫文字生成器的工作原理: 对文献中的每一个单词进行有效处理, 再建立一个二维字典, 用于统计二元词组的词频. 每次以当前单词所在节点为查询表, 选择下一个节点. 随机生成一个权重, 用词频减权重, 一旦权重减为非正数, 确定该单词为下一单词. 词频高的单词使权重减小得更厉害, 因此更容易获得&lt;/li&gt;
&lt;li&gt;在寻找有向图的最短路径问题中, 效果最好且最常用的方法是&lt;code&gt;广度优先搜索(breadth-first search, bfs)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;自然语言处理&quot;&gt;自然语言处理&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;哪些单词使用得最频繁?哪些单词用得最少?一个单词后面跟着哪几个单词?这些单词是如何组合在一起的?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;, 用于识别和标记英语文本中各词的词性.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;很擅长生成一些&lt;code&gt;统计信息&lt;/code&gt;,包括对一段文字的&lt;code&gt;单词数量&lt;/code&gt;、&lt;code&gt;单词频率&lt;/code&gt;和&lt;code&gt;单词词性&lt;/code&gt;的统计&lt;/li&gt;
&lt;li&gt;用nltk做统计分析一般从&lt;code&gt;Text&lt;/code&gt;对象开始`&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nltk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word_tokenize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nltk &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tokens &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; word_tokenize(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Here is some not very interesting text&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;text &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Text(tokens)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;word_tokenize&lt;/code&gt;函数的参数可以是任何python字符串, 其将字符串按语义分割成单词&lt;/li&gt;
&lt;li&gt;文本对象可以像普通的python list那样操作, 好像它们就是一个包含文本里所有单词的list一样.&lt;/li&gt;
&lt;li&gt;将文本对象放到一个&lt;code&gt;频率分布对象FreqDist&lt;/code&gt;中, 可查看哪些单词是最常用的, 以及单词的频率等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bigrams&lt;/code&gt;模块的&lt;code&gt;bigrams&lt;/code&gt;函数作用于文本对象, 得到一个&lt;code&gt;bigrams(2-gram)&lt;/code&gt;的生成器对象, 其也可以作为参数传入&lt;code&gt;FreqDist&lt;/code&gt;, 得到频率分布对象&lt;/li&gt;
&lt;li&gt;更一般的情况, 可以导入&lt;code&gt;ngrams&lt;/code&gt;模块, &lt;code&gt;ngrams&lt;/code&gt;函数被用来将文本对象分解成任意规模的&lt;code&gt;n-gram&lt;/code&gt;序列,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;库设计了许多不同的工具和对象来组织, 统计, 排序和度量大段文字的含义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;默认使用&lt;code&gt;宾夕法尼亚大学&lt;/code&gt;的&lt;code&gt;Penn Treebank&lt;/code&gt;项目的&lt;code&gt;语料标记&lt;/code&gt;部分&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;可以用它的超级大字典分析文本内容, 帮助寻找单词的含义. &lt;code&gt;nltk&lt;/code&gt;的一个基本功能是识别句子中各个词的词性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nltk&lt;/code&gt;用英语的上下文无关文法(context-free grammar)识别词性. 上下文无关文法基本上可以看成一个规则集合,用一个有序的列表确定一个词后面可以跟哪些词&lt;/li&gt;
&lt;li&gt;对&lt;code&gt;nltk&lt;/code&gt;进行训练, 创建一个全新的上下文无关文法规则, 比如用&lt;code&gt;Penn Treebank&lt;/code&gt;词性标记手工完成语言的大部分文本的语义标记, 将结果传给&lt;code&gt;nltk&lt;/code&gt;, 训练它对未标记文本的语义标记.&lt;/li&gt;
&lt;li&gt;网络数据采集经常需要处理搜索的问题, 比如采集的文字的词性, 举例, &lt;em&gt;&lt;strong&gt;希望采集是动词飞的fly, 而过滤掉名词苍蝇的fly&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;自然语言中的许多歧义问题都可以用&lt;code&gt;nltk&lt;/code&gt;的&lt;code&gt;pos_tag&lt;/code&gt;解决, 不只是搜索目标单词或短语, 而是搜索带标记的目标单词或短语,这样可以大大提高爬虫搜索的准确率和效率&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter9-穿越网页表单与登录窗口进行采集&quot;&gt;Chapter9 穿越网页表单与登录窗口进行采集&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;利用post方法, 将信息推送到服务器进行存储与分析&lt;/li&gt;
&lt;li&gt;页面表单基本上可以看成一种用户提交post请求的方式, 且这种请求方式是服务器能够理解和使用的&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;requests库&quot;&gt;Requests库&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Requests&lt;/code&gt;库是一个擅长处理复杂的http请求, cookie, header等内容的第三方库&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;提交一个基本表单&quot;&gt;提交一个基本表单&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;大多数网页表单都是由一些 HTML 字段、一个提交按钮、一个在表单处理完之后跳转的“执行结果”(表单属性 action 的值)页面构成。&lt;/li&gt;
&lt;li&gt;字段的名称决定了表单被确认后要被传送到服务器上的变量名称&lt;/li&gt;
&lt;li&gt;表单的真实行为发生在&lt;code&gt;action&lt;/code&gt;指定的页面&lt;/li&gt;
&lt;li&gt;HTML 表单的目的,只是帮助网站的访问者发送格式合理的请求,向服务器请求没有出现的页面&lt;/li&gt;
&lt;li&gt;大多数情况下, 提交表单只需要注意:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;提交数据的字段名称&lt;/li&gt;
&lt;li&gt;表单的&lt;code&gt;action&lt;/code&gt;属性, 即表单提交后网站会显示的页面&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;单选按钮-复选框和其他输入&quot;&gt;单选按钮, 复选框和其他输入&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;无论表单的字段看起来多么复杂,仍然只有两件事是需要关注的:&lt;code&gt;字段名称&lt;/code&gt;和&lt;code&gt;值&lt;/code&gt;。字段名称可以通过查看源代码寻找 &lt;code&gt;name&lt;/code&gt; 属性轻易获得&lt;/li&gt;
&lt;li&gt;&lt;code&gt;字段&lt;/code&gt;的值有时会比较复杂. 如果不确定一个输入字段指定数据格式, 可&lt;strong&gt;跟踪浏览器正在通过网站发出或接受的get和post请求的内容&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;跟踪get请求效果最好也最直接的手段是看网站的url链接, &lt;code&gt;?xxx=xxx&amp;amp;xxx=...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;复杂的&lt;code&gt;post&lt;/code&gt;表单, 可查看浏览器向服务器传递的参数, 用chrome的&lt;code&gt;F12&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;提交文件和图像&quot;&gt;提交文件和图像&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用&lt;code&gt;requests&lt;/code&gt;处理文件上传的方式与提交普通表单类似:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;image&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;filename&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;rb&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.post(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt; file&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;处理登录和cookie&quot;&gt;处理登录和cookie&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;大多数新式的网站都用 &lt;code&gt;cookie&lt;/code&gt; 跟踪用户是否已登录的状态信息。一旦网站验证了登录权证,它就会将它们保存在浏览器的 cookie 中,里面通常包含一个服务器生成的&lt;code&gt;令牌&lt;/code&gt;、&lt;code&gt;登录有效时限&lt;/code&gt;和&lt;code&gt;状态跟踪信息&lt;/code&gt;。网站会把这个 cookie 当作信息验证的证据,在你浏览网站的每个页面时出示给服务器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;obj.cookies.method&lt;/code&gt;从响应中获取&lt;code&gt;cookie&lt;/code&gt;, 再通过&lt;code&gt;cookies&lt;/code&gt;参数将&lt;code&gt;cookie&lt;/code&gt;发送给服务器&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;session&lt;/code&gt;对象, 可以&lt;code&gt;持续&lt;/code&gt;跟踪会话信息, 比如cookie, header, 甚至运行http协议的信息.&lt;/li&gt;
&lt;li&gt;使&lt;code&gt;session&lt;/code&gt;的方法就是, 创建&lt;code&gt;Session&lt;/code&gt;对象, 再用&lt;code&gt;Session&lt;/code&gt;对象发送请求&lt;/li&gt;
&lt;li&gt;写爬虫时, 应持续关注cookie的状态, 掌握它们在可控范围内非常重要. 这样可以避免痛苦地调试和追寻网站行为异常,节省很多时间。&lt;/li&gt;
&lt;li&gt;在cookie发明之前, 处理网站登录常用的方法是用&lt;code&gt;http基本接入认证(http basic access authentication)&lt;/code&gt;, requests库的&lt;code&gt;auth&lt;/code&gt;模块专门用来处理http认证:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.auth &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; AuthBase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.auth &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HTTPBasicAuth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;auth &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HTTPBasicAuth(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ryan&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;r &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; requests.post(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://pythonscraping.com/pages/auth/login.php&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;auth)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# HTTPBasicAuth对象作为auth参数传递到请求&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;其他表单问题&quot;&gt;其他表单问题&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;表单是网络恶意机器人(malicious bots)酷爱的网站切入点。&lt;/li&gt;
&lt;li&gt;新式的网站经常在HTML 中使用很多安全措施,让表单不能被快速穿越&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter10-采集-javascript&quot;&gt;Chapter10 采集 JavaScript&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 是网络上最常用也是支持者最多的&lt;code&gt;客户端脚本语言&lt;/code&gt;。它可以收集用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页游戏&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;javascript-简介&quot;&gt;JavaScript 简介&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript 是一种弱类型语言, 所有变量都用&lt;code&gt;var&lt;/code&gt;关键字进行定义, 可以将函数作为变量使用&lt;/li&gt;
&lt;li&gt;常用的 JavaScript 库&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;jQuery&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;一个网站使用&lt;code&gt;jQuery&lt;/code&gt;的特征是源代码里包含了&lt;code&gt;jQuery&lt;/code&gt;入口.&lt;/li&gt;
&lt;li&gt;jQuery 可以动态地创建 HTML 内容,只有在 JavaScript 代码执行之后才会显示。如果用传统的方法采集页面内容,就只能获得 JavaScript 代码执行之前页面上的内容&lt;/li&gt;
&lt;li&gt;这些页面还可能包含动画、用户交互内容和嵌入式媒体,这些内容对网络数据采集都是挑战。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Google Analytics&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果网站使用了Google Analytics,在页面底部会有类似&lt;code&gt;&amp;lt;!-- Google Analytics --&amp;gt;&lt;/code&gt;的 JavaScript 代码&lt;/li&gt;
&lt;li&gt;如果一个网站使用了 Google Analytics 或其他类似的网络分析系统,而不想让网站知道在采集数据,就要确保把那些分析工具的 cookie 或者所有 cookie 都关掉。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Google Map&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;在 Google 地图上,显示一个位置最常用的方法就是用标记&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; marker &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; google.maps.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Marker&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    position: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; google.maps.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LatLng&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;25.363882&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;131.044922&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    map: map,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    title: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Some marker text&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;取出google.maps.LatLng()里的坐标, 生成一组经纬度坐标值, 再通过google的&lt;code&gt;地理坐标反向查询&lt;/code&gt;api(&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse&quot;&gt;https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse&lt;/a&gt;), 就可以将经纬度坐标组解析成格式规范的地址, 可存储或分析&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ajax和动态html&quot;&gt;ajax和动态html&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;提交表单之后,或从服务器获取信息之后,网站的页面不需要重新刷新,那么访问的网站就在用 Ajax 技术。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ajax(Asynchronous JavaScript and XML)&lt;/code&gt; 其实并不是一门语言,而是用来完成网络任务的一系列技术, 网站不需要使用单独的页面请求就可以和网络服务器进行交互&lt;/li&gt;
&lt;li&gt;动态 HTML(dynamic HTML,&lt;code&gt;DHTML&lt;/code&gt;)也是一系列用于解决网络问题的技术集合。DHTML 是用&lt;code&gt;客户端语言&lt;/code&gt;改变页面的 HTML 元素(HTML、CSS,或者二者皆被改变). 是否使用了DHTML, 关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素&lt;/li&gt;
&lt;li&gt;有时,&lt;em&gt;&lt;strong&gt;网页用一个加载页面把你引到另一个页面上,但是网页的 URL 链接在这个过程中一直没有变化。&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;当用爬虫采集的网页内容与浏览器上显示的不同时, 是因为爬虫不能执行 JavaScript 代码, 而浏览器可以正确地执行 JavaScript .&lt;/li&gt;
&lt;li&gt;用python爬取使用了&lt;code&gt;ajax&lt;/code&gt;或&lt;code&gt;dhtml&lt;/code&gt;的页面的2种办法:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;直接从 JavaScript 代码里采集内容&lt;/li&gt;
&lt;li&gt;用第三方库运行 JavaScript , 直接采集在浏览器中看到的页面&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;selenium&quot;&gt;Selenium&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;是一个网络数据采集工具, 其最初是为&lt;code&gt;网站自动化测试&lt;/code&gt;而开发的, 近几年,它还被广泛用于获取精确的网站快照.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;可以直接运行在浏览器上, 它可以让浏览器自动加载页面, 获取需要的数据, 甚至页面截屏, 或者判断网站上某些动作是否发生&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;需要与第三方浏览器结合使用.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PhantomJS&lt;/code&gt;可以将网站加载到内存并执行页面上的 JavaScript, 但它不会向用户展示网页的图形界面.&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;Selenium&lt;/code&gt; 和 &lt;code&gt;PhantomJS&lt;/code&gt; 结合在一起,就可以运行一个非常强大的网络爬虫了,可以处理 &lt;code&gt;cookie&lt;/code&gt;、&lt;code&gt;JavaScrip&lt;/code&gt;、&lt;code&gt;header&lt;/code&gt;,以及任何需要做的事情&lt;/li&gt;
&lt;li&gt;python的&lt;code&gt;Selenium&lt;/code&gt;库是一个在&lt;code&gt;WebDriver&lt;/code&gt;上调用的api, &lt;code&gt;webdriver&lt;/code&gt;有点像加载网站的浏览器, 可以像&lt;code&gt;BeautifulSoup&lt;/code&gt;对象一样用来查找页面元素, 与页面上的元素进行交互, 以及执行其他动作来运行爬虫&lt;/li&gt;
&lt;li&gt;webdriver的page_source函数返回页面的源代码字符串. 若不想要&lt;code&gt;Selenium&lt;/code&gt;选择器, 则可以用返回的源代码创建&lt;code&gt;BeautifulSoup&lt;/code&gt;对象.&lt;/li&gt;
&lt;li&gt;页面的加载时间是不确定的, 具体依赖于服务器某一毫秒的负载情况, 以及不断变化的网速&lt;/li&gt;
&lt;li&gt;解决&lt;code&gt;页面加载时间不确定&lt;/code&gt;的有效方法是, 让&lt;code&gt;Selenium&lt;/code&gt;不断检查某个元素是否存在, 以确定页面是否完全加载, 如果页面加载成功, 就执行后续程序.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebDriverWait&lt;/code&gt;与&lt;code&gt;expected_conditions&lt;/code&gt;组合构成了&lt;code&gt;Selenium&lt;/code&gt;的隐式等待(implicit wait).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;隐式等待&lt;/code&gt;与&lt;code&gt;显式等待&lt;/code&gt;的不同之处在于,隐式等待是等 DOM 中某个状态发生后再继续运行代码(没有明确的等待时间,但是有最大等待时限,只要在时限内就可以),而显式等待明确设置了等待时间。在隐式等待中,DOM 触发的状态用expected_conditions 定义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;中元素被触发的期望条件有许多种, 包括&lt;/li&gt;
&lt;li&gt;弹出一个提示框&lt;/li&gt;
&lt;li&gt;一个元素被选中(比如文本框)&lt;/li&gt;
&lt;li&gt;页面的标题改变了,或者某个文字显示在页面上或者某个元素里&lt;/li&gt;
&lt;li&gt;一个元素在 DOM 中变成可见的,或者一个元素从 DOM 中消失了&lt;/li&gt;
&lt;li&gt;大多数期望的条件在使用前需要指定等待的目标元素, 元素用&lt;code&gt;定位器&lt;/code&gt;指定.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;定位器&lt;/code&gt;是一种抽象的查询语言,用 &lt;code&gt;By&lt;/code&gt; 对象表示,可以用于不同的场合,包括创建选择器(driver.find_element(By.ID, “content”))&lt;/li&gt;
&lt;li&gt;如果可以不用定位器, 就不用, 毕竟可以少导入一个模块&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xpath(XML Path)&lt;/code&gt;是在xml文档中导航和选择元素的查询语言. &lt;code&gt;BeautifulSoup&lt;/code&gt;不支持&lt;code&gt;Xpath&lt;/code&gt;. 使用方法通常和css选择器一样.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xpath&lt;/code&gt;语法中四个重要概念:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;根节点与非根节点&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;/div 选择 div 节点,只有当它是文档的根节点时&lt;/li&gt;
&lt;li&gt;//div 选择文档中所有的 div 节点(包括非根节点)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;通过属性选择节点&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;//@href 选择带 href 属性的所有节点&lt;/li&gt;
&lt;li&gt;//a[@href=‘&lt;a href=&quot;http://google.com%E2%80%99&quot;&gt;http://google.com’&lt;/a&gt;] 选择页面中所有指向 Google 网站的链接&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;通过位置选择节点&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;//a[3] 选择文档中的第三个链接&lt;/li&gt;
&lt;li&gt;//table[last()] 选择文档中的最后一个表&lt;/li&gt;
&lt;li&gt;//a[position() &amp;lt; 3] 选择文档中的前三个链接&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;*(星号)匹配任意字符或节点, 可以在不同条件下使用&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;//table/tr/* 选择所有表格行 tr 标签的所有的子节点(这很适合选择 th 和 td 标签)&lt;/li&gt;
&lt;li&gt;//div[@*] 选择带任意属性的所有 div 标签&lt;/li&gt;
&lt;li&gt;微软的Xpath语法页面&lt;a href=&quot;https://msdn.microsoft.com/en-us/enus/library/ms256471&quot;&gt;https://msdn.microsoft.com/en-us/enus/library/ms256471&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;处理重定向&quot;&gt;处理重定向&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;客户端重定向&lt;/code&gt;是在服务器将页面内容发送到浏览器之&lt;strong&gt;前&lt;/strong&gt;,由浏览器执行 JavaScript 完成的页面跳转,而不是服务器完成的跳转&lt;/li&gt;
&lt;li&gt;&lt;code&gt;客户端重定向&lt;/code&gt;是&lt;code&gt;redirect&lt;/code&gt;, 即服务器在返回的响应中告知客户端请求资源的正确url, 并设置状态码&lt;code&gt;3xx&lt;/code&gt;. 客户端根据响应结果对新的url发起请求&lt;/li&gt;
&lt;li&gt;&lt;code&gt;服务器重定向&lt;/code&gt;是&lt;code&gt;dispatch&lt;/code&gt;, 指服务器在处理请求的过程中将请求先后分发(委托)给其他部件处理的过程. 服务器内部如何操作, 不能知道也不需要知道吧&lt;/li&gt;
&lt;li&gt;&lt;code&gt;服务器重定向&lt;/code&gt;可通过python的urllib库解决&lt;/li&gt;
&lt;li&gt;利用&lt;code&gt;Selenium&lt;/code&gt;执行 JavaScript 代码, 解决客户端重定向问题. 关键点在于何时停止页面监控. 一种方法是: 从页面开始加载时就“监视”DOM 中的一个元素,然后重复调用这个元素直到Selenium抛出一&lt;code&gt;个StaleElementReferenceException&lt;/code&gt;异常;也就是说,元素不在页面的 DOM 里了,说明这时网站已经跳转&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter11-图像识别与文字处理&quot;&gt;Chapter11 图像识别与文字处理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当不想让文字被爬虫采集时, 将文字做成图片放在网页上是常用的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ocroptical-character-recognition-光学文字识别&quot;&gt;OCR(Optical Character Recognition, 光学文字识别)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;利用&lt;code&gt;Pillow&lt;/code&gt;完成图片的预处理, 让机器可以更方便地读取图片&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tesseract&lt;/code&gt;是一个ocr库, 高精确度, 高灵活性, 可以通过训练识别出任何字体以及任何unicode字符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tesseract&lt;/code&gt;是一个python的命令行工具, 通过&lt;code&gt;tesseract&lt;/code&gt;命令在python外运行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NumPy&lt;/code&gt;库具有大量线性代数以及大规模科学计算的方法, 其可以用数学方法将图片表示成巨大的像素数组&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;处理格式规范的文字&quot;&gt;处理格式规范的文字&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;格式规范的文字的特点:&lt;/li&gt;
&lt;li&gt;使用一个标准字体(不包含手写体、草书,或者十分“花哨的”字体)&lt;/li&gt;
&lt;li&gt;虽然被复印或拍照,字体还是很清晰,没有多余的痕迹或污点&lt;/li&gt;
&lt;li&gt;排列整齐,没有歪歪斜斜的字&lt;/li&gt;
&lt;li&gt;没有超出图片范围,也没有残缺不全,或紧紧贴在图片的边缘&lt;/li&gt;
&lt;li&gt;对于难于辨认的图片, 先对图片进行预处理, 可大大提高图文转换的正确率&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tesseract&lt;/code&gt;最大的缺点是对渐变背景色的处理, 它的算法在读取文件之前自动尝试调整图片对比度&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Selenium&lt;/code&gt; + &lt;code&gt;Tesseract&lt;/code&gt;从网站图片中抓取文- 对于难于辨认的图片, 先对图片进行预处理, 可大大提高图文转换的正确率&lt;/li&gt;
&lt;li&gt;通过给 &lt;code&gt;Tesseract&lt;/code&gt; 提供大量已知的文字与图片映射集,经过训练 &lt;code&gt;Tesseract&lt;/code&gt; 就可以“学会”识别同一种字体,而且可以达到极高的精确率和准确率,甚至可以忽略图片中文字的背景色和相对位置等问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;读取验证码与训练tessract&quot;&gt;读取验证码与训练Tessract&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart, 全自动区分计算机与人类的图灵测试)&lt;/code&gt;, 简称验证码, 其目的是为了阻止网络访问&lt;/li&gt;
&lt;li&gt;要训练&lt;code&gt;Tesseract&lt;/code&gt;识别一种文字, 都需要向其提供每个字符不同形式的样本:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;将大量验证码样本下载到一个目录, 样本数量由验证码的复杂程度决定.(用验证码的真实结果给每个样本文件命名)&lt;/li&gt;
&lt;li&gt;准确地告诉tesseract一张图片中的每个字符是什么, 以及每个字符的具体位置. 这时要创建一些矩形定位文件, 一个验证码图片生成一个矩形定位文件(字符 + 包围字符的最小矩形的左下角和右上角坐标 + 图片样本的编号)(&lt;a href=&quot;http://pp19dd.com/tesseract-ocr-chopper/&quot;&gt;http://pp19dd.com/tesseract-ocr-chopper/&lt;/a&gt; 在线转换工具)&lt;/li&gt;
&lt;li&gt;矩形定位文件必须保存为一个&lt;code&gt;.box&lt;/code&gt;的文本文件, 同样用验证码的实际结果命名&lt;/li&gt;
&lt;li&gt;备份&lt;/li&gt;
&lt;li&gt;用同时包含验证码图片和.box文件的目录, 创建训练文件, 即.tessdata文件&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;获取验证码提交答案&quot;&gt;获取验证码提交答案&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;大多数网站生成的验证码图片都具有以下属性。&lt;/li&gt;
&lt;li&gt;它们是服务器端的程序动态生成的图片。验证码图片的 src 属性可能和普通图片不太一样,但是可以和其他图片一样进行下载和处理。&lt;/li&gt;
&lt;li&gt;图片的答案存储在服务器端的数据库里。&lt;/li&gt;
&lt;li&gt;很多验证码都有时间限制,如果你太长时间没解决就会失效。虽然这对网络机器人来说不是什么问题,但是如果你想保留验证码的答案一会儿再使用,或者想通过一些方法延长验证码的有效时限,可能很难成功。&lt;/li&gt;
&lt;li&gt;常用的处理方法就是,首先把验证码图片下载,清理干净,然后用 Tesseract 处理图片,最后返回符合网站要求的识别结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter12-避开采集陷阱&quot;&gt;Chapter12 避开采集陷阱&lt;/h3&gt;
&lt;h4 id=&quot;道德规范&quot;&gt;道德规范&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在采集那些不想被采集的网站时, 可能存在一些非常符合道德和法律规范的理由&lt;/li&gt;
&lt;li&gt;大多数网络机器人一开始都只能做一些宽泛的信息和漏洞扫描&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;让网络机器人看起来像人类用户&quot;&gt;让网络机器人看起来像人类用户&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests&lt;/code&gt;模块除了处理网站的表单, 还是设置请求首部的利器.&lt;/li&gt;
&lt;li&gt;7个被大多数浏览器用来初始化所有网络请求的请求报文首部字段:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Host            : &lt;a href=&quot;http://kissg.me&quot;&gt;http://kissg.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Connection      : keep-alive&lt;/li&gt;
&lt;li&gt;Accept          : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp&lt;/li&gt;
&lt;li&gt;User-Agent      : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, likeGecko) Chrome/39.0.2171.95 Safari/537.36&lt;/li&gt;
&lt;li&gt;Referrer        : &lt;a href=&quot;https://kissg.me/&quot;&gt;https://kissg.me/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Accept-Encoding : gzip,deflate,sdch&lt;/li&gt;
&lt;li&gt;Accept-Language : en-US,en&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;经典的python爬虫在使用urllib标准库时, 都会发送&lt;code&gt;Accept-Encoding=identity;User-Agent=Python-urllib/3.4&lt;/code&gt;的请求头&lt;/li&gt;
&lt;li&gt;虽然网站可能会对http请求首部的每个字段进行“是否具有人性”的检查, 但最重要的参数是“User-Agent”&lt;/li&gt;
&lt;li&gt;在处理一些警觉性非常高的网站, 要注意经常用却很少检查的首部字段, 比如&lt;code&gt;Accept-Language&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;请求头的不同, 可以让网站改变内容的布局, 比如&lt;code&gt;User-Agent&lt;/code&gt;设置成移动设备的首部, &lt;code&gt;Accpet-Language&lt;/code&gt;设置成另一种语言&lt;/li&gt;
&lt;li&gt;可 以 调 用 &lt;code&gt;delete_cookie()&lt;/code&gt; 、 &lt;code&gt;add_cookie()&lt;/code&gt; 和 &lt;code&gt;delete_all_cookies()&lt;/code&gt; 方 法 来 处 理cookie。另外,还可以保存 cookie 以备其他网络爬虫使用&lt;/li&gt;
&lt;li&gt;有时候需要增加时延, 来使爬虫伪装得更像人类&lt;/li&gt;
&lt;li&gt;多线程爬虫, 一个线程处理数据, 另一个加载数据(我以前想的提高爬取速度的方法大致也是这样)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;常见表单安全措施&quot;&gt;常见表单安全措施&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在html表单中, &lt;code&gt;隐含&lt;/code&gt;字段可以让字段对浏览器可见, 而对用户不可见. 随着越来越多的网站开始用 cookie 存储状态变量来管理用户状态,在找到另一个最佳用途之前,隐含字段主要用于阻止爬虫自动提交表单&lt;/li&gt;
&lt;li&gt;用隐含字段阻止网络数据采集的2种方式:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;使表单页面上的一个字段可以用服务器生成的随机变量表示. 解决办法就是先采集表单页面上的这个随便变量, 再与其他表单数据一并提交&lt;/li&gt;
&lt;li&gt;&lt;code&gt;蜜罐(honey pot)&lt;/code&gt;, 故意设一个隐含字段, 服务器将自动忽略这些字段的真实值, 但所有填写了隐含字段的用户可能被封杀. 说白了, 就是为爬虫故意设的字段, 一般用户看不到, 也就不会填&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果隐含字段带有较大的随机字符串变量, 则可能网络服务器会在表单提交时检查它们. 另外还有一些检查, 用来保证当前生成的表单变量只被使用一次或是最近生成的&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html+css&lt;/code&gt;隐藏元素的方法:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;通过简单css属性设置进行隐藏&lt;/li&gt;
&lt;li&gt;隐含输入字段&lt;/li&gt;
&lt;li&gt;设置元素的位置超出浏览器边界, 并隐藏滚动条&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;可以获取访问页面的内容, 因此它可以区分页面上的可见与不可见元素, 通过&lt;code&gt;is_displayed()&lt;/code&gt;可以判断元素在页面上是否可见. 连第3种方式隐藏的元素都能被揪出来!&lt;/li&gt;
&lt;li&gt;在提交表单之前, 确认已经在表单中, 准备提交的隐含字段的值(或者让Selenium自动提交)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;问题检查表&quot;&gt;问题检查表&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;首先,如果从网络服务器收到的页面是空白的,缺少信息,或其遇到他不符合预期的情况(或者不是在浏览器上看到的内容),有可能是因为网站创建页面的 JavaScript执行有问题&lt;/li&gt;
&lt;li&gt;如果准备向网站提交表单或发出 POST 请求,记得检查一下页面的内容,看看想提交的每个字段是不是都已经填好,而且格式也正确。用 chrome 浏览器的网络面板(快捷键 f12 打开开发者控制台,然后点击“network”即可看到)查看发送到网站的 POST命令,确认每个参数都是正确的。&lt;/li&gt;
&lt;li&gt;如果已经登录网站却不能保持登录状态,或者网站上出现了其他的“登录状态”异常,请检查 cookie。确认在加载每个页面时 cookie 都被正确调用,而且 cookie 在每次发起请求时都发送到了网站上。&lt;/li&gt;
&lt;li&gt;如果在客户端遇到了 HTTP 错误,尤其是 403 禁止访问错误,这可能说明网站已经把此 IP 当作机器人了,不再接受任何请求。要么等待 IP 地址从网站黑名单里移除,要么就换个 IP 地址. 如果你确定自己并没有被封杀,那么再检查下面的内容。&lt;/li&gt;
&lt;li&gt;确认爬虫在网站上的速度不是特别快。&lt;code&gt;快速采集是一种恶习&lt;/code&gt;,会对网管的服务器造成沉重的负担,还会让自己陷入违法境地,也是 IP 被网站列入黑名单的首要原因。给爬虫增加延迟,让它们在夜深人静的时候运行。切记:&lt;code&gt;匆匆忙忙写程序或收集数据都是拙劣项目管理的表现&lt;/code&gt;;&lt;code&gt;应该提前做好计划,避免临阵慌乱&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;还有一件必须做的事情:修改你的请求头!有些网站会封杀任何声称自己是爬虫的访问者。如果不确定请求头的值怎样才算合适,就&lt;code&gt;用浏览器的请求头&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;确认没有点击或访问任何人类用户通常不能点击或接入的信息&lt;/li&gt;
&lt;li&gt;如果用了一大堆复杂的手段才接入网站,考虑联系一下网管吧,告诉他们目的。试试发邮件到 webmaster@&amp;lt; 域名 &amp;gt; 或 admin@&amp;lt; 域名 &amp;gt;,请求网管允许你使用爬虫采集数据。管理员也是人嘛!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;用爬虫测试网站&quot;&gt;用爬虫测试网站&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当研发一个技术栈较大的网络项目时,经常只对栈底(项目后期用的技术)进行一些常规测试.&lt;/li&gt;
&lt;li&gt;网站通常是不同标记语言额编程语言的大杂烩. 这使网站的前端测试自动化变得困难. 比如, 为 JavaScript 部分写单元测试,但没什么用,如果 JavaScript 交互的 HTML 内容改变了,那么即使 JavaScript 可以正常地运行,也不能完成网页需要的动作&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;测试简介&quot;&gt;测试简介&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;一个单元测试通常包含以下特点:&lt;/li&gt;
&lt;li&gt;每个单元测试用于测试一个组件(component). 通常一个组件的所有单元测试都集成在同一个类里&lt;/li&gt;
&lt;li&gt;每个单元测试都可以完全独立地运行, 一个单元测试需要的所有启动(setup)和卸载(teardown)都必须通过这个单元测试本身去处理. 单元测试不能对其他测试造成干扰, 而且无论按何种顺序排列, 都必须能够正常运行&lt;/li&gt;
&lt;li&gt;每个单元测试通常至少含有一个断言&lt;/li&gt;
&lt;li&gt;单元测试与生产代码是分离的. 虽然它们需要导入然后在待测试的代码中使用,但是它们一般被保留在独立的类和目录中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;python单元测试&quot;&gt;python单元测试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;python的单元测试模块&lt;code&gt;unittest&lt;/code&gt;. 只要先导入模块然后继承&lt;code&gt;unittest.TestCase&lt;/code&gt;类, 就可实现:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;为每个单元测试的开始和结束提供&lt;code&gt;setUp&lt;/code&gt;和&lt;code&gt;tearDown&lt;/code&gt;函数&lt;/li&gt;
&lt;li&gt;提供不同类型的&lt;code&gt;断言&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将所有以&lt;code&gt;test_&lt;/code&gt;开头的函数当作单元测试运行, 忽略不带&lt;code&gt;test_&lt;/code&gt;的函数&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setUpClass&lt;/code&gt;函数只在类的初始化阶段运行一次, &lt;code&gt;setUp&lt;/code&gt;函数在每个测试启动时都运行.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;selenium单元测试&quot;&gt;Selenium单元测试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;是一个可以解决网站上各种复杂问题的优秀测试框架, 初衷就是用来做网站测试&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;可以在浏览器上做任何事情, 包括输入, 点击等, 这样就可以找出异常表单, JavaScript 代码错误, html排版错误, 以及其他用户使用过程中可能出现的问题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Selenium&lt;/code&gt;测试的关键是&lt;code&gt;element&lt;/code&gt;. 可以对任何给定元素做许多操作, 如:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;myElement.click()&lt;/li&gt;
&lt;li&gt;myElement.click_and_hold()&lt;/li&gt;
&lt;li&gt;myElement.release()&lt;/li&gt;
&lt;li&gt;myElement.double_click()&lt;/li&gt;
&lt;li&gt;myElement.send_keys_to_element(“content to enter”()&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;为了一次性完成一个元素的多个操作, 可以用&lt;code&gt;行为连(action chain)&lt;/code&gt;储存多个操作, 然后在程序中执行多次&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.drag_and_drop&lt;/code&gt;实现拖拽, 可用于一部分登录验证&lt;/li&gt;
&lt;li&gt;&lt;code&gt;driver.get_screenshot_as_file(&quot;xxx&quot;)&lt;/code&gt;截屏&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;python单元测试与selenium单元测试的选择&quot;&gt;Python单元测试与Selenium单元测试的选择&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Python 的单元测试语法严谨冗长,更适合为大多数大型项目写测试&lt;/li&gt;
&lt;li&gt;Selenium 的测试方式灵活且功能强大, 可以成为一些网站功能测试的首选&lt;/li&gt;
&lt;li&gt;用Selenium可以轻易获取网站的信息, 而单元测试可以评估信息是否满足测试条件, 二者组合是最佳搭档&lt;/li&gt;
&lt;li&gt;任何网站上能看到的内容都可以通过Python的单元测试和Selenium组合来测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;chapter14-远程采集&quot;&gt;Chapter14 远程采集&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当你停止在自己的笔记本上运行python爬虫, 生活会变得更加轻松&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;为什么要用远程服务器&quot;&gt;为什么要用远程服务器&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;建立爬虫的第一原则是: &lt;code&gt;所有信息都可以伪造&lt;/code&gt;. 可以用非本人的邮箱发送邮件, &lt;code&gt;通过命令自动化鼠标行为&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;阻止网站被采集的注意力主要集中在识别人类与机器人的行为差异上面&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tor代理服务器&quot;&gt;Tor代理服务器&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;洋葱路由(The Onion Router)&lt;/code&gt;网络,是一种&lt;code&gt;IP地址匿名&lt;/code&gt;手段。通过不同服务器构成多个层(就像洋葱)把客户端包在最里面。数据进入网络之前会被加密,因此任何服务器都不能偷取通信数据&lt;/li&gt;
&lt;li&gt;虽然 Tor 网络可以让你访问网站时显示的 IP 地址是一个不能跟踪到你的 IP 地址,但是你在网站上留给服务器的任何信息都会暴露你的身份。&lt;/li&gt;
&lt;li&gt;登录 Tor 网络不是一个自动的匿名措施,也不能让你进入互联网上任何区域。虽然它是一个实用的工具,但是用它的时候一定要谨慎、清醒,并且遵守道德规范&lt;/li&gt;
&lt;li&gt;当你用 Tor 的时候网速会变慢。这是因为代理有可能要先在全世界网络上转几次才到目的地!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tor&lt;/code&gt;服务必须运行在&lt;code&gt;9150&lt;/code&gt;端口上&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PySocks&lt;/code&gt;是一个代理服务器通信模块, 可与&lt;code&gt;tor&lt;/code&gt;配合使用&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;tor&lt;/code&gt;中使用&lt;code&gt;selenium&lt;/code&gt;和&lt;code&gt;phantomjs&lt;/code&gt;, 不需要&lt;code&gt;pysocks&lt;/code&gt;, 只要保证tor在运行, 然后增加&lt;code&gt;service_args&lt;/code&gt;参数设置代理端口, 让&lt;code&gt;selenium&lt;/code&gt;通过端口&lt;code&gt;9150&lt;/code&gt;连接网站就可以了&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;互联网其实就是一个用户界面不太友好的超级API&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-zen-of-python&quot;&gt;The Zen of Python&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea – let’s do more of those!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;优美胜于丑陋
明了胜于隐晦
简洁胜于复杂
复杂胜于混乱
扁平胜于嵌套
宽松胜于紧凑
可读性很重要
即便是特例,也不可违背这些规则
虽然现实往往不那么完美
但是不应该放过任何异常
除非你确定需要如此
如果存在多种可能,不要猜测
肯定有一种——通常也是唯一一种——最佳的解决方案
虽然这并不容易,因为你不是Python之父 1
动手比不动手要好
但不假思索就动手还不如不做
如果你的方案很难懂,那肯定不是一个好方案
如果你的方案很好懂,那肯定是一个好方案
命名空间非常有用,我们应当多加利用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;互联网简介&quot;&gt;互联网简介&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;互联网是一种信息交换形式&lt;/li&gt;
&lt;li&gt;当http从浏览器收到一个数据包, 数据包的内容一定被看成一个网站, 网站的结构由html构成&lt;/li&gt;
&lt;li&gt;虽然html通常被看成是编程语言, 但它实际是一个标记语言, 通过标签定义文档等结构以确定各个元素&lt;/li&gt;
&lt;li&gt;css是配合html对网站样式进行定义的语言, 其为网站对象定义颜色, 位置, 尺寸和背景等属性&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;网络数据采集的法律和道德约束&quot;&gt;网络数据采集的法律和道德约束&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;知识产权分3种基本类型: 商标(^TM或®), 版权(©), 专利.&lt;/li&gt;
&lt;li&gt;专利用来声明内容所有权仅属于发明者&lt;/li&gt;
&lt;li&gt;商标是一个单词, 词组, 符号或设计, 用来识别和区分一种商品的来源. 商标的所有权很大程度上由使用场景决定&lt;/li&gt;
&lt;li&gt;只要将一件东西带到世间, 它就自动受到版权法的保护&lt;/li&gt;
&lt;li&gt;版权保护只涉及创造性的作品, 不涉及统计数据或事实, 而许多爬虫采集的都是事实和统计数据. 如果数据是事实性的信息, 那么完全照搬也不违反版权法&lt;/li&gt;
&lt;li&gt;如果满足下列条件, 爬虫算侵犯动产:&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;缺少许可. 如果网站的服务协议条款明确禁止使用爬虫&lt;/li&gt;
&lt;li&gt;造成实际伤害.&lt;/li&gt;
&lt;li&gt;故意而为- -&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;只有三个条件都满足, 才算侵犯动产.&lt;/li&gt;
&lt;li&gt;如果网速正常, 即使一台pc也可以给许多网站造成沉重负担.&lt;/li&gt;
&lt;li&gt;真的没理由去伤害别人的网站&lt;/li&gt;
&lt;li&gt;可能可以在大型网站的根目录找到&lt;code&gt;robots.txt&lt;/code&gt;(我真的在维基百科上找到了!)&lt;/li&gt;
&lt;li&gt;robots.txt 文件可以被程序轻易地解析和使用, rebots.txt 文件的语法没有标准格式。它是一种业内惯用的做法. rebots.txt 文件并不是一个强制性约束.&lt;/li&gt;
&lt;li&gt;robots.txt第一行非注释内容是 User-agent: ,注明具体哪些机器人需要遵守规则。后面是一组规则 Allow: 或 Disallow: ,决定是否允许机器人访问网站的该部分内容. 如果一条规则后面跟着一个与之矛盾的规则,则按后一条规则执行.&lt;/li&gt;
&lt;li&gt;google爬虫采集网站时, 会为网站留一个副本, 然后放在互联网上, 任何人可接入这些缓存. &lt;a href=&quot;http://webcache.googleusercontent.com/search?q=cache:YourURL&quot;&gt;http://webcache.googleusercontent.com/search?q=cache:YourURL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>Python</category></item><item><title>How to Solve it?</title><link>https://lifelonglove.cn/notes/ci-how-to-solve-it/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/ci-how-to-solve-it/</guid><description>用目标、现状、差距、方案和行动的结构，整理一套可重复使用的问题解决方法。</description><pubDate>Sun, 22 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;路漫漫其修远兮 吾将上下而求索&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;上周, 学校组织了一场比较大型的实习双选会. 之前一直在做&lt;strong&gt;企业筛选&lt;/strong&gt;, &lt;strong&gt;简历准备&lt;/strong&gt;的工作, 没来得及对最近学的&lt;code&gt;Python&lt;/code&gt;知识做一个总结回顾. 用一篇&lt;code&gt;职慧&lt;/code&gt;的听课记录代替.\
(&lt;a href=&quot;#content&quot;&gt;直接看正文&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;开始之前, 先对&lt;code&gt;职慧&lt;/code&gt;做个简单介绍吧.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;职慧, 是一个公益组织, “致力于提升中国大学生的职业素养, 使他们成长为具有诚信品格, 专业精神和社会责任的青年英才”. 应试教育下成长起来的中国大学生除了应付考试, 对自我, 社会和职场知之甚少. 职慧希望让所有大学生都有机会享受到高质量的职场能力提升培训和辅导.\
(以上文字摘自“职慧”微信公众号, 有兴趣的同学可以微信搜索“livetogive”, 个人认为推文不错)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单地说, &lt;strong&gt;职慧&lt;/strong&gt;是一个培养大学生职场能力的公益组织, 这一点从他们的课程安排也可以看出来: 简历制作, 职场礼仪, 解决问题……&lt;/p&gt;
&lt;p&gt;我们学校至今只申请到一堂&lt;strong&gt;职慧&lt;/strong&gt;的课, 而我有幸去听了, 感觉老师讲得很好. 现将上课内容整理出来与大家分享, 希望能有所帮助.&lt;/p&gt;
&lt;p id=&quot;content&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;那堂课的主题是&lt;strong&gt;解决问题&lt;/strong&gt;. 老师将解决问题的过程, 概括为了4个字: &lt;strong&gt;望&lt;/strong&gt;, &lt;strong&gt;闻&lt;/strong&gt;, &lt;strong&gt;问&lt;/strong&gt;, &lt;strong&gt;切&lt;/strong&gt;. 本文也将顺着这个思路展开.&lt;/p&gt;
&lt;h3 id=&quot;望&quot;&gt;望&lt;/h3&gt;
&lt;p&gt;所谓&lt;strong&gt;望&lt;/strong&gt;, 即&lt;strong&gt;定义问题&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;在定义问题之前, 其实有必要先对**“问题”&lt;strong&gt;下一个定义. 什么是问题? 在英语里, &lt;strong&gt;question&lt;/strong&gt;和&lt;/strong&gt;problem&lt;strong&gt;都可以表示问题. 但明显, 此处所谓的问题是problem, 而非question. question, 更偏向于疑惑, 相应地, 与之搭配的动词是&lt;/strong&gt;answer**, 即解答; 而problem的定义是——&lt;strong&gt;现状&lt;/strong&gt;与&lt;strong&gt;期望&lt;/strong&gt;的&lt;strong&gt;差距&lt;/strong&gt;——相应地, 与之搭配的动词是&lt;strong&gt;solve&lt;/strong&gt;, 即解决.&lt;/p&gt;
&lt;p&gt;知道了&lt;strong&gt;问题&lt;/strong&gt;的概念之后, 再来定义问题, 思路就相对清晰了. 既然问题是“差距”, 那么&lt;strong&gt;定义问题&lt;/strong&gt;就是将这差距给描述出来. 一种好的方法是, 采用&lt;strong&gt;量化&lt;/strong&gt;的思想, 即为现状和期望都设一个恰当的数值, 中间的差值就是问题所在.&lt;/p&gt;
&lt;p&gt;举个简单的例子. 一开始我们定义的问题可能是这样的: &lt;code&gt;这个程序跑得太~慢了, 我希望它能跑得更快&lt;/code&gt;. 用&lt;strong&gt;量化差距&lt;/strong&gt;的方法来修改问题定义, 可以是这样: &lt;code&gt;这个程序执行一次要200ms, 我希望它能在100ms以内跑完&lt;/code&gt;. 与模糊的&lt;strong&gt;快&lt;/strong&gt;和&lt;strong&gt;慢&lt;/strong&gt;相比, 用时间间隔来刻画现状与期望, 一眼就能看出问题所在.&lt;/p&gt;
&lt;p&gt;一个更具有普适性的例子是: &lt;code&gt;我每天都睡太迟了, 今天要早点睡 -(量化)-&amp;gt; 我每天都凌晨1点才睡着, 今天要11点就睡.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;此外, 有必要指出&lt;strong&gt;选项不是问题&lt;/strong&gt;, 即类似&lt;strong&gt;这个功能是用for循环呢, 还是用递归呢&lt;/strong&gt;(晚饭是吃面呢, 还是吃饭呢), 是一个选项, 而非问题. 你可以选择用for循环, 或者叫递归, 全凭心情(当然, 考虑到代码实现的难易, 可读性和程序效率, 你会有所取舍的). 但明显, 这个“问题”本身并不涉及现状与期望, 也就无从谈差距了.&lt;/p&gt;
&lt;h3 id=&quot;闻&quot;&gt;闻&lt;/h3&gt;
&lt;p&gt;所谓&lt;strong&gt;闻&lt;/strong&gt;, 即&lt;strong&gt;分析问题, 找出原因&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;针对“闻”, 老师介绍了2个工具——&lt;code&gt;鱼骨图&lt;/code&gt;和&lt;code&gt;五个为什么&lt;/code&gt;——一个用于列出所有原因, 一个用于归纳主要原因.&lt;/p&gt;
&lt;p&gt;先看看&lt;strong&gt;鱼骨图&lt;/strong&gt;, 它的大致形状如下所示: 鱼头指向当前现状, “脊椎”两侧的“大鱼刺”代表大方面的原因, “大鱼刺”上的各“小鱼刺”则表示更细的原因.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-05-22-ci-how-to-solve-it/fish-bone.png&quot; alt=&quot;鱼骨图&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看出, 鱼骨图可以非常清晰地显示出导致当前现状的各方面原因. 这也是使用鱼骨图的关键所在——尽可能列出所有原因.&lt;/p&gt;
&lt;p&gt;而&lt;strong&gt;五个为什么&lt;/strong&gt;, 以一种层层深入地提问的方式, 找出根因所在. 来看一个具体例子吧(一下子很难想到编程相关的例子, 以下摘自&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E4%BA%94%E4%B8%AA%E4%B8%BA%E4%BB%80%E4%B9%88&quot;&gt;维基百科&lt;/a&gt;, 有删改):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情景: 汽车无法启动了.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Q: 为什么汽车无法启动?\
A: 因为电池电量耗尽了.&lt;/li&gt;
&lt;li&gt;Q: 为什么电池电量会耗尽?\
A: 因为交流发电机不能正常工作.&lt;/li&gt;
&lt;li&gt;Q: 为什么发电机不能正常工作?\
A: 因为交流发电机的皮带断裂了.&lt;/li&gt;
&lt;li&gt;Q: 为什么皮带会断裂?\
A: 因为交流发电机皮带远远超出了其使用寿命, 从未更换过.&lt;/li&gt;
&lt;li&gt;Q: 为什么超出了使用寿命, 却从不更换?\
A: 因为我一直没有按照厂家推荐的保养计划对汽车进行过保养和维护.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上述例子, 已经能看出来, 五个为什么就是通过不断地&lt;strong&gt;追问&lt;/strong&gt;来找出根本原因. 一般到第五问时, 根因差不多也就出来了. 当然具体要追问多少次, 还得情况而定.&lt;/p&gt;
&lt;h3 id=&quot;问&quot;&gt;问&lt;/h3&gt;
&lt;p&gt;所谓&lt;strong&gt;问&lt;/strong&gt;, 即&lt;strong&gt;产生解决方案&lt;/strong&gt;, 用到的工具可以是&lt;code&gt;头难风暴&lt;/code&gt;. 这也是我第一次听到这个名词, 了解之后, 发现确实很有用.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;头脑风暴法（英语：Brainstorming），又称为脑力激荡法，是一种为激发创造力、强化思考力而设计出来的一种方法。可以由一个人或一组人进行。参与者围在一起，随意将脑中和研讨主题有关的见解提出来，然后再将大家的见解重新分类整理。在整个过程中，无论提出的意见和见解多么可笑、荒谬，其他人都不得打断和批评，从而产生很多的新观点和问题解决方法\
——&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E8%85%A6%E5%8A%9B%E6%BF%80%E7%9B%AA%E6%B3%95&quot;&gt;维基百科-头脑风暴&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单地说, 头脑风暴就是一个&lt;strong&gt;群策&lt;/strong&gt;的过程, 日常的讨论算是广义的头脑风暴吧. 进行头脑风暴, 有几个要点:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设定一个主题&lt;/li&gt;
&lt;li&gt;设定时间限制&lt;/li&gt;
&lt;li&gt;列出所有意见&lt;/li&gt;
&lt;li&gt;激励大家参与&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不做评价&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;就“不做评价”做一个简单说明, 头脑风暴的目的是尽可能多地产生解决方法, 仅此而已, 评价筛选的工作在&lt;strong&gt;确定方案&lt;/strong&gt;阶段进行. 若提早对某一方案做评价(一般会是批判性的), 将影响参与者的积极性, 一定程度上也会限制意见的创新. 当时我们头脑风暴主题是“如何写学术论文”, 连“抄袭”都出来了, 不过大家也就默契地一笑置之.&lt;/p&gt;
&lt;h3 id=&quot;切&quot;&gt;切&lt;/h3&gt;
&lt;p&gt;所谓&lt;strong&gt;切&lt;/strong&gt;, 就是&lt;strong&gt;确定解决方案&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;一种可行的工具是&lt;code&gt;评价矩阵&lt;/code&gt;, 候选的解决方案就是上一阶段头脑风暴的结果, 还需要设置一个或多个评价标准, 大概的样子如下所示:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/2016-05-22-ci-how-to-solve-it/recursion-loop.png&quot; alt=&quot;Fibonacci number in recursion and loop&quot;&gt;&lt;/p&gt;
&lt;p&gt;由评价矩阵可知, 虽然循环和递归方式实现Fibonacci数列, 两者各项得分有所不同, 但按总分来看, 可以优先选择循环的方式(当然更好的构造Fibonacci数列的方式可能是生成器).&lt;/p&gt;
&lt;p&gt;根据使用场景的不同, 还可以为各项评价标准设定不同的权重, 比如就上述例子而言, 若重点考虑空间消耗, 可设其权重为2或更多, 其他评价标准权重为1或其他值.&lt;/p&gt;
&lt;p&gt;另外, 得分越高越优还是越低越优, 可以根据个人喜好来, 但务必保持一致. 比如有些同学喜欢用小的数值表示消耗低, 而效率则喜欢用大的数值表示高效性, 这就造成了混乱, 最后究竟是得分高者为优还是低的为优? 这是个问题.&lt;/p&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;至此, 解决问题的一套流程算是跑完了. 让我们简单梳理一下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义问题 - 问题, 本身就被定义为&lt;code&gt;现状与期望的差距&lt;/code&gt;, 因此一种行之有效的定义问题的方法是: &lt;code&gt;量化差距&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;分析原因 - 用&lt;code&gt;鱼骨图&lt;/code&gt;列出所有原因, 或用&lt;code&gt;五个为什么&lt;/code&gt;找出根本原因&lt;/li&gt;
&lt;li&gt;产生解决方案 - 可以用&lt;code&gt;头脑风暴&lt;/code&gt;的方式产生尽可能多的解决方案, 注意不要做评价&lt;/li&gt;
&lt;li&gt;确定解决方案 - 可以用&lt;code&gt;评价矩阵&lt;/code&gt;来确定最终方案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了讲解清楚, 正文中所举的例子都是针对单个环节的. 但在实际使用时, 大家&lt;code&gt;望闻问切&lt;/code&gt;的思路来解决问题, 应该会有意料之外的收获.&lt;/p&gt;
&lt;p&gt;One more thing, &lt;code&gt;解决问题的方法是多样的&lt;/code&gt;, 本文也仅仅提供了一种解决问题的思路. 你完全可以在不同环节采用不同的方法或工具, 比如在分析出问题产生的原因之后, 针对各原因逐个消除, 或解决主要矛盾, 再或者完全采用其他的解决之道. 用伟大领袖我邓主席的话作结:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;黑猫白猫, 能抓住老鼠的, 就是好猫.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(本篇博客的题目, 引用了 Zbigniew Michalewicz &amp;amp; David B.Fogel 两位的著作&lt;strong&gt;How to Solve It: Modern Heuristics(如何求解问题:现代启发式方法)&lt;/strong&gt; 的书题)&lt;/p&gt;
</content:encoded><category>笔记</category><category>职慧</category><category>杂</category></item><item><title>Asesome Pyhthon3 WebApp 总结与导读</title><link>https://lifelonglove.cn/articles/python3-webapp-summary/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/python3-webapp-summary/</guid><description>复盘跟随教程完成 Python 3 Web 应用的过程，从异步框架、ORM、模板到 MVC 与部署逐层串联代码，帮助初学者理解各模块为何存在以及如何协作。</description><pubDate>Tue, 10 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;看山是山, 看山不是山, 看山还是山&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;引文&quot;&gt;引文&lt;/h2&gt;
&lt;p&gt;说实话, 我没资格写这样的博客, 因为水平不够. 说出来不怕丢人, 我完完全全没有学过web开发, 是彻头彻尾的大白(比小白还小白). 该项目的代码几乎全是照着廖老师的, 自己一个一个敲出来的. 但是为了理解这些代码, 期间我查阅了许多&lt;a href=&quot;#reference&quot;&gt;资料&lt;/a&gt;, 几乎为每一行代码都加了详尽的注释.&lt;/p&gt;
&lt;p&gt;秉承着“无总结, 不学习”的信念, 我选择对这一个月以来跟随廖老师学习 python3 开发 webapp 的过程做一个总结. 而这篇文章将同时作为&lt;a href=&quot;https://github.com/Engine-Treasure/awesome-python3-webapp&quot;&gt;该项目&lt;/a&gt;的&lt;code&gt;README&lt;/code&gt;. 因此, 就有了这么个怪异的标题: 于我是“总结”, 于读者是“导读”.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Awesome Python3 WebApp 这个博客系统, 我已经部署到服务器上了.&lt;/p&gt;
&lt;p&gt;|:–:|:–:|
|地址|&lt;a href=&quot;http://googlegeeks.xyz:23333&quot;&gt;googlegeeks.xyz:23333&lt;/a&gt;|
|管理员帐号|&lt;a href=&quot;mailto:kissg@kissg.com&quot;&gt;kissg@kissg.com&lt;/a&gt;|
|密码|kissgkissg|&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;首先, 我觉得最重要的, 要有大局观, 即整体把握能力. 这也正是我作为“过来人”, 能为学习中的各位提供的最大帮助.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014323392805925d5b69ddad514511bf0391fe2a0df2b0000&quot;&gt;廖老师第15天的实战&lt;/a&gt;已经给出了一个系统的全局模型:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/awesome-python3-webapp/nginx-awesome-mysql.png&quot; alt=&quot;Nginx-awesome-MySQL&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Nginx&lt;/code&gt;作为Web服务器, 用于处理静态资源, 同时作为反向代理将动态请求交给python代码处理;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;awesome&lt;/code&gt;, 即我们用python3写的webapp, 负责事务处理, 并与数据库交互;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MySQL&lt;/code&gt;, 提供数据的存储与处理服务.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个项目的重点, 就在于作为&lt;code&gt;Nginx&lt;/code&gt;后端和&lt;code&gt;MySQL&lt;/code&gt;前端的&lt;code&gt;awesome&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;让我们先来看看, 开发&lt;code&gt;awesome&lt;/code&gt;主要用到了哪些库. 用好工具的基础, 是了解, 因此有必要花点时间了解一下这些工具都干嘛的:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jinja2&lt;/code&gt; - 前端模板引擎. 所有的前端页面都是通过&lt;code&gt;jinja2&lt;/code&gt;调用模板并渲染得到的 \
详情: &lt;a href=&quot;http://jinja.pocoo.org/docs/dev/&quot;&gt;官方文档&lt;/a&gt; | &lt;a href=&quot;https://github.com/Engine-Treasure/learning-notebook/blob/master/python/jinja2-note.markdown&quot;&gt;我的简单笔记&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiohttp&lt;/code&gt; - 基于&lt;code&gt;asyncio&lt;/code&gt;的异步http框架. 此处主要用于实现 web 服务器, 提供单线程多用户高并发支持\
详情: &lt;a href=&quot;http://aiohttp.readthedocs.io/en/stable/&quot;&gt;官方文档&lt;/a&gt; | &lt;a href=&quot;https://github.com/Engine-Treasure/learning-notebook/blob/master/python/aiohttp-note.markdown&quot;&gt;我的简单笔记&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiomysql&lt;/code&gt; - mysql的python异步驱动程序\
详情: &lt;a href=&quot;http://aiomysql.readthedocs.io/en/latest/&quot;&gt;官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asyncio&lt;/code&gt; - python内置的异步io库. 几乎所有的异步IO操作都与之有关.\
详情: &lt;a href=&quot;https://docs.python.org/3/library/asyncio-task.html&quot;&gt;官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如上所示, &lt;code&gt;awesome&lt;/code&gt;为了实现对事务的高效处理, 使用了许多&lt;code&gt;异步&lt;/code&gt;框架与提供&lt;code&gt;异步IO&lt;/code&gt;能力的库. 如果忘了什么是&lt;code&gt;异步IO&lt;/code&gt;, 可翻看&lt;a href=&quot;http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573480558080fa77514407cb23834c78c6c7309000&quot;&gt;廖老师之前的教程&lt;/a&gt;. 简而言之, 就是&lt;strong&gt;当程序需要执行耗时的IO操作时, 只发出IO命令, 并不等待IO结果, 让CPU执行其他任务. 当IO结束时, 再通知CPU处理&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;在讲解&lt;code&gt;awesome&lt;/code&gt;的实现之前, 来看看&lt;code&gt;awesome&lt;/code&gt;是如何工作的, 或许能对各位接下来的编程有所帮助. 以下是我运行&lt;code&gt;app.py&lt;/code&gt;后的日志(你也可以观察&lt;code&gt;app.py&lt;/code&gt;中&lt;code&gt;init(loop)&lt;/code&gt;的初始化步骤):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] found model: User (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;table:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; users&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] found model: Blog (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;table:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; blogs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] found model: Comment (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;table:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; comments&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] create database connection pool...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ==============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] init jinja2...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] set jinja2 template path: /home/kissg/Developing/awesome-python3-webapp/www/templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ==============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] add route GET /api/blogs =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; api_blogs(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;page&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] add route get /signin =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; signin(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ==============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] add static /static/ =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /home/kissg/Developing/awesome-python3-webapp/www/static&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ==============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 15:30:01] server started at http://127.0.0.1:9000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由日志可知, 系统启动之后进行了以下初始化操作:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立model, 简单点说就是建立python中的&lt;code&gt;类&lt;/code&gt;与数据库中的&lt;code&gt;表&lt;/code&gt;的映射关系\
&lt;em&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/em&gt;: &lt;code&gt;found model: User (table: users)&lt;/code&gt; 可在&lt;code&gt;orm.py&lt;/code&gt; 中元类的定义中找到, 可见&lt;code&gt;orm.py&lt;/code&gt;的重要性. (事实上,我在这一天的内容上停顿了一个多星期, 就是为了&lt;strong&gt;理解元类&lt;/strong&gt;, &lt;strong&gt;理解ORM&lt;/strong&gt;. 本人特地写了篇&lt;a href=&quot;/notes/python-metaclass/&quot;&gt;博客&lt;/a&gt;记录元类的点点滴滴, 有兴趣的同学可看下)&lt;/li&gt;
&lt;li&gt;创建全局数据库连接池\
&lt;em&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/em&gt;: &lt;code&gt;create database connection pool&lt;/code&gt; 同样可在&lt;code&gt;orm.py&lt;/code&gt;中找到. 按廖老师的说法, 这是为了使每个http请求都可以从连接池中直接获取数据库连接, 而不必频繁地打开关闭数据库连接. 现在你真的意识到&lt;code&gt;orm.py&lt;/code&gt;的重要性了吗? 很重要, 与数据打交道的活在其中都做了定义&lt;/li&gt;
&lt;li&gt;初始化&lt;code&gt;jinja2&lt;/code&gt;引擎, 并设置模板的路径\
&lt;em&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/em&gt;: 两条日志都可在&lt;code&gt;app.py&lt;/code&gt;的jinja2初始化函数中找到. 注意, 此初始化函数之前, 已经创建了&lt;code&gt;aiohttp.web.Application&lt;/code&gt;对象, 即所谓的&lt;code&gt;webapp&lt;/code&gt;. 该函数主要是初始化了&lt;code&gt;jinja2 env&lt;/code&gt;(环境), 并将&lt;code&gt;jinja2 env&lt;/code&gt;绑定到&lt;code&gt;webapp&lt;/code&gt;的&lt;code&gt;__templating__&lt;/code&gt;属性上. 后一条日志指出了模板的路径, 这是因为在初始化&lt;code&gt;jinja2 env&lt;/code&gt;时, 指定了加载器(&lt;code&gt;loader&lt;/code&gt;)为文件系统加载器(&lt;code&gt;FileSystemLoader&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;注册处理器(handler)\
&lt;em&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/em&gt;: &lt;code&gt;add route ...&lt;/code&gt; 日志可在&lt;code&gt;corow.py&lt;/code&gt;中找到. &lt;code&gt;事务处理&lt;/code&gt;的三要素: &lt;code&gt;方法(http method)&lt;/code&gt; &amp;amp;  &lt;code&gt;路径(path)&lt;/code&gt; &amp;amp; &lt;code&gt;处理函数(func)&lt;/code&gt;. 将三者连起来看就是, 将对某路径的某种http请求交给某个函数处理, 而该函数就称为某路径上某种请求的处理器(handler).&lt;/li&gt;
&lt;li&gt;添加静态资源&lt;/li&gt;
&lt;li&gt;创建服务器对象, 并绑定到socket&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;仔细看, 你会发现, 从上到下的过程就是一个实现&lt;code&gt;MVC&lt;/code&gt;模型的过程: &lt;code&gt;建立model&lt;/code&gt; -&amp;gt; &lt;code&gt;构建前端视图&lt;/code&gt; -&amp;gt; &lt;code&gt;注册处理控制&lt;/code&gt;. 而廖老师的实战过程也是按这个顺序一步步下来的.&lt;/p&gt;
&lt;p&gt;系统启动之后, 当我访问博客首页, 在首页完全加载出来之后, 日志如下所示, 我们不妨以此来一览事务处理的过程:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] Request: GET /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] check user: GET /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] Response handler...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] call with args: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] SQL: &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; count(id) _num_ from &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;blogs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rows&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[2016-05-10 17:48:28] 127.0.0.1 - - [10/May/2016:09:48:28 +0000] &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;GET / HTTP/1.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; 200 3513 &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们还是通过找出日志的出处来分析:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Request: method path&lt;/code&gt;, &lt;code&gt;check user: method path&lt;/code&gt;, &lt;code&gt;Response handler ...&lt;/code&gt; 分别可在&lt;code&gt;app.py&lt;/code&gt;的&lt;code&gt;logger_factory&lt;/code&gt;, &lt;code&gt;auth_factory&lt;/code&gt;和&lt;code&gt;response_factory&lt;/code&gt;中找到.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;call with args: {}&lt;/code&gt; 可在&lt;code&gt;coroweb.py&lt;/code&gt;中&lt;code&gt;RequestHandler&lt;/code&gt;的&lt;code&gt;__call__&lt;/code&gt;方法中找到&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SQL: sql-statement&lt;/code&gt; 可在&lt;code&gt;orm.py&lt;/code&gt;中封装了sql语句的函数&lt;code&gt;select&lt;/code&gt;和&lt;code&gt;execute&lt;/code&gt;中找到. &lt;code&gt;rows return n&lt;/code&gt; 仅在&lt;code&gt;orm.py&lt;/code&gt;的&lt;code&gt;select&lt;/code&gt;函数中找到.&lt;/li&gt;
&lt;li&gt;最后一条日志, 对不起~我没找到- -.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;找到了这些日志的出处之后, 通过打印日志的先后顺序, 就可以理出事务处理的脉络了:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当客户端发起请求时, 由于&lt;code&gt;中间件(middlewares)&lt;/code&gt;的存在, 事务处理将被拦截. 根据&lt;a href=&quot;http://aiohttp.readthedocs.io/en/stable/web.html#middlewares&quot;&gt;aiohttp文档&lt;/a&gt;的说法, &lt;code&gt;middleware&lt;/code&gt;提供了自定义&lt;code&gt;handler&lt;/code&gt;的机制, 可以简单地理解成&lt;code&gt;装饰器(decorator)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;根据日志, 我们可以知道, 在事务处理之前, &lt;code&gt;logger_factory&lt;/code&gt;首先执行, 打印一个收到请求的日志;&lt;/li&gt;
&lt;li&gt;接着&lt;code&gt;auth_factory&lt;/code&gt;执行, 验证用户是否登录以及用户权限, 并将用户信息绑定到请求上;&lt;/li&gt;
&lt;li&gt;之后进入&lt;code&gt;response_factory&lt;/code&gt;. &lt;code&gt;response_factory&lt;/code&gt;实际是根据事务处理的结果向客户端发回响应. 因此, 在&lt;code&gt;response_factory&lt;/code&gt;中, 事务处理先被执行;&lt;/li&gt;
&lt;li&gt;我们注册到webapp上的&lt;code&gt;handler&lt;/code&gt;实际上是一个&lt;code&gt;RequestHandler&lt;/code&gt;对象, 由于实现了&lt;code&gt;__call__&lt;/code&gt;方法, 因此&lt;code&gt;RequestHandler&lt;/code&gt;对象可以当作函数使用(&lt;a href=&quot;https://en.wikipedia.org/wiki/Duck_typing&quot;&gt;Duck typing&lt;/a&gt;). 因此, 每次事务处理都会打印&lt;code&gt;call args ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;此例中, 我们的请求是针对&lt;code&gt;/&lt;/code&gt;的&lt;code&gt;GET&lt;/code&gt;, 处理过程中调用了&lt;code&gt;Blog.findAll&lt;/code&gt;方法, 间接调用了&lt;code&gt;orm.py&lt;/code&gt;中定义的&lt;code&gt;select&lt;/code&gt;方法, 因此会有那2条sql相关的日志&lt;/li&gt;
&lt;li&gt;事务处理返回的结果是带有模板信息的字典, &lt;code&gt;response_factory&lt;/code&gt;根据这个结果, 加载模板, 并将渲染之后的&lt;code&gt;html&lt;/code&gt;作为响应发回给客户端.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上就是一次事务处理的基本过程, 条理应该是很清晰的.&lt;/p&gt;
&lt;p&gt;在对系统有了一个全局概念之后, 编程应该会容易不少, 至少我们知道了要写些什么. 接下来, 看看每个脚本都做了什么:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;app.py&lt;/code&gt;: &lt;strong&gt;web app骨架&lt;/strong&gt;(廖老师的说法). 在这里, 初始化了&lt;code&gt;jinja2&lt;/code&gt;环境, 实现了各&lt;code&gt;middleware factory&lt;/code&gt;, 最重要的是——创建了app对象, 完成系统初始化&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orm.py&lt;/code&gt;: 建立ORM(Object, Relational Mapping, 对象关系映射), 此处所有代码都是为此服务的——创建了全局数据库连接池, 封装sql操作, 自定义&lt;a href=&quot;/notes/python-metaclass/&quot;&gt;元类&lt;/a&gt;, 定义Model类&lt;/li&gt;
&lt;li&gt;&lt;code&gt;models.py&lt;/code&gt;: 在ORM基础上, 建立具体的类, 相对比较简单&lt;/li&gt;
&lt;li&gt;&lt;code&gt;coroweb.py&lt;/code&gt;: web框架(廖老师的说法), 说白了就是事务处理(&lt;code&gt;handler&lt;/code&gt;)的基础准备. 此处定义了&lt;code&gt;get&lt;/code&gt;与&lt;code&gt;post&lt;/code&gt;装饰器, 与之对应的是&lt;code&gt;handler&lt;/code&gt;的&lt;code&gt;http method&lt;/code&gt;部分概念. 又定义了&lt;code&gt;RequestHandler&lt;/code&gt;类, 前文说过, 注册到app上的其实就是&lt;code&gt;RequestHandler&lt;/code&gt;对象, 因为实现了&lt;code&gt;__call__&lt;/code&gt;方法, 所以可以当函数使用. 可以说, &lt;code&gt;RequestHandler&lt;/code&gt;起了包装&lt;code&gt;handler&lt;/code&gt;的作用. 还有一些辅助函数, 比如添加静态文件, 自动注册&lt;code&gt;handler&lt;/code&gt;等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config*.py&lt;/code&gt;: 配置文件. 在这里多嘴一句, 将默认配置文件与自定义配置文件分离真的是好主意. 很多软件, 比如&lt;code&gt;rime&lt;/code&gt;, &lt;code&gt;sublime text&lt;/code&gt;都是这种思路, 没想到这里就用上了.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handlers.py&lt;/code&gt;: 全部的&lt;code&gt;handlers&lt;/code&gt;及一些辅助函数, 包括验证用户权限, 检查cookie等.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apis.py&lt;/code&gt;: 定义了&lt;code&gt;APIError&lt;/code&gt;类与&lt;code&gt;Page&lt;/code&gt;类, 分别用于api错误提示与页面管理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pymonitor.py&lt;/code&gt;: 检测&lt;code&gt;www&lt;/code&gt;目录下&lt;code&gt;.py&lt;/code&gt;文件的修改, 自动重启&lt;code&gt;app.py&lt;/code&gt;\
(这个脚本不算系统的一部分, 但它给我的启发意义是巨大的. 受其与&lt;code&gt;fabfile.py&lt;/code&gt;的影响, 以及自己的一些经历, 我发现编程的意义之一就是实现&lt;code&gt;自动化&lt;/code&gt;. 原谅我, 又多嘴了.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上就是对各脚本的简单介绍与梳理, 具体的还请看&lt;a href=&quot;http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432170937506ecfb2f6adf8e4757939732f3e32b781c000&quot;&gt;廖老师的教程&lt;/a&gt;. 我的建议是, 大家编程的时候, 最好有目标, 至少应该知道&lt;strong&gt;要做什么&lt;/strong&gt;, 这也是我把这些模块都拎一拎的原因所在. 而根据我的经验, &lt;code&gt;app.py&lt;/code&gt;, &lt;code&gt;orm.py&lt;/code&gt;, &lt;code&gt;coroweb.py&lt;/code&gt;, &lt;code&gt;handlers.py&lt;/code&gt;几个模块相对比较重要, 建议大家多花点时间, 好好理解.&lt;/p&gt;
&lt;p&gt;正文内容就到这里吧, 我也不知道接下来该讲什么了. 更多的, 还是见&lt;code&gt;代码与注释&lt;/code&gt;吧.&lt;/p&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;作为一个大白, 这次webapp的开发经历给我的感受就如开头引用的偈语所言:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看山是山, 看山不是山, 看山还是山&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;没接触webapp开发之前, 我的想法是: 这个不难, 无非就是请收请求, 根据请求内容进行相应处理, 再返回响应. 这就对应看山是山(1)的阶段.&lt;/p&gt;
&lt;p&gt;等到真正开始实战, 我懵逼了. 从第三天开始, 完全不会, 之前学的数据库之前全还给老师了. 当时想过放弃, 后来告诉自己, “勇敢地抄袭吧, 少年! 自己多花点时间理解, 总会有收获的.” 然后我真的就这样做了, 也坚持下来了, 最后发现收获真的不少. 我慢慢接触到一些新的概念, 比如&lt;code&gt;MVC&lt;/code&gt;, &lt;code&gt;MVVM&lt;/code&gt;等等, 也学会一些技巧, 比如&lt;code&gt;通过linux软链接实现版本控制&lt;/code&gt;, &lt;code&gt;watchdog与subprocess提高开发效率&lt;/code&gt;等等. 我也是到最后才真的发觉, 廖老师的教程真的是好教程. 为此, 我还特地给他小额赞助了- -. \
(插入语: &lt;strong&gt;评判事情好与不好的一个标准可以是, 有没有收获&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;我花了这2天的时间来梳理总结, 发现整个过程, 真的不算复杂, 无非就是…(- -.)&lt;/p&gt;
&lt;p&gt;以此文, 与诸君共勉&lt;/p&gt;
&lt;h2 id=&quot;部分学习材料&quot;&gt;部分学习材料&lt;/h2&gt;
&lt;p id=&quot;reference&quot;&gt;&lt;/p&gt;
&lt;p&gt;关于元类:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python/6581949#6581949&quot;&gt;e-satis在stackoverflow上的回答&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.jobbole.com/21351/&quot;&gt;上述的中译版&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/notes/python-metaclass/&quot;&gt;我的元类总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我看过的一些库的文档:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://aiohttp.readthedocs.org/en/stable/web.html&quot;&gt;aiohttp官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://aiomysql.readthedocs.io/en/latest/index.html&quot;&gt;aiomysql官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jinja.pocoo.org/docs/latest/&quot;&gt;jinja2官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://docs.jinkan.org/docs/jinja2/&quot;&gt;jinja2中文版文档&lt;/a&gt;(翻译不全, 不如看原版)&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>文章</category><category>Python</category><category>webapp</category></item><item><title>Python之socket初见</title><link>https://lifelonglove.cn/articles/python-socket-programming-and-packet-sniffer/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/python-socket-programming-and-packet-sniffer/</guid><description>结合抓包实验讲解 Python socket 的基本接口、原始套接字与网络字节序，并逐字段解析 IP 数据包，最终实现一个满足课程要求的简易抓包器。</description><pubDate>Sun, 01 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;不给跬步，无以至千里&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;引文&quot;&gt;引文&lt;/h2&gt;
&lt;p&gt;因为学业要求，需要编程实现一个抓包器，具体要求如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;捕获本机网卡的IP包，对捕获的IP包进行解析。\
要求必须输出以下字段：版本号、总长度、标识位、片偏移、协议、原地址与目的地址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于编程语言不做要求，我绝对当然就选择了很好很强大的&lt;strong&gt;python&lt;/strong&gt;，然后恶补了下&lt;strong&gt;socket&lt;/strong&gt;与&lt;strong&gt;python socket programming&lt;/strong&gt;的相关知识，这也正是本文的主要内容。我发现，用python来实现抓包器真的太简单了(至少达到实验要求是这样的)，因此将这部分内容放在文章最后(&lt;a href=&quot;#packet-sniffer&quot;&gt;先睹为快&lt;/a&gt;)。&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;按照往常的习惯，我还是从最基础的概念讲起——什么是socket？&lt;/p&gt;
&lt;p&gt;socket实际上是&lt;a href=&quot;https://en.wikipedia.org/wiki/Inter-process_communication&quot;&gt;进程间通信(Inter-process communication，IPC)&lt;/a&gt;的一种形式。单机模式下，你有许多方式可以实现IPC，比如文件、管道、共享内存等等，因此这里也并不是socket的用武之地。socket真正的强大在于&lt;strong&gt;跨平台的通信&lt;/strong&gt;，即网络通信(换言之，网络通信归根结底是进程间的通信)，几乎是“只此一家，别无分号”。因此，我们常说的socket，又称&lt;strong&gt;netwok socket&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A network socket is an endpoint of a connection across a computer network.——&lt;a href=&quot;https://en.wikipedia.org/wiki/Network_socket&quot;&gt;维基百科&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;根据以上定义可知，socket是一个网络连接的一端。其实这样说，socket的概念还是很抽象，但它指明了一点：&lt;em&gt;&lt;strong&gt;是端，即通信的起点或终点&lt;/strong&gt;&lt;/em&gt;，而不是这整条通信链。socket的本意是插座，我觉得挺形象的：我们不考虑也并不用甚至不能知道数据是如何从一端发送到另一端的，就像我们不用知道插座里的电怎样来的一样，只要它在那里，而我们能通过它实现通信的目的(对于插座是用电)就行了。&lt;/p&gt;
&lt;p&gt;有了基本概念之后，我们再来讲讲socket的结构。一个socket至少由以下两部分组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;本地socket地址：由本地主机的ip地址和端口号组成。&lt;/li&gt;
&lt;li&gt;传输协议：例如TCP、UDP、raw IP等。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当一个socket连接到另一个socket时，它还需要加上远程socket的地址。&lt;/p&gt;
&lt;p&gt;还是用插座模型来加深理解：每个插座都有一个物理位置属性，而且一定是全球唯一的，比如隔壁老王家二楼卧室左边床头的插座，你再也找不出第二个与它物理位完全相同的插座了，这就是IP地址的作用；要用电，则插座里的电总是要输送到某个用电器的，电灯还是电视机，这就由端口号来指定了，此处的用电器就相当于应用进程；而传输协议则相当于规定了输送到插座的电的电压，是110V还是220V还是380V高压电；远程socket地址就相当于指明了电的来源，是秦山核电站还是葛洲坝水电站。&lt;/p&gt;
&lt;p&gt;以上便是对socket结构的比喻描述，需要强调的是，每一项都可以唯一地确定一个socket，比如一个TCP服务器可能同时为多个客户端提供服务，因此就会有多个socket——它们的本地socket地址完全相同，但远程socket地址却不一样；再比如本地与远程socket地址都相同，但使用的协议不一样，一个使用TCP协议，一个使用UDP协议，那就是两个不同的socket。&lt;/p&gt;
&lt;p&gt;前文简单地提到了端口号与应用进程之间的关系，不知道大家对端口的概念有没有产生疑惑，比如为什么不直接用进程标识符(Process Identifier，简称PID)来表征一个应用进程呢？以下是我为了更好地理解端口的概念，所查资料的摘要。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A port is a logical construct that identifies a specific process or a type of service.&lt;/strong&gt;(端口是能确定一个特定进程或一类服务的逻辑结构。)\&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ports are unnecessary on direct point-to-point links when the computers at each end can only run one program at a time. Ports became necessary after computers became capable of executing more than one program at a time and were connected to modern packet-switched networks.&lt;/strong&gt;(在直接以点对点形式连接，并且通信双方每次只能运行一个程序的计算机上，端口是非必要的。当计算机具备了同时运行多个程序的能力，并连接到现代分组交换网上时，端口就显得很必要了。)\
——&lt;a href=&quot;https://en.wikipedia.org/wiki/Port_(computer_networking)&quot;&gt;维基百科&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事实上，以上内容并不能为我解惑，但我大致明白了端口的功能与PID类似，这让我更困惑了——port与PID有什么不同?&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;把一个特定机器上运行的特定进程指明为因特网上通信的终点是不可行的，因为进程的创建与撤销都是动态的，通信的一方几乎无法识别对方机器上的进程。另外，往往需要利用目的主机提供的&lt;strong&gt;功能&lt;/strong&gt;来识别终点，而不需要知道具体实现这个功能的进程是哪一个。\&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;虽然通信的终点是应用进程，但只要把传送的报文交到目的主机的某个合适的目的端口，最后的交付到目的进程的工作由TCP来完成就可以了。此处的端口为软件端口，有别于路由器或交换机上的硬件端口，它是应用层的各种协议进程与运输实体进行层间交互的一种地址.不同的操作系统具体实现端口的方法可以不同.\&lt;/li&gt;
&lt;li&gt;端口号只具有本地意义，它只是为了标识本地计算机应用层中各进程在和运输层交互时的层间接口。\
——《计算机网络(第六版)(谢希仁)》(有删改)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这下清晰多了，让我们再把重点拎一拎：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;port是标识本地计算机应用层中各进程和运输层交互的层间接口&lt;/li&gt;
&lt;li&gt;port是运输层的概念，而进程是应用层的概念&lt;/li&gt;
&lt;li&gt;通信的终点虽然是进程，但数据只要传输到目的主机的某个合适的端口就行了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再用一个比喻来更形象地解释这个概念：如果用一个学校来比喻一台计算机，那应用层就应该是学校里的各个部门，运输层是传达室，而端口则是传达室里的存放各部门信件的信箱，网络通信的数据就是信件。当有某部门的信件达到时，并不需要直接送到那个部门，送件人也并不知道那个部门怎么走，就将信件投递到传达室内该部门的信箱里，之后的事情与送件人再没有任何关系了。部门里的人需要自己去取件再处理。发件的过程与收件类似。&lt;/p&gt;
&lt;p&gt;有了前面的知识铺垫之后，socket编程就变得简单多了，因为说白了就2步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建socket&lt;/li&gt;
&lt;li&gt;使用socket&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在python中创建一个socket，与上述的socket的结构又有点不同，具体格式如下：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_STREAM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数解释(后2者一般使用默认即可，有用到再讲解):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;family - 用于指定socket的地址类型，前缀AF即Address Family的缩写。最常见的有&lt;code&gt;AF_INET&lt;/code&gt;和&lt;code&gt;AF_INET6&lt;/code&gt;两种，分别表示使用IPv4和IPv6协议&lt;/li&gt;
&lt;li&gt;type   - 用于指定socket的类型，使用SOCK前缀。常见的有以下几种类型：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;SOCK_STREAM&lt;/code&gt;：使用TCP或STCP协议，面向连接的socket&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SOCK_DGRAM&lt;/code&gt;：使用UDP协议，无连接的socket&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SOCK_RAW&lt;/code&gt;：这类socket，数据包将绕过运输层，因此可以在应用层得到数据包的头部&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般，使用&lt;code&gt;AF_INET&lt;/code&gt;和&lt;code&gt;SOCK_STREAM&lt;/code&gt;/&lt;code&gt;SOCK_DGRAM&lt;/code&gt;就能应付绝大多数情况了。因为INET socket几乎占了socket总数的99%，而stream socket和datagram socket又是这绝大多数中的绝大多数。&lt;/p&gt;
&lt;p&gt;socket的使用，根据情况的不同而有所不同。按使用的协议不同，主要可分为TCP通信和UDP通信；按使用场景的不同，可分为服务器端和客户端两类。但是套路却很简单，如下所示：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# TCP通信的基本步骤：&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# TCP server: socket &amp;gt; bind &amp;gt; listen &amp;gt; while True &amp;gt; {accept &amp;gt; recv &amp;gt; send} &amp;gt; close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# TCP client: socket -----------------------------&amp;gt; connect &amp;gt; send &amp;gt; recv  &amp;gt; close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# UDP通信的基本步骤:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# UDP server: socket &amp;gt; bind &amp;gt; recvfrom &amp;gt; sendto &amp;gt; close&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# UDP client: socket -------&amp;gt; sendto &amp;gt; recvfrom &amp;gt; close&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基本套路就是如此，简单说下几个函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;socket.bind(adress)&lt;/code&gt; - 该函数将socket绑定到一个地址上。要注意2点：1. 一个socket只能绑定到一个地址上，不能重复绑定；2. 地址格式必须与创建socket时指定的一致，例如不能将INET socket绑定到INET6 socket上&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.listen([backlog])&lt;/code&gt; - 启动监听，即允许服务器接受连接。backlog参数用于指定最大连接数，一旦服务器达到最大连接数，之后的连接都将被拒绝。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.connect(address)&lt;/code&gt; - 与指定地址的远程socket建立连接。建立连接之后就可以发送或接受数据了。只有使用TCP协议才需要建立连接，UDP协议是无连接的(connectless)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.accept()&lt;/code&gt; - 接受一个连接。其返回值是&lt;code&gt;(conn, address)&lt;/code&gt;，其中conn是是一个新的socket对象，用于与建立连接的socket收发数据，address则作为远程socket地址绑定到该socket上。注意，此时母服务器socket仍然在兢兢业业地负责监听。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.send(bytes)&lt;/code&gt;/&lt;code&gt;socket.sendto(bytes[,flags], address)&lt;/code&gt; - 向远程socket发送数据，注意，必须以bytes格式发送。而使用sendto时，要指名目的socket的地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socket.recv(bufsize[, flags])&lt;/code&gt;/&lt;code&gt;socket.recvfrom(bufsize[, flags])&lt;/code&gt; - 从socket接收数据，通过bufsize指定一次性接收数据的最大量，recv的返回值是bytes对象，而recvfrom的返回值是&lt;code&gt;(bytes, address)&lt;/code&gt;，address即为发送数据的socket地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在此，在此举且仅举一个例子，如下：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Echo server program&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 创建socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_STREAM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    s.bind((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;56789&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 绑定地址，如前所述，socket地址由ip地址与port组成&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    s.listen(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 启动监听，并设置最大连接数为1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    conn, addr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.accept()  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 接受连接，并将返回的新socket与addr info分别储存&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conn:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Connected by&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, addr)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 在本地打印信息，测试用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; conn.recv(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;65565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 65535是一次性能接收的最大值&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; data:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            conn.send(data)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 将数据原样发回&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Echo client program&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 创建socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_STREAM&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    s.connect((&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;127.0.0.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;56789&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 连接到另一个socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    s.send(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Hello, world&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 向对方socket发送数据&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.recv(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;65565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Received&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data))  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 打印收到的数据&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/python-socket-packet-sniffer/socket_demo.png&quot; alt=&quot;echo-back-demo&quot;&gt;&lt;/p&gt;
&lt;p&gt;其实这就是一个socket用于单机模式下IPC的例子，&lt;code&gt;127.0.0.1&lt;/code&gt;是&lt;code&gt;localhost&lt;/code&gt;的IP地址。你只要将此处本地socket地址改成远程的socket地址，就可以与远程计算机通信了。(当然，一般情况下，你要发些有意义的消息，人家才会响应你)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;注，运行脚本需要使用管理员权限，因为socket的创建实际是系统调用。&lt;/em&gt;&lt;/p&gt;
&lt;p id=&quot;packet-sniffer&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来我们讲讲抓包器的python3实现。&lt;/p&gt;
&lt;p&gt;与前面的例子不同，这里我们使用&lt;code&gt;RAW socket&lt;/code&gt;，除此之外，我们还需要指定协议，即&lt;code&gt;socket.socket(family, type, proto, fileno)&lt;/code&gt;中的&lt;code&gt;proto&lt;/code&gt;，这相当于为指定协议在运输层打开一个缺口，从而在运输层放行该协议的包。另外，由于事先不需要连接到某个远程socket，因此我们使用&lt;code&gt;recvfrom&lt;/code&gt;来捕获所有本机收到的数据包。以TCP协议为例，最初的程序应该是这样的：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_RAW&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;IPPROTO_TCP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s.recvfrom(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;65565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;➜  /home/kissg/Tests sudo python3 socket_demo.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(b&apos;E\x00\x00&amp;lt;Na@\x00@\x06\xeeX\x7f\x00\x00\x01\x7f\x00\x00\x01\xb9\xa6\x048G\xd4\x95\xff\x00\x00\x00\x00\xa0\x02\xaa\xa$\xfe0\x00\x00\x02\x04\xff\xd7\x04\x02\x08\n\x00\xc8\xdb\x9e\x00\x00\x00\x00\x01\x03\x03\x07&apos;, (&apos;127.0.0.1&apos;, 0))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;recvfrom&lt;/code&gt;的返回值是&lt;code&gt;(data, addrinfo)&lt;/code&gt;，因此上述结果中的&lt;code&gt;b&apos;E\x00...&apos;&lt;/code&gt;就是一个原始的IP数据报，其由如下3部分组成：&lt;code&gt;IP header&lt;/code&gt; + &lt;code&gt;TCP header&lt;/code&gt; + &lt;code&gt;data&lt;/code&gt;。我们只要从数据报中取出想要解析的头部，再做解析即可。以&lt;code&gt;IP header&lt;/code&gt;为例：一个IP数据报的头部由20字节的固定部分与长度可变的可选字段组成，如下所示：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 0                   1                   2                   3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |Version|  IHL  |Type of Service|          Total Length         |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |         Identification        |Flags|      Fragment Offset    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |  Time to Live |    Protocol   |         Header Checksum       |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |                       Source Address                          |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |                    Destination Address                        |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# |                    Options                    |    Padding    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常只解析固定部分即可，因此通过切片(slice)取得数据包的前20个字节，即为所需的IP数据报头部。之后再对各字段分别处理。此处为了方便取得各字段，用到&lt;code&gt;struct&lt;/code&gt;模块的&lt;code&gt;unpack&lt;/code&gt;函数(&lt;a href=&quot;https://docs.python.org/3/library/struct.html#struct.unpack&quot;&gt;使用方法详情&lt;/a&gt;)，具体见代码注释：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; struct &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; unpack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_INET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_RAW&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;IPPROTO_TCP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        raw_packet &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; s.recvfrom(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;65565&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (data, addr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        packet &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; raw_packet[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        raw_iph &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; packet[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 尚未解析的IP数据报头部固定部分&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # unpack(fmt, buffer) - 根据指定的格式化字符串来拆解给定的buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # B 单字节的整型&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # H 双字节的整型&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # s bytes，前加数字表示取4字节的bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        iph &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; unpack(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;!BBHHHBBH4s4s&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, raw_iph)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Version&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 版本字段与IP数据报头部共享一个字节，通过右移操作取得单独的版本字段&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;IP Header Length&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;F&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 首部长度字段的1代表4个字节&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Type of Service&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 区分服务，一般情况下并不使用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Total Length&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# IP首部+数据的总长度，即len(packet)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Identification&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 标识&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        flags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 13&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 标识位与片偏移共享2个字节，且最高位并且未使用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MF&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; flags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 测试最低位&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;DF&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; flags &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 测试中间位&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Fragment Offset&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; 0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1FFF&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # 位与操作取得片偏移&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Time to Live&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 生存时间，单位是跳数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Protocol&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 数据报携带的数据使用的协议，TCP为6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Header Checksum&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 首部校验和&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # socket.inet_ntoa(..)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # - convert an ip address from 32-bit packed binary format to string format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Source Address&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.inet_ntoa(iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        fields[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Destination Address&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; socket.inet_ntoa(iph[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; k, v &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fields.items():  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 遍历打印，由于是dict，因此打印是无序的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;            print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(k, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, v)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如下所示：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Identification : 58009&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Total Length : 40&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Header Checksum : 23092&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Time to Live : 64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IHL : 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Source Address : 127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Protocol : 6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Version : 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Destination Address : 127.0.0.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Fragment Offset : 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;MF : 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Type of Service : 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DF : 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP首部的解析与此类似，不再赘述。但是需要注意，不能简单地取&lt;code&gt;packet[20:40]&lt;/code&gt;的片段作为TCP首部(固定部分也为20字节)，因为由于可选字段的存在，IP首部长度是可变的。&lt;/p&gt;
&lt;p&gt;更进一步，当使用&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;socket.socket(socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;AF_PACKET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;SOCK_RAW&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, socket.ntohs(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0003&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来创建socket时，将可以捕获本机所有收到或发出的以太网帧。这意味着我们可以实现功能更加强大的抓包器！现在数据包的格式就变成了：&lt;code&gt;ethernet frame header&lt;/code&gt; + &lt;code&gt;IP header&lt;/code&gt; + &lt;code&gt;inner-Protocol header&lt;/code&gt; + &lt;code&gt;data&lt;/code&gt;。这&lt;code&gt;inner-Protocol header&lt;/code&gt;表示IP数据报的数据部分使用协议的首部，具体视数据包类型而定。各首部解析方法都类似，基本上算是重复劳动，不再赘述。以下文字对为何使用这几个参数做了权威的说明：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Packet sockets are used to receive or send raw packets at the device driver (OSI Layer 2) level.&lt;/strong&gt;(packet sockets允许我们在OSI模型的第二层(即数据链路层)收发raw packets。)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The socket_type is either SOCK_RAW for raw packets including the link level header or SOCK_DGRAM for cooked packets with the link level header removed.&lt;/strong&gt;(通过指定socket类型为&lt;code&gt;SOCK_RAW&lt;/code&gt;或&lt;code&gt;SOCK_DGRAM&lt;/code&gt;，我们可以分别获得数据链路层头部在内的raw packet或去除了数据链路层头部的packet。)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocol is the IEEE 802.3 protocol number in network order.&lt;/strong&gt;(协议是网络顺序的IEEE802.3协议)(注：网络字节顺序，按从高到低的顺序存储，在网络上使用统一的网络字节顺序以避免兼容性问题；主机字节顺序，不同的主机的主机可能不同，与CPU设计有关，x86结构与PowerPC结构字节顺序相反)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All incoming packets of that protocol type will be passed to the packet socket before they are passed to the protocols implemented in the kernel.&lt;/strong&gt;(即上文提到的放行)\
——Linux Programmer’s Manual&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;*&lt;em&gt;#define ETH_P_ALL 0x0003      /&lt;/em&gt; Every packet (be careful!!!) */**(0x0003表示匹配所有的包)\
——&amp;lt;linux/if_ether.h&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;看了上面的说明之后，相信大家对此处参数的使用已经有所了解了：&lt;code&gt;AF_PACKET&lt;/code&gt;表示创建一个packet socket，从而使我们能在数据链路层&lt;em&gt;收发&lt;/em&gt;raw packets，而&lt;code&gt;SOCK_RAW&lt;/code&gt;则让系统保留了数据链路层头部，&lt;code&gt;0x0003&lt;/code&gt;放行了所有包，&lt;code&gt;socket.ntohs(..)&lt;/code&gt;函数将收到的所有包的字节顺序从网络顺序转换到本机顺序。如此一层层下来，我们就实现了对本机所有收发的以太网帧的捕获。&lt;/p&gt;
&lt;p&gt;参考代码,请看&lt;a href=&quot;https://github.com/Engine-Treasure/the-little-python/blob/master/others/packet_sniffer.py&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;我一直觉得将原理、概念等理清了非常重要。编程的手段是多变的，但万变不离其宗，掌握了基本原理，不管是用Python来编程也好，用C/C++也罢，流程都是类似的。因此本文花了大量的篇幅来介绍这些原理/概念，总结一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;socket是进程间通信的一种形式，尤其适用于跨平台的进程间通信，即网络通信；&lt;/li&gt;
&lt;li&gt;socket至少由本地socket地址与传输协议组成，若建立了连接，那么还应有远程socket地址；&lt;/li&gt;
&lt;li&gt;port是标识本地计算机应用层中各进程和运输层交互的层间接口(因此，也有说包含了port的socket是应用层与运输层的层间接口的)；&lt;/li&gt;
&lt;li&gt;python socket programming，需要根据客户端/服务器，使用的协议(TCP/UDP)不同来选择不同的编程模式；&lt;/li&gt;
&lt;li&gt;抓包器要使用raw socket，并指定放行的协议，根据首部的不同，解析方法有不同，但套路类似&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>文章</category><category>Python</category><category>socket</category></item><item><title>Python之元类笔记</title><link>https://lifelonglove.cn/notes/python-metaclass/</link><guid isPermaLink="true">https://lifelonglove.cn/notes/python-metaclass/</guid><description>从类也是对象出发，解释 type、类创建过程、元类定制与常见使用边界。</description><pubDate>Mon, 25 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;凡事最怕是认真.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;引文&quot;&gt;引文&lt;/h2&gt;
&lt;p&gt;自上一次写博客到现在已经过去整整15天了.这期间,我看过许多材料,也有许多想付诸笔端与大家分享的.但苦于前人几乎已经把该讲的不该讲的都讲了,而且讲得非常透彻,鞭僻入里,有他们的珠玉在前,加上我又希望坚持原创,一时竟不知从何落笔了.思前想后,不如就写一篇“读书笔记”吧.于是就选定了关于&lt;code&gt;python3 metaclass&lt;/code&gt;的&lt;a href=&quot;http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python/6581949#6581949&quot;&gt;这篇文章&lt;/a&gt;(中文版请看&lt;a href=&quot;http://blog.jobbole.com/21351/&quot;&gt;这里&lt;/a&gt;).（为叙述方便，后文用“原文”来指代这篇文章）&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;按照惯例,先介绍一些预备知识,以便读者能更好地理解.&lt;/p&gt;
&lt;p&gt;首先,我想简单讲下&lt;code&gt;关键字&lt;/code&gt;(keyword)的概念(有时也叫保留字(reserved word))&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关键字,是编程语言的一类&lt;em&gt;语法结构&lt;/em&gt;.它们在语言设计之初就被定义了.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说白了,&lt;strong&gt;关键字就是编程语言已经为我们准备好的工具&lt;/strong&gt;,我们可以直接拿过来用.举一个例子,比如,要定义一个类,我们会这样写:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Myclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处&lt;code&gt;class&lt;/code&gt;关键字就相当于告诉python解释器:“解释器大哥,用户自定义了一个类,类名就叫Myclass,参数是…麻烦您给构造一下”.然后,Myclass类就自动被创建了.&lt;/p&gt;
&lt;p&gt;另一个需要澄清的概念是&lt;code&gt;动态编程语言&lt;/code&gt;(dynamic programming language)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;动态编程语言,是一类在运行时可以改变&lt;strong&gt;程序结构&lt;/strong&gt;的语言,例如新的函数,对象甚至代码可以被引进,已有的函数可以被删除或者其他结构上的变化.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面是一个简单的例子:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Myclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Myclass()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mc.name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;kissg&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # Myclass类本身并没有name属性，在运行期间为其添加了name属性&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(mc.name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kissg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(注:动态编程语言 != 动态类型语言.动态类型语言是指在运行时&lt;strong&gt;确定变量的类型&lt;/strong&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;众所周知,python是面向对象的编程语言.对此,我们要清楚并始终牢记一点:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在python的世界里,一切皆为对象.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;整型浮点型字符串型变量是对象,函数也是对象，类还是对象。只不过,&lt;strong&gt;类作为对象有点特殊,它是自身具有创建对象（类实例）能力的对象&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;那么，类是一个对象有何意义呢？这意味着，我们完全可以像操纵普通对象一样操纵一个类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以将它赋给一个变量&lt;/li&gt;
&lt;li&gt;可以对它进行拷贝&lt;/li&gt;
&lt;li&gt;可以为它增加属性&lt;/li&gt;
&lt;li&gt;可以将它作为参数传递给某个函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正是由于python的这些特点，为动态编程提供一个可能，一种思路。&lt;/p&gt;
&lt;p&gt;前文已经提到，使用&lt;code&gt;class&lt;/code&gt;关键字，python解释器就为我们自动地创建了一个类。其实，我们还可以手动地创建一个类，即调用&lt;code&gt;type&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;实际上，当我们使用&lt;code&gt;class&lt;/code&gt;关键字定义好一个类，python解释器就是通过调用&lt;code&gt;type&lt;/code&gt;函数来构造类的，它以我们写好的类定义(包括类名，父类，属性）作为参数，并返回一个类。官方文档对此描述如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;class type(name, bases, dict)&lt;/strong&gt;\
With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the__bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and becomes the __dict__ attribute.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下2种方法创建类的方法完全相同。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; X&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; X &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;X&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;dict&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么，如何为动态创建的类添加method（为了不混淆视听，此处用method来表示类的方法）呢？有两种方法，一种是在创建类的时候指定，一种是在后期动态地添加。方法与前文介绍的添加属性基本类似。实际上，完全可以将method看作是特殊一点的属性，这样，处理method的时候，想想属性是如何处理的，就会简单许多。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 动态创建类时，指定method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; echo_bar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;       print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.bar)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Foo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;Foo&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,), {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;echo_bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: echo_bar})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#=======================================================&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 动态地添加method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; echo_bar_more&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;       print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;yet another method&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Foo.echo_bar_more &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; echo_bar_more&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得注意的是，我们使用&lt;code&gt;class&lt;/code&gt;关键字定义类的时候，method的第一个参数一般总是&lt;code&gt;self&lt;/code&gt;，它指向调用method的对象（类实例）本身。因此，在我们动态地添加method之前，定义函数时，千万别忘了&lt;code&gt;self&lt;/code&gt;关键字。否则，错误将超乎你的想象.(思考一下，这个method并没有绑定到类实例上，这是你想要的效果吗？如果是，当我没说)&lt;/p&gt;
&lt;p&gt;细心的同学可能已经发现了，&lt;code&gt;type(name, bases, dict)&lt;/code&gt;其实是一个构造函数（见上文官方文档的引用: &lt;code&gt;return a new type object&lt;/code&gt;)。而我之前却说，它返回一个类。其实，一个类就是一个&lt;code&gt;type&lt;/code&gt;的对象。这个结果不算惊世骇俗：我们已经知道类实例是由类创建的一个对象，那么既然类也是一个对象，理应有一个更加强大的存在能够创建类。我们称这个更加强大的存在为&lt;code&gt;元类(metaclass)&lt;/code&gt;，即类的类。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;（注如果你在其他地方接触过“元xx”，应该很容易就能理解。比如“元数据”就是描述数据的数据）&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;无疑，&lt;code&gt;type&lt;/code&gt;就是一个元类，并且它还是所有类的元类:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 通过__class__属性可获得对象的类&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; age &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 35&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; age.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;int&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;bob&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;str&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; foo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(): &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;foo.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;function&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Bar&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Bar()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;class&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;__main__.Bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 通过查看对象的__class__.__class__,可以看出所有类的类都是type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; age.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;type&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;type&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; foo.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;type&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__class__&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;type&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在你知道，为什么是&lt;code&gt;type&lt;/code&gt;，而不是&lt;code&gt;Type&lt;/code&gt;了吧。(提示，对比下&lt;code&gt;str&lt;/code&gt;，&lt;code&gt;int&lt;/code&gt;你就懂啦)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意到，上面的代码段有一句&lt;code&gt;&amp;lt;type &apos;function&apos;&amp;gt;&lt;/code&gt;，这表示存在一个内建的&lt;code&gt;function&lt;/code&gt;类\
所以为什么说函数也是一个对象，因为它们都是function类的一个实例。\
联系&lt;code&gt;关键字&lt;/code&gt;的知识，当我们使用&lt;code&gt;def&lt;/code&gt;关键字时，就是在告诉python解释器“请给我一个function实例”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;除了使用&lt;code&gt;type(name, bases, dict)&lt;/code&gt;来动态地创建类外，我们还可以自定义元类，并用自定义的元类来&lt;strong&gt;控制类的创建行为&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如说，我希望类的所有属性都加上前缀&lt;code&gt;kissg_&lt;/code&gt;。当然我可以在定义类的时候为每个属性手动地加上&lt;code&gt;kiss_&lt;/code&gt;前缀。而另一种行之有效的方法就是自定义一个元类，由它在创建类的时候，自动地给每个属性加上&lt;code&gt;kissg_&lt;/code&gt;前缀。这就是所谓的“控制类的创建行为”,并且它是自动进行的。&lt;/p&gt;
&lt;p&gt;那么，如何来自定义元类呢？其实只要我们明白了&lt;code&gt;type&lt;/code&gt;是如何创建类的，自定义元类就是非常简单的一件事，无非就是接收类定义，并修改类定义，再返回一个类。而且，我们完全可以调用&lt;code&gt;type&lt;/code&gt;函数来返回这个类，从而简化操作。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 我们已经知道type是一个元类，因此自定义元类应继承自type或其子类&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 有一个约定俗成的习惯，自定义元类一般以Metaclass作为后缀，以明确表示这是一个元类&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AddPrefixMetaclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # __new__方法在__init__方法之前被调用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 因此，当我们想要控制类的创建行为时，一般使用__new__方法&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 定义普通类的方法时，我们用self作为第一个参数，来指向调用方法的类实例本身&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 此处addprefix_metaclass的意义与self类似，用于指向使用该元类创建的类本身&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 其他参数就是类的定义了，依次是类名，父类的元组，属性的字典&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __new__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(addprefix_metaclass, class_name, class_bases, class_dict):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        prefix &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;kissg_&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        addprefix_dict &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {} &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 我们用一个新的字典来储存加了前缀的属性&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 遍历类的属性，为所有非特殊属性与私有属性加上前缀&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name, val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; class_dict.items():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name.startswith(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;_&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                addprefix_dict[prefix &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                addprefix_dict[name] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 调用type函数来返回类，此时我们使用的是加了前缀的属性字典&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(class_name, class_bases, addprefix_dict)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 指定metaclass为自定义的元类，将在创建类时使用该自定义元类&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Myclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;metaclass&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddPrefixMetaclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;kissg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kg &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Myclass()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;hasattr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Myclass, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出: False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;hasattr&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Myclass, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;kissg_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出: True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(kg.kissg_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 输出: kissg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如你所见，自定义元类就这么简单，而元类的使用同样简单，只需在类定义时像使用关键字参数一样，指定metaclass为自定义的元类即可。&lt;/p&gt;
&lt;p&gt;按照原文的说法，以上自定义元类不是面向对象编程(Object-oriented programming,简称OOP)的正确写法。正确的写法应该是这样的：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AddPrefixMetaclass&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # 此处__new__的参数也是约定俗成的写法，就像用**kw表示关键字参数一样&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # cls - 使用自定义元类要创建的类，你可以就简单地记成self&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # clsname - 类名&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # bases - 父类的元组的(tuple)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # dct - 类属性的字典&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; __new__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cls, clsname, bases, dct):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        prefix &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;kissg_&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        addprefix_dict &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name, val &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dct.items():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name.startswith(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;_&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                addprefix_dict[prefix &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; name] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            else&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                addprefix_dict[name] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 元类也是可以被继承的。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # 调用父类的__new__方法来创建类,简化继承&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; super&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AddPrefixMetaclass, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;__new__&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cls&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, clsname, bases, addprefix_dict)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是比之前的优雅了许多?既避免了直接调用&lt;code&gt;type&lt;/code&gt;函数，又使用&lt;code&gt;super&lt;/code&gt;使继承显得更容易了，而且使用约定俗称的命名方法立显规范与高大上气息。&lt;/p&gt;
&lt;p&gt;最后，创建类的元类是可以被继承的，有点拗口，但请区别于元类是可以被继承的。这句话的意思是：定义子类时没有指定元类（即没有&lt;code&gt;metaclass=XXXMetaclass&lt;/code&gt;），将自动使用其父类的元类来创建该子类。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;注: python2还可以通过指定__metaclass__属性为元类或其他任何能返回类的东西（比如函数）来控制类的创建。python3虽然保留了__metaclass__属性，但其实并无用处，因此本文不展开讲。有兴趣的同学可以看看&lt;a href=&quot;http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python/6581949#6581949&quot;&gt;原文&lt;/a&gt;，但我觉得没太大必要&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;元类被誉为python的黑魔法(black magic)之一，一方面强调了元类使用的困难，另一方面也强调了元类的强大。如果你仔细看过正文的内容，会发现元类的使用似乎也不太难。如果觉得仍有难度，再看一遍，或者可以看下&lt;a href=&quot;http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python/6581949#6581949&quot;&gt;原文&lt;/a&gt;。当然本文所举的例子都比较简单，你完全可以用元类实现一些更加强大的功能，比如自定义一个ORM(Object Relational Mapping，对象关系映射),我也是基于此，才找了一些材料学习元类的。&lt;/p&gt;
&lt;p&gt;总的来说,元类就做了以下3件事:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;拦截类的创建&lt;/li&gt;
&lt;li&gt;修改类定义&lt;/li&gt;
&lt;li&gt;返回修改后的类&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也许这样将步骤拆分了，你能更好得理解记忆。而元类真的就这么简单。&lt;/p&gt;
&lt;p&gt;引用原文的一句话作结：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Everything is an object in python, and they are all either instances of classes or instances of metaclasses.\
(在python的世界里，一切皆为对象，它们要么是类的实例，要么是元类的实例。)&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>笔记</category><category>Python</category><category>metaclass</category></item><item><title>Python之生成器详解</title><link>https://lifelonglove.cn/articles/python-generator-yield/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/python-generator-yield/</guid><description>从可迭代对象与迭代器的协议出发，说明生成器函数如何借助 yield 暂停并恢复执行，以及惰性计算为何能降低内存占用并表达连续数据流。</description><pubDate>Sat, 09 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;一叶落知天下秋,知秋更可知叶落.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;引文&quot;&gt;引文&lt;/h2&gt;
&lt;p&gt;编程派前几天推送了一篇文章,叫&lt;a href=&quot;http://codingpy.com/article/python-progression-path-simple-version/&quot;&gt;“Python学习进阶路线(简版)”&lt;/a&gt;,&lt;code&gt;生成器(generator)&lt;/code&gt;赫然在列.可是我不太会.不会怎么办?学咯.于是上网看了不少教程,又看了官方文档,学到了不少知识.在此,权且做个学习笔记,也与大家分享一下.&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;要理解&lt;code&gt;generator&lt;/code&gt;,我们先从&lt;code&gt;迭代(iteration)&lt;/code&gt;与&lt;code&gt;迭代器(iterator)&lt;/code&gt;讲起.当然,本文的重点是&lt;code&gt;generator&lt;/code&gt;,&lt;code&gt;iteration&lt;/code&gt;与&lt;code&gt;iterator&lt;/code&gt;的知识将点到即止.&lt;a href=&quot;#generator&quot;&gt;直接看&lt;code&gt;generator&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;迭代是重复反馈过程的活动，其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”，而每一次迭代得到的结果会被用来作为下一次迭代的初始值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上是&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E8%BF%AD%E4%BB%A3&quot;&gt;维基百科&lt;/a&gt;对迭代的定义.在python中,迭代通常是通过&lt;code&gt;for ... in ...&lt;/code&gt;来完成的,而且只要是&lt;code&gt;可迭代对象(iterable)&lt;/code&gt;,都能进行迭代.这里简单讲下&lt;code&gt;iterable&lt;/code&gt;与&lt;code&gt;iterator&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;iterable&lt;/code&gt;是实现了&lt;code&gt;__iter__()&lt;/code&gt;方法的对象.更确切的说,是&lt;code&gt;container.__iter__()&lt;/code&gt;方法,该方法返回的是的一个&lt;code&gt;iterator&lt;/code&gt;对象,因此&lt;code&gt;iterable&lt;/code&gt;是你可以从其获得&lt;code&gt;iterator&lt;/code&gt;的对象.~~使用&lt;code&gt;iterable&lt;/code&gt;时,将一次性返回所有结果,都存放在内存中,并且这些值都能重复使用.~~以上说法严重错误!对于&lt;code&gt;iterable&lt;/code&gt;,我们该关注的是,它是一个能一次返回一个成员的对象(iterable is an object capable of returning its members one at a time),一些&lt;code&gt;iterable&lt;/code&gt;将所有值都存储在内存中,比如&lt;code&gt;list&lt;/code&gt;,而另一些并不是这样,比如我们下面将讲到的&lt;code&gt;iterator&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;iterator&lt;/code&gt;是实现了&lt;code&gt;iterator.__iter__()&lt;/code&gt;和&lt;code&gt;iterator.__next__()&lt;/code&gt;方法的对象.&lt;code&gt;iterator.__iter__()&lt;/code&gt;方法返回的是&lt;code&gt;iterator&lt;/code&gt;对象本身.根据官方的说法,正是这个方法,实现了&lt;code&gt;for ... in ...&lt;/code&gt;语句.而&lt;code&gt;iterator.__next__()&lt;/code&gt;是&lt;code&gt;iterator&lt;/code&gt;区别于&lt;code&gt;iterable&lt;/code&gt;的关键了,它允许我们&lt;em&gt;&lt;strong&gt;显式&lt;/strong&gt;&lt;/em&gt;地获取一个元素.当调用&lt;code&gt;next()&lt;/code&gt;方法时,实际上产生了2个操作:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;更新&lt;code&gt;iterator&lt;/code&gt;状态,令其指向后一项,以便下一次调用&lt;/li&gt;
&lt;li&gt;返回当前结果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你学过&lt;code&gt;C++&lt;/code&gt;,它其实跟指针的概念很像(如果你还学过链表的话,或许能更好地理解).&lt;/p&gt;
&lt;p&gt;正是&lt;code&gt;__next__()&lt;/code&gt;,使得&lt;code&gt;iterator&lt;/code&gt;能在每次被调用时,返回一个单一的值(有些教程里,称为一边循环,一边计算,我觉得这个说法不是太准确.但如果这样的说法有助于你的理解,我建议你就这样记),从而极大的节省了内存资源.另一点需要格外注意的是,&lt;code&gt;iterator&lt;/code&gt;是消耗型的,即每一个值被使用过后,就消失了.因此,你可以将以上的操作2理解成&lt;code&gt;pop&lt;/code&gt;.对&lt;code&gt;iterator&lt;/code&gt;进行遍历之后,其就变成了一个空的容器了,但不等于&lt;code&gt;None&lt;/code&gt;哦.因此,若要重复使用&lt;code&gt;iterator&lt;/code&gt;,利用&lt;code&gt;list()&lt;/code&gt;方法将其结果保存起来是一个不错的选择.&lt;/p&gt;
&lt;p&gt;我们通过代码来感受一下.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; collections &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Iterable, Iterator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 众所周知,list是一个iterable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; iter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a)   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 通过iter()方法,得到iterator,iter()实际上调用了__iter__(),此后不再多说&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, Iterable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a, Iterator)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b, Iterable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; isinstance&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b, Iterator)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 可见,itertor一定是iterable,但iterable不一定是itertor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# iterator是消耗型的,用一次少一次.对iterator进行变量,iterator就空了!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; list&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; d &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; list&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(b)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 空的iterator并不等于None.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 再来感受一下next()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; iter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(a)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e)     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#next()实际调用了__next__()方法,此后不再多说&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;既然提到了&lt;code&gt;for ... in ...&lt;/code&gt;语句,我们再来简单讲下其工作原理吧,或许能帮助理解以上所讲的内容.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;     ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们对一个&lt;code&gt;iterable&lt;/code&gt;用&lt;code&gt;for ... in ...&lt;/code&gt;进行迭代时,实际是先通过调用&lt;code&gt;iter()&lt;/code&gt;方法得到一个&lt;code&gt;iterator&lt;/code&gt;,假设叫做X.然后循环地调用X的&lt;code&gt;next()&lt;/code&gt;方法取得每一次的值,直到iterator为空,返回的&lt;code&gt;StopIteration&lt;/code&gt;作为循环结束的标志.&lt;code&gt;for ... in ... &lt;/code&gt;会自动处理&lt;code&gt;StopIteration&lt;/code&gt;异常,从而避免了抛出异常而使程序中断.如图所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/python-generator-yield/for-in.png&quot; alt=&quot;what-really-happens-in-for-in-statement&quot;&gt;&lt;/p&gt;
&lt;p id = &quot;generator&quot;&gt;&lt;/p&gt;
&lt;p&gt;磨刀不误砍柴工,有了前面的知识,我们再来理解&lt;code&gt;generator&lt;/code&gt;与&lt;code&gt;yield&lt;/code&gt;将会事半功倍.&lt;/p&gt;
&lt;p&gt;首先先理清几个概念:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;generator&lt;/code&gt;: A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;generator iterator&lt;/code&gt;: An object created by a generator funcion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;generator expression&lt;/code&gt;: An expression that returns an iterator.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上的定义均来自&lt;a href=&quot;https://docs.python.org/3/glossary.html#term-generator&quot;&gt;python官方文档&lt;/a&gt;.可见,我们常说的&lt;code&gt;生成器&lt;/code&gt;,就是带有&lt;code&gt;yield&lt;/code&gt;的函数,而&lt;code&gt;generator iterator&lt;/code&gt;则是&lt;code&gt;generator function&lt;/code&gt;的返回值,即一个&lt;code&gt;generator&lt;/code&gt;对象,而形如&lt;code&gt;(elem for elem in [1, 2, 3])&lt;/code&gt;的表达式,称为&lt;code&gt;generator expression&lt;/code&gt;,实际使用与&lt;code&gt;generator&lt;/code&gt;无异.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (elem &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; elem &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;generator &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;genexpr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7f0d23888048&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; fib&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     a, b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;         yield&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;         a, b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b, a &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fib&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;function fib at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7f0d238796a8&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fib()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;generator &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; fib at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7f0d20bbfea0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实说白了,&lt;code&gt;generator&lt;/code&gt;就是&lt;code&gt;iterator&lt;/code&gt;的一种,以更优雅的方式实现的&lt;code&gt;iterator&lt;/code&gt;.官方的说法是:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Python’s generators provide a convenient way to implement the iterator protocol.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你完全可以像使用&lt;code&gt;iterator&lt;/code&gt;一样使用&lt;code&gt;generator&lt;/code&gt;,当然除了定义.定义一个&lt;code&gt;iterator&lt;/code&gt;,你需要分别实现&lt;code&gt;__iter__()&lt;/code&gt;方法和&lt;code&gt;__next__()&lt;/code&gt;方法,但&lt;code&gt;generator&lt;/code&gt;只需要一个小小的&lt;code&gt;yield&lt;/code&gt;(好吧,&lt;code&gt;generator expression&lt;/code&gt;的使用比较简单,就不展开讲了.)&lt;/p&gt;
&lt;p&gt;前文讲到&lt;code&gt;iterator&lt;/code&gt;通过&lt;code&gt;__next__()&lt;/code&gt;方法实现了每次调用,返回一个单一值的功能.而&lt;code&gt;yield&lt;/code&gt;就是实现&lt;code&gt;generator&lt;/code&gt;的&lt;code&gt;__next__()&lt;/code&gt;方法的关键!先来看一个最简单的例子:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; g&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;     print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1 is&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     yield&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;     print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2 is&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     yield&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;     print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;3 is&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     yield&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; z &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;generator &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g at &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;0x&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;7f0d2387c8b8&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(z)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; is&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(z)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; is&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(z)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; is&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(z)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Traceback (most recent call last):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  File &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;lt;stdin&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, line &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;StopIteration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一次调用&lt;code&gt;next()&lt;/code&gt;方法时,函数似乎执行到&lt;code&gt;yield 1&lt;/code&gt;,就暂停了.然后再次调用&lt;code&gt;next()&lt;/code&gt;时,函数从&lt;code&gt;yield 1&lt;/code&gt;之后开始执行的,并再次暂停.第三次调用&lt;code&gt;next()&lt;/code&gt;,从第二次暂停的地方开始执行.第四次,抛出&lt;code&gt;StopIteration&lt;/code&gt;异常.&lt;/p&gt;
&lt;p&gt;事实上,&lt;code&gt;generator&lt;/code&gt;确实在遇到&lt;code&gt;yield&lt;/code&gt;之后暂停了,确切点说,是先返回了&lt;code&gt;yield&lt;/code&gt;表达式的值,再暂停的.当再次调用&lt;code&gt;next()&lt;/code&gt;时,从先前暂停的地方开始执行,直到遇到下一个&lt;code&gt;yield&lt;/code&gt;.这与上文介绍的对&lt;code&gt;iterator&lt;/code&gt;调用&lt;code&gt;next()&lt;/code&gt;方法,执行原理一般无二.&lt;/p&gt;
&lt;p&gt;有些教程里说&lt;code&gt;generator&lt;/code&gt;保存的是算法,而我觉得用&lt;code&gt;中断服务子程序&lt;/code&gt;来描述&lt;code&gt;generator&lt;/code&gt;或许能更好理解,这样你就能将&lt;code&gt;yield&lt;/code&gt;理解成一个中断服务子程序的&lt;code&gt;断点&lt;/code&gt;,没错,是中断服务子程序的断点.我们每次对一个&lt;code&gt;generator&lt;/code&gt;对象调用&lt;code&gt;next()&lt;/code&gt;时,函数内部代码执行到“断点”&lt;code&gt;yield&lt;/code&gt;,然后返回这一部分的结果,并保存上下文环境,“中断”返回.&lt;/p&gt;
&lt;p&gt;怎么样,是不是瞬间就明白了&lt;code&gt;yield&lt;/code&gt;的用法?&lt;/p&gt;
&lt;p&gt;我们再来看另一段代码.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; gen&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;():&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; True&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;         s &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; yield&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;         print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(s)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; gen()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g.send(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;kissg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Traceback (most recent call last):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  File &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;lt;stdin&amp;gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, line &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;TypeError&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: can&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;t send non-None value to a just-started generator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(g)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g.send(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;kissg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kissg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我正是看到这个形式的&lt;code&gt;generator&lt;/code&gt;,懵了,才想要深入学习&lt;code&gt;generator&lt;/code&gt;与&lt;code&gt;yield&lt;/code&gt;的.结合以上的知识,我再告诉你,&lt;code&gt;generator&lt;/code&gt;其实有第2种调用方法(恢复执行),即通过&lt;code&gt;send(value)&lt;/code&gt;方法将&lt;code&gt;value&lt;/code&gt;作为&lt;code&gt;yield&lt;/code&gt;表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了.当我们调用&lt;code&gt;send(value)&lt;/code&gt;方法时,&lt;code&gt;generator&lt;/code&gt;正由于&lt;code&gt;yield&lt;/code&gt;的缘故被暂停了.此时,&lt;code&gt;send(value)&lt;/code&gt;方法传入的值作为&lt;code&gt;yield&lt;/code&gt;表达式的值,函数中又将该值赋给了变量&lt;code&gt;s&lt;/code&gt;,然后print函数打印&lt;code&gt;s&lt;/code&gt;,循环再遇到&lt;code&gt;yield&lt;/code&gt;,暂停返回.&lt;/p&gt;
&lt;p&gt;调用&lt;code&gt;send(value)&lt;/code&gt;时要注意,要确保,&lt;code&gt;generator&lt;/code&gt;是在&lt;code&gt;yield&lt;/code&gt;处被暂停了,如此才能向&lt;code&gt;yield&lt;/code&gt;表达式传值,否则将会报错(如上所示),可通过&lt;code&gt;next()&lt;/code&gt;方法或&lt;code&gt;send(None)&lt;/code&gt;使&lt;code&gt;generator&lt;/code&gt;执行到&lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;再来看一段&lt;code&gt;yield&lt;/code&gt;更复杂的用法,或许能加深你对&lt;code&gt;generator&lt;/code&gt;的&lt;code&gt;next()&lt;/code&gt;与&lt;code&gt;send(value)&lt;/code&gt;的理解.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; def&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;None&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;   while&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;yield&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;     print&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;The value is&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;     if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; value:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; echo(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(g)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g.send(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;The value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; g.send(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;The value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(g)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;The value &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; None&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码既有&lt;code&gt;yield value&lt;/code&gt;的形式,又有&lt;code&gt;value = yield&lt;/code&gt;形式,看起来有点复杂.但以&lt;code&gt;yield&lt;/code&gt;分离代码进行解读,就不太难了.第一次调用&lt;code&gt;next()&lt;/code&gt;方法,执行到&lt;code&gt;yield value&lt;/code&gt;表达式,保存上下文环境暂停返回&lt;code&gt;1&lt;/code&gt;.第二次调用&lt;code&gt;send(value)&lt;/code&gt;方法,从&lt;code&gt;value = yield&lt;/code&gt;开始,打印,再次遇到&lt;code&gt;yield value&lt;/code&gt;暂停返回.后续的调用&lt;code&gt;send(value)&lt;/code&gt;或&lt;code&gt;next()&lt;/code&gt;都不外如是.&lt;/p&gt;
&lt;p&gt;但是,这里就引出了另一个问题,&lt;code&gt;yield&lt;/code&gt;作为一个暂停恢复的点,代码从&lt;code&gt;yield&lt;/code&gt;处恢复,又在下一个&lt;code&gt;yield&lt;/code&gt;处暂停.可见,在一次&lt;code&gt;next()&lt;/code&gt;(非首次)或&lt;code&gt;send(value)&lt;/code&gt;调用过程中,实际上存在&lt;strong&gt;2&lt;/strong&gt;个&lt;code&gt;yield&lt;/code&gt;,一个作为恢复点的&lt;code&gt;yield&lt;/code&gt;与一个作为暂停点的&lt;code&gt;yield&lt;/code&gt;.因此,也就有2个&lt;code&gt;yield&lt;/code&gt;表达式.&lt;code&gt;send(value)&lt;/code&gt;方法是将值传给&lt;em&gt;恢复点&lt;/em&gt;&lt;code&gt;yield&lt;/code&gt;;调用&lt;code&gt;next()&lt;/code&gt;表达式的值时,其&lt;strong&gt;恢复点&lt;/strong&gt;&lt;code&gt;yield&lt;/code&gt;的值总是为&lt;code&gt;None&lt;/code&gt;,而将&lt;strong&gt;暂停点&lt;/strong&gt;的&lt;code&gt;yield&lt;/code&gt;表达式的值返回.为方便记忆,你可以将此处的&lt;strong&gt;恢复点&lt;/strong&gt;记作当前的(current),而将&lt;strong&gt;暂停点&lt;/strong&gt;记作下一次的(next),这样就与&lt;code&gt;next()&lt;/code&gt;方法匹配起来啦.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;generator&lt;/code&gt;还实现了另外两个方法&lt;code&gt;throw(type[, value[, traceback]])&lt;/code&gt;与&lt;code&gt;close()&lt;/code&gt;.前者用于抛出异常,后者用于关闭&lt;code&gt;generator&lt;/code&gt;.不过这2个方法似乎很少被直接用到,本文就不再多说了,有兴趣的同学请看&lt;a href=&quot;https://docs.python.org/3/reference/expressions.html#generator.throw&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;小结&quot;&gt;小结&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/img/python-generator-yield/iterators-generators-iterables.png&quot; alt=&quot;iterable-iterator-generator&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可迭代对象(Iterable)是实现了&lt;code&gt;__iter__()&lt;/code&gt;方法的对象,通过调用&lt;code&gt;iter()&lt;/code&gt;方法可以获得一个迭代器(Iterator)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;迭代器(Iterator)是实现了&lt;code&gt;__iter__()&lt;/code&gt;和&lt;code&gt;__next__()&lt;/code&gt;的对象&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;for ... in ...&lt;/code&gt;的迭代,实际是将可迭代对象转换成迭代器,再重复调用&lt;code&gt;next()&lt;/code&gt;方法实现的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生成器(generator)是一个特殊的迭代器,它的实现更简单优雅.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;yield&lt;/code&gt;是生成器实现&lt;code&gt;__next__()&lt;/code&gt;方法的关键.它作为生成器执行的暂停恢复点,可以对&lt;code&gt;yield&lt;/code&gt;表达式进行赋值,也可以将&lt;code&gt;yield&lt;/code&gt;表达式的值返回.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;(本人刚开始写博客,本文写得狠辛苦,转载希望著明出处,不胜感谢.)&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>文章</category><category>Python</category><category>generator</category><category>yield</category></item><item><title>Welcome to kissg Blog</title><link>https://lifelonglove.cn/articles/hello-blog/</link><guid isPermaLink="true">https://lifelonglove.cn/articles/hello-blog/</guid><description>记录从收到域名、尝试 Jekyll 到采用现成博客模板的建站过程，也解释了为何重新开始维护个人博客：把学习、折腾和持续写作留在自己的空间里。</description><pubDate>Fri, 01 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;kissg Blog is now on!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;特别感谢&lt;a href=&quot;http://huangxuan.me&quot;&gt;Hux,黄玄&lt;/a&gt;,本博客就是使用他的模板.&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;正文&lt;/h2&gt;
&lt;p&gt;第一篇博客,按照惯例,先讲讲搭建博客的过程吧.&lt;/p&gt;
&lt;p&gt;第一次想写博客是在去年10月份.那天我生日,好友Coffee送了我一个域名.没错,是域名!看着我一脸懵逼的表情,他说:如果你不知道干嘛的话,去Google一下“Github + Jekyll”写博客吧.&lt;/p&gt;
&lt;p&gt;头几天,兴致很足,跟着&lt;a href=&quot;http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html&quot;&gt;阮一峰老师的教程&lt;/a&gt;走了一遍,成功!能通过 “username.github.io/xxx” 的方式访问博客了.但是绑定域名就一直失败.当时不知道原因所在,现在想想,原因应该是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;publishing branch不正确;&lt;/li&gt;
&lt;li&gt;_config.yml的baseurl没有设置好.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于第一点,Github上有明确的说明:
&lt;img src=&quot;/img/hello-blog/github-pages-repos.png&quot; alt=&quot;Github Pages Publishing branch 说明&quot;&gt;
而阮一峰老师的教程里使用的是&lt;code&gt;gh-pages&lt;/code&gt;分支,因此是无法通过“username.github.io”直接访问博客的,而域名又直接绑定到“username.github.io”上,因此一直都会是404错误.可惜当时没明白,兴致缺缺然,也就没再玩了.&lt;/p&gt;
&lt;p&gt;时隔半年,就在我差点就要忘了那个域名的存在的时候,邂逅了Hux的博客(好吧,这里我真的很想用“惊为天人”来描述我当时的感受).再加上一些其他因素,就再次萌生了写博客的冲动.&lt;/p&gt;
&lt;p&gt;以下记录了本人搭建“Github Pages+ Jekyll”写作环境的全过程.希望能对其他有写博客想法的同学有所帮助.(如果有想学习如何写博客的同学,请移步&lt;a href=&quot;http://www.ruanyifeng.com/blog/2012/08/blogging_with_jekyll.html&quot;&gt;阮一峰老师的教程&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;以下的指令全部基于Ubuntu14.04 LTS,其他操作系统可能略有不同,请自行更正.&lt;/p&gt;
&lt;p&gt;首先,安装&lt;code&gt;git&lt;/code&gt;,这个似乎不需多描述.使用Ubuntu的包管理工具下载的git是1.9.1版的,并不影响使用.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其次就是安装&lt;code&gt;Jekyll&lt;/code&gt;了.&lt;/p&gt;
&lt;p&gt;根据&lt;code&gt;Jekyll&lt;/code&gt;官网的推荐,我安装的是&lt;code&gt;node.js&lt;/code&gt;,是下载的源码,自行编译安装的.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Downloads/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;wget&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://nodejs.org/dist/v4.4.0/node-v4.4.0.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; #强迫症使然,一定要下载到/Downloas下&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; gcc&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; make&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; g++&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                                               #编译需要用到的工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tar&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -xvf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; node-v4.4.0.tar.gz&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; node-v4.4.0/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;./configure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                                                            #可通过node -v验证安装&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后就是安装&lt;code&gt;ruby&lt;/code&gt;了.这里有一些波折.
我一开始使用Ubuntu的包管理工具安装&lt;code&gt;ruby&lt;/code&gt;,版本比较旧,是1.9.3的,而2.0以下的&lt;code&gt;ruby&lt;/code&gt;并不支持&lt;code&gt;Jekyll3.0&lt;/code&gt;及以上.当我询问过Hux之后,被告知&lt;code&gt;Github Pages&lt;/code&gt;官方进行了升级,要升级至&lt;code&gt;Jekyll3&lt;/code&gt;以上(原话).因此,我也其实并没有尝试使用&lt;code&gt;Jekyll2&lt;/code&gt;来搭配Hux的模板.&lt;/p&gt;
&lt;p&gt;然后卸载ruby,重新安装.这次通过&lt;code&gt;ruby&lt;/code&gt;管理工具来安装最新版本的&lt;code&gt;ruby&lt;/code&gt;.我选择的是&lt;code&gt;rvm&lt;/code&gt;,以下是安装过程:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --keyserver&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; hkp://keys.gnupg.net&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --recv-keys&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 409B6B1796C275462A1703113804BB82D39DC0E3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;\curl&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -sSL&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://get.rvm.io&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bash&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; stable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ~/.rvm/scripts/rvm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再安装&lt;code&gt;ruby2.3.0&lt;/code&gt;,以及&lt;code&gt;Jekyll&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;rvm&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2.3.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://rubygems.org/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -a&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://ruby.taobao.org/&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; #更新源&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; jekyll&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此,环境算是基本搭建完成了~&lt;/p&gt;
&lt;p&gt;终于可以开始我的博客之旅了!我直接使用的Hux的模板,因此比较省事.但问题依旧存在.&lt;/p&gt;
&lt;p&gt;如前所属,如果想通过 “username.github.io” 直接进行博客访问,要设置_config.yml的baseurl的值为空或“/“,并且选择&lt;code&gt;master&lt;/code&gt;分支.本人在此吃了无数的亏,这是血与泪的教训啊!&lt;/p&gt;
&lt;p&gt;希望绑定自己的域名的话,需要先在博客的根目录下创建CNAME文件,在其中添加想要绑定的域名.然后去域名提供商那里添加解析即可.Coffee送我的域名是万网上买的,现在被阿里收购了.因此我就是去万网添加域名解析.添加2条A型记录,主机记录都设置为&lt;code&gt;www&lt;/code&gt;,解析线路我选择的默认,记录值分别是192.30.252.154/153.再添加一条C型记录,主机记录设为&lt;code&gt;@&lt;/code&gt;,记录值设为&lt;code&gt;username.github.io&lt;/code&gt;即可.&lt;/p&gt;
&lt;p&gt;到此,就算是大功告成了~&lt;/p&gt;
&lt;h2 id=&quot;后记&quot;&gt;后记&lt;/h2&gt;
&lt;p&gt;呼~从3.29到4.1,总算是将第一篇博文了结了!&lt;/p&gt;
&lt;p&gt;看得出来,我是小小菜鸟一枚,姑且就将博客命名为“菜鸟成长日记”吧.&lt;/p&gt;
&lt;p&gt;无始方能无终.&lt;/p&gt;
</content:encoded><category>文章</category><category>无始</category></item></channel></rss>