<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>古明地觉的编程教室</title>
    <link>https://wechat2rss.xlab.app/feed/9e21dbf7a7cca45762bbed43f86cf04f82b23e1a.xml</link>
    <description>Python、Rust 程序猿，你感兴趣的内容我都会写，点个关注吧(#^.^#)&#xA;(wechat feed made by @ttttmr https://wechat2rss.xlab.app)</description>
    <managingEditor> (古明地觉的编程教室)</managingEditor>
    <image>
      <url>https://wx.qlogo.cn/mmhead/Q3auHgzwzM5Fkmlvg8tY5dOX17y2Sm9my8HicrKaWNIsx9xbGZzOjTQ/0</url>
      <title>古明地觉的编程教室</title>
      <link>https://wechat2rss.xlab.app/feed/9e21dbf7a7cca45762bbed43f86cf04f82b23e1a.xml</link>
    </image>
    <item>
      <title>兜兜转转，我还是回到了 ChatGPT 的怀中</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532328&amp;idx=1&amp;sn=de0c2b6435d19916680a69773de9cb5c</link>
      <description>记得 2022 年底，ChatGPT 刚出来的时候，很多人都只是把它当做一个玩具。</description>
      <content:encoded><![CDATA[<p>原创 <span>古明地觉</span> <span>2026-03-07 14:52</span> <span style="display: inline-block;">北京</span></p>






  
  <p><img src="https://wechat2rss.xlab.app/img-proxy/?k=855b4473&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_jpg%2Ft1xRt9m6oAl1tcjs0tm0RhDtSHxwP5dBmyYkZrYAbUSI4s66z1nmGdDccz6Zp0cC2fIqiawWeyJ9bicucfy2TWXG1NbZPBtNribVBvbA5lWpwk%2F0%3Fwx_fmt%3Djpeg"/></p>
  
  <p><span leaf="">记得 2022 年底，ChatGPT 刚出来的时候，很多人都只是把它当做一个玩具。因为当时的版本是 GPT-3.5，还并不是很智能，我们做个对比。</span></p><p style="text-align: left;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.8212962962962963" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100048672" src="https://wechat2rss.xlab.app/img-proxy/?k=04762d74&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Ft1xRt9m6oAmDerfAVQU9EBxL48ImEuiciaicGpibEq71iccSrbicaDh6xOvPE4bJmIyrphaB6b6oqQEep0zcDOpe659z2PgKlkpYXq9FneAxUAWyo%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">可以看到 GPT-3.5 不会对用户的问题提出质疑，有时也会说一堆正确的废话，总之得不到想要的答案。</span></p><p><span leaf="">当时我已经隐隐感觉到，搞技术的人天要塌了，但我并没有布局 AI，也没有持仓英伟达，因为没那个高瞻远瞩的眼光。毕竟谁能想到，短短几年时间，AI 能发展得这么迅猛。</span></p><p><span leaf="">之后 Claude 诞生了，我看推特上都在说自己已经转向 Claude 了，于是我也抱着兴趣试了试。试了之后发现，这玩意写代码简直无敌，之后我便退订 GPT，订阅 Claude 了。</span></p><p><span leaf="">然后一直用到现在，直到号被封，现在想想心还在滴血。当然除了 Claude，我还用 Gemini，这是我薅了谷歌的羊毛，送了我 15 个月的 Gemini Pro。</span></p><p><span leaf="">所以之前我都是 Claude 和 Gemini 搭配使用的，这两个 AI 都挺不错，我实际用下来体验感很好。但现在 Claude 被封了，正好 OpenAI 说他们发布了 GPT-5.4，并给出了测试报告。</span></p><p style="text-align: left;"><span leaf=""><img data-aistatus="1" alt="图像" class="rich_pages wxw-img" data-ratio="0.562962962962963" data-type="png" data-w="1080" data-imgfileid="100048673" src="https://wechat2rss.xlab.app/img-proxy/?k=09454b12&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Ft1xRt9m6oAklzVbneTw25ENiciaRrXAIz7f5WIybkMffkAlUKYwHT7MhTPTHMd5UyYicTaelTGUntN1bDexytUBLu2by75icf9Gxo2aqu0zOOJw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></span></p><p><span leaf="">所以我决定回归 GPT。</span></p><p><span leaf="">不得不说这些 AI 厂商之间，大有一幅你方唱罢我登场的样子，你发布了新版本的模型，过段时间我也要发布一个。</span></p><p><span leaf="">最后向大家介绍这几个 AI 的充值办法。</span></p><p><span leaf="">Gemini 的话很简单，因为谷歌对银行卡的限制不严格，你用国内招商的全币种 Visa 就可以。但 GPT 和 Claude 不行，你在充值的时候如果用国内的银行卡，基本都会被拒绝。</span></p><p><span leaf="">Claude 的话，你需要有一台 iPhone，然后在 App Store 里面搜索 Claude，将 APP 下载下来。注：下载之前需要将</span><span leaf="" data-pm-slice="1 1 [&#34;para&#34;,null]">地区改成美国，因为大陆地区搜不到。</span></p><p><span leaf="">下载完之后将地区改回去，这样就可以绑定支付宝了，然后直接打开 Claude App 订阅即可。假设你订阅的 Pro，一个月 20 美元，会直接按照汇率计算出人民币，从你的支付宝里面扣除。</span></p><p style="text-align: left;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.4925925925925926" data-s="300,640" data-type="jpeg" data-w="1080" type="block" data-imgfileid="100048674" src="https://wechat2rss.xlab.app/img-proxy/?k=99e64842&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_jpg%2Ft1xRt9m6oAlAN20HTExYQ9rh8qO59JCqfE2Bb7b9k1mWzay90oOia1YOzOqLhcJQjNTXAzyJlx8nnD4ib2NOlRwBP4FtFxqR3L2ubGXSR1tLk%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p><span leaf="">最后是 GPT，我们同样要使用 iOS 订阅，但它更复杂一些。因为当你将地区改成中国大陆之后，GPT 会感知到，它不让你支付，或者说支付失败，所以你的 App Store 从始至终都必须是美国地区。</span></p><p><span leaf="">但美国地区无法绑定支付宝，经过测试，国内 Visa </span><span leaf="" data-pm-slice="1 1 [&#34;para&#34;,null]">也会被拒，那怎么办呢？还是点击支付宝，搜索 Pockyt Shop。</span></p><p style="text-align: left;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.9990740740740741" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100048675" src="https://wechat2rss.xlab.app/img-proxy/?k=c00aaa0c&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Ft1xRt9m6oAmHSCAsmZyf1pWr7EylLzlHlNXZ1yfDpnKnlfc5xJoQhVmMQllbePy94BxYbiaA28g0DouPEzo1ucAbrljQ60Opkp5QmqcBdlsc%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">点击 App Store，然后充值对应的美元即可。由于人民币涨了，所以充值 20 美元，大概花费 138 块多。付款成功之后，会给你一个 16 位的充值码，复制下来并打开 App Store，然后点击头像。</span></p><p style="text-align: center;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.5212962962962963" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100048676" src="https://wechat2rss.xlab.app/img-proxy/?k=5d10edf7&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Ft1xRt9m6oAm61fpT5ArssfcQKNWwkgE7FibxtkI1UrKVslvS29rMQh16EdWkzHCk5lxUHhCPhiaqtRrQjZoDlttWw5zs6xIEU4ugU4gTRDfVE%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">直接充值兑换即可，这样账户就有钱了，然后订阅即可。如果你的 App Store 里面有足够的钱，那么会立刻订阅成功，只有当你的账户没钱时，才会让你绑卡。</span></p><p><span leaf="">好，以上就是我使用 AI 的历程。还是多说一句，一定要拥抱 AI，技术越来越不值钱了。但问题是怎么用 AI 变现，相信大部分程序员都没有啥好的思路，这大概就是我们这些搞技术的局限性吧。</span></p><p><span leaf="">而那些拥有产品思维的人，却总能精准抓住用户需求，实现变现。比如 &#34;小猫补光灯&#34; 和 &#34;死了么&#34; 这两个 App，从技术含量来看几乎没有，但就是能火起来。</span></p><p><span leaf="">所以给初入职场的小伙伴一个建议，不用太过努力、太痴迷技术，除非你是 Top 0 级别的，否则很难有好下场。时代、机遇、偶然的好运，以及学会处理人际关系，远比搞技术更重要。当然最重要的是，要有一副好身体，不要卷到最后，把钱都给医院了。该休息就休息，该摸鱼就摸鱼。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=10334183&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532328%26idx%3D1%26sn%3Dde0c2b6435d19916680a69773de9cb5c">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Sat, 07 Mar 2026 14:52:00 +0800</pubDate>
    </item>
    <item>
      <title>悲报：Claude 账号被禁。Anthropic，我燥你冯</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532318&amp;idx=1&amp;sn=4a3beba0ae69deb3d5778f1d39821f08</link>
      <description>今天用 Claude 的时候发现号被禁了，虽然可以正常登录，但不让提问。我懵逼了，啥情况这是。</description>
      <content:encoded><![CDATA[<p>原创 <span>古明地觉</span> <span>2026-03-06 12:30</span> <span style="display: inline-block;">北京</span></p>






  
  <p><img src="https://wechat2rss.xlab.app/img-proxy/?k=dcfe5cb6&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_jpg%2Ft1xRt9m6oAmP1OvTyvrnWGXcrvOZk2DDHX5CbFJsicibDcM7lIvYEj6ibqDpEqvZZt1CNn0BRCqaojRMK7jgDIFonAfjLHI4soj6PF5iaNuaKQo%2F0%3Fwx_fmt%3Djpeg"/></p>
  
  <p><span leaf="">今天用 Claude 的时候发现号被禁了，虽然可以正常登录，但不让提问。</span></p><p style="text-align: left;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.24795640326975477" data-s="300,640" data-type="png" data-w="734" type="block" data-imgfileid="100048661" src="https://wechat2rss.xlab.app/img-proxy/?k=82de95a9&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2Ft1xRt9m6oAm67pTib3cpEfWUbPtibQtt8koib3GrVSyTCh5Z2T5iaySRd86rWXQO73RwpeGja7GvzAlPpKhiaj2elTicKP4zZljhTiatUbIrlV1nE0%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">我懵逼了，啥情况这是。</span></p><p><span leaf="">我知道 Claude 封号严重，但问题是我都用几年了，为啥今天突然被封了。</span></p><p><span leaf="">在此之前，我经常看到有人吐槽自己 Claude 账号被封，哪怕 IP 固定也被封。而我呢，不仅 IP 经常变，哪个国家的延迟低用哪个，而且还分享给我朋友，我俩一起用，从来没出过事。</span></p><p><span leaf="">我思考过原因，觉得可能有三种原因。</span></p><ul style="list-style-type: disc;" class="list-paddingleft-1"><li><p><span leaf="">1）我用的 IOS 订阅。</span></p></li><li><p><span leaf="">2）账号注册得很早，对于老用户，不进行风控，或者风控不严格。</span></p></li><li><p><span leaf="">3）单纯运气好，铁拳没有制裁到我（最不想看到）。</span></p></li></ul><p><span leaf="">直到我收到了 </span><span leaf="">Anthropic 发来的邮件。</span></p><p style="text-align: center;" nodeleaf=""><img class="rich_pages wxw-img" data-aistatus="1" data-imgfileid="100048662" data-ratio="0.7361111111111112" data-s="300,640" type="block" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=9e75a0a4&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Ft1xRt9m6oAmJjpC1f0ensfiaGzXE65rQQ8hJjbSYn7j7lhs3Z22JPsjoRvzZXRicD3NicUl9jKGm0tTURAM1e2etN7hFam92DIKNJpXiavpeWXY%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">这时候我想起来了，前两天我参与了 Claude 官方的一个活动。就是如果你在 github 上是知名项目的核心开发或维护者，或者有 star 数超过 5000 的项目等，那么可以免费申请 Claude Max 20。</span></p><p><span leaf="">于是前天我闲的没事，就申请了一下，结果今天被封了。思来想去，觉得应该就是这个原因，不然也太巧了，用了几年都没事，结果申请之后就被封了。</span></p><p><span leaf="">真的哔了狗了，会不会是 </span><span leaf="">Anthropic 通过 github 推断我来自 China，然后就把我的号给封了。</span></p><p><span leaf="">最后不管怎样，</span><span leaf="">Anthropic，我想对你说。</span></p><p style="text-align: center;" nodeleaf=""><img data-aistatus="1" class="rich_pages wxw-img" data-ratio="0.5053003533568905" data-s="300,640" data-type="png" data-w="849" type="block" data-imgfileid="100048666" src="https://wechat2rss.xlab.app/img-proxy/?k=836ab729&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2Ft1xRt9m6oAlnmycwxTQHZu7ibSqG4eSxiblpDPR4WZtl9Wunf8mHEcoaYPdczWib3ucsln0rkgImfGiaC7krwrEevokewWCoickNOKxodt3k8CUE%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=56e7df3e&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532318%26idx%3D1%26sn%3D4a3beba0ae69deb3d5778f1d39821f08">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Fri, 06 Mar 2026 12:30:00 +0800</pubDate>
    </item>
    <item>
      <title>ahocorasick：构建短信特征的利器</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532290&amp;idx=1&amp;sn=888cf5abac10a63e34f666cc5641680f</link>
      <description>在信贷风控领域，短信特征（即从借款人授权的短信内容中提取的结构化信息）正扮演着越来越重要的角色。</description>
      <content:encoded><![CDATA[<p>原创 <span>古明地觉</span> <span>2026-01-01 14:49</span> <span style="display: inline-block;">北京</span></p>






  
  <p><img src="https://wechat2rss.xlab.app/img-proxy/?k=90ae53da&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsGRCWleloP52BRNsrGD2tzVptue1sqOUzGm8HmX8GiagsNTKPrtdtnZGpryW8afptV4tj1icBbh6qhQ%2F0%3Fwx_fmt%3Djpeg"/></p>
  
  <p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mhsv71ba18bn" style="font-size: 15px;" data-mpa-action-id="mhsv71bs1u5m" data-pm-slice="0 0 []">在信贷风控领域，短信特征（即从借款人授权的短信内容中提取的结构化信息）正扮演着越来越重要的角色。它的核心价值在于，为金融机构提供了一个独特且有效的窗口，以评估那些传统征信记录覆盖不足或缺失的客群的信用风险。这对于拓展普惠金融服务、提升风控模型的精准度和覆盖面具有重要意义。</span></p><p><span leaf="" mpa-font-style="mhsv74fb3hn" style="font-size: 15px;" data-mpa-action-id="mhsv74fs1f7i" data-pm-slice="0 0 []">短信特征在信贷风控中的主要用途可以归结为以下几个方面：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mhsv7jqm1yl6" style="font-size: 15px;">补充传统征信的不足： 对于缺乏央行征信记录或信贷历史较短的 &#34;信用白户&#34;，短信数据成为了评估其还款意愿和还款能力的重要替代数据源，通过分析短信中的消费、缴费、收入等信息，可以初步勾勒出用户的财务状况。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv7jqmpeb" style="font-size: 15px;">多维度交叉验证，防范欺诈风险： 短信特征可以与用户在申请贷款时提交的其它信息进行交叉验证。例如可以通过分析银行、三方支付等发送的交易短信，核实用户的收入流水、消费水平和负债情况，有效识别信息造假和潜在的欺诈行为。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv7jqm2377" style="font-size: 15px;">构建更全面的用户画像： 短信内容涵盖了用户生活的方方面面，通过对这些非结构化数据进行深度挖掘和分析，可以构建出比传统数据更为立体和丰富的用户画像，这有助于更精准地评估用户的信用等级。</span></p></li></ul><p><span leaf="" mpa-font-style="mhsv7ysqtft" style="font-size: 15px;" data-mpa-action-id="mhsv7yt81wsd" data-pm-slice="0 0 []">风控模型通常会从海量的短信数据中提取出多种维度的特征，这些特征共同构成了对借款人信用的综合评估。那么从短信中可以挖掘出哪些关键特征呢？</span></p><div><div><p><table style="min-width: 75px;"><tbody><tr><td><p><span style="font-size: 15px;"><span leaf=""><span textstyle="" style="font-weight: bold;">特征类别</span></span></span></p></td><td><p><span style="font-size: 15px;"><span leaf=""><span textstyle="" style="font-weight: bold;">具体内容</span></span></span></p></td><td><p><span style="font-size: 15px;"><span leaf=""><span textstyle="" style="font-weight: bold;">风控应用价值</span></span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">收入与消费特征</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">工资入账、交易提醒、账单支付（信用卡、花呗、白条等）、线上线下消费记录。</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">评估用户的收入稳定性、消费能力和消费习惯，判断其现金流状况。</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">资产与负债特征</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">银行存款变动、理财产品通知、贷款审批与还款提醒、信用卡账单及额度信息。</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">了解用户的资产水平、负债规模和还款压力，评估其整体偿债能力。</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">行为与习惯特征</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">水电煤气等公共事业缴费提醒、航旅出行信息、会员服务通知、验证码短信。</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">分析用户的稳定性（如居住稳定性）、生活规律性以及对不同服务的需求和偏好。</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">风险预警特征</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">催收短信、逾期提醒、赌博或博彩类应用的验证码或通知、多头借贷平台的注册和借款信息。</span></span></p></td><td><p data-mpa-action-id="mhsv8jco1yeq"><span style="font-size: 15px;"><span leaf="">及时发现用户的潜在风险行为，如逾期、多头借贷、不良嗜好等，作为重要的风险预警信号。</span></span></p></td></tr></tbody></table></p></div></div><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mhsv95q5t3s" style="font-size: 15px;">而对于出海现金贷，我们通常关注的是最后一个，那么具体都要做哪些工作呢？</span></p><p><strong mpa-font-style="mhsv95q523el" style="font-size: 15px;"><span leaf="">1）关键词匹配与规则引擎</span></strong></p><p><span leaf="" mpa-font-style="mhsv95q568m" style="font-size: 15px;">首先风控系统会内置一个庞大的、持续更新的关键词库和规则库，这些库包含了市面上几乎所有已知现金贷平台的信息。</span></p><p><span leaf="" mpa-font-style="mhsv95q5fjs" style="font-size: 15px;">然后通过发件人的号码或名称、以及匹配关键词，来识别是否是现金贷平台发送的短信。</span></p><p><strong mpa-font-style="mhsv95q519uy" style="font-size: 15px;"><span leaf="">2）自然语言处理（NLP）</span></strong></p><p><span leaf="" mpa-font-style="mhsv95q51io3" style="font-size: 15px;">对于更复杂的短信，简单的关键词匹配可能不够，因为有可能是平台广告，这时就需要引入 NLP 技术来判断短信的真实意图。</span></p><p><strong mpa-font-style="mhsv95q5205b" style="font-size: 15px;"><span leaf="">3）文本挖掘</span></strong></p><p><span leaf="" mpa-font-style="mhsv95q51mfq" style="font-size: 15px;">从文本中提取关键的结构化信息，比如借款平台名称、申请时间、申请金额、授信额度、放款金额、还款日、是否命中指定的关键词等。</span></p><p><span leaf="" mpa-font-style="mhsv95q52h1" style="font-size: 15px;">当然啦，如果你知道其它现金贷平台的短信格式，能够区分出广告，那么 NLP 这一步也可以省略。</span></p><p data-pm-slice="0 0 []"><strong><span leaf="" mpa-font-style="mhsv9kbqfmk" style="font-size: 15px;">4）特征工程与指标计算</span></strong></p><p><span leaf="" mpa-font-style="mhsv9kbq15pv" style="font-size: 15px;">当短信被识别和解析后，系统会围绕多头借贷和逾期这两个核心风险点，构建一系列量化特征指标。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mhsv9kbqcdd" style="font-size: 15px;">申请平台数量： 在特定时间窗口内（如过去 7 天、15 天、30 天），用户向多少个不同的贷款平台发起了申请？如果平台过多，说明可能存在资金紧张、有多头借贷风险。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv9kbq1oxk" style="font-size: 15px;">申请频率： 用户在短期内申请贷款的次数。例如一天内向超过 3 个平台发送注册验证码请求，就是一个强烈的风险信号。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv9kbq1l75" style="font-size: 15px;">命中高风险平台数量： 用户是否在已知的高利贷、套路贷或风控口碑差的平台上进行过申请？</span></p></li><li><p><span leaf="" mpa-font-style="mhsv9kbq24sj" style="font-size: 15px;">信贷审批通过率： 在所有申请的平台中，有多少比例的申请被批准了？如果申请了很多家但都失败了，说明该用户资质可能存在严重问题。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv9kbq1056" style="font-size: 15px;">贷平台数量： 根据放款和还款短信，判断用户当前同时在多少个平台上有尚未结清的贷款。</span></p></li><li><p><span leaf="" mpa-font-style="mhsv9kbqay8" style="font-size: 15px;">逾期次数、天数：判断短信是否命中逾期、催收等关键词，获取逾期次数和天数。</span></p></li></ul><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mhsva0p61uoa" style="font-size: 15px;">基于以上特征便可以构建准入策略了，比如逾期次数大于 3，直接拒绝；使用的现金贷平台数量大于 10，直接拒绝。</span></p><p><span leaf="" mpa-font-style="mhsva0p61cxl" style="font-size: 15px;">所以短信特征工程的本质是通过用户的数字行为轨迹来评估其信用风险和还款能力，这套系统特别聪明的地方在于：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mhsva0p620b2" style="font-size: 15px;">全景视角：不只看一家的贷款记录，而是看整个市场的信贷行为。</span></p></li><li><p><span leaf="" mpa-font-style="mhsva0p6alo" style="font-size: 15px;">动态追踪：通过时序特征捕捉用户信用状况的变化趋势。</span></p></li><li><p><span leaf="" mpa-font-style="mhsva0p61kt" style="font-size: 15px;">细粒度分析：从 APP 级别到具体的金额、期限、逾期天数等。</span></p></li><li><p><span leaf="" mpa-font-style="mhsva0p61mh4" style="font-size: 15px;">行为预测：基于历史模式预测未来的还款表现。</span></p></li></ul><p><span leaf="" mpa-font-style="mhsva0p6ue6" style="font-size: 15px;">核心就是：你的短信暴露了你的真实财务状况。出现逾期，代表还款能力有问题；出现多平台借贷短信，代表资金紧张；出现催收短信，代表信用状况恶化。</span></p><p><span leaf="" mpa-font-style="mhsva0p61vn0" style="font-size: 15px;">所以短信特征本质上就是通过短信文本分析来做信用评估，这比传统的征信报告更加实时和全面，所以它在风控领域中非常重要。</span></p><hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"/><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mhsvanx516im" style="font-size: 15px;">关于短信特征，我们就简单聊了一下，当然接下来才是本文真正的重点。我们说了，要通过解析短信文本来构建一系列特征指标，那么这个解析过程该怎么做呢？</span></p><p><span leaf="" mpa-font-style="mhsvanx51eyi" style="font-size: 15px;">毫无疑问，解析过程的核心是关键词匹配，所以正则就是一个非常自然的选择。但我们不能光依赖正则，因为在匹配具体的关键词时，正则的速度不够快，我们还需要一个模块叫 ahocorasick。</span></p><p><span leaf="" mpa-font-style="mhsvanx51ciq" style="font-size: 15px;">有一个算法叫 Aho-Corasick，也被称为 AC 自动机，该算法是一种高效的字符串匹配算法，它能一次性在一个文本中查找多个模式串（关键词）。该算法的巧妙之处在于，无论你有多少个关键词，都只需要对主文本进行一次遍历即可完成查找，因此效率极高。你可以把它想象成升级版的 KMP 算法，KMP 是为了解决一个模式串的匹配问题，而 Aho-Corasick 则是为了解决多个模式串的匹配问题。</span></p><p><span leaf="" mpa-font-style="mhsvanx5192t" style="font-size: 15px;">AC 自动机的具体原理这里就不赘述了，只需要知道它很高效，时间复杂度为 O(N+M+K)，其中 N 是主文本长度，M 是所有模式串的总长度（用于构建 Trie 树），K 是匹配到的模式串数量。因此可以看出，AC 自动机和模式串的数量无关，即不管 K 是多少，都只需要遍历一次主文本。</span></p><p><span leaf="" mpa-font-style="mhsvanx51is1" style="font-size: 15px;">Aho-Corasick（AC 自动机）因其高效的多模式匹配特性，广泛应用于以下场景。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mhsvb17e1b57" style="font-size: 15px;">敏感词过滤：在论坛、聊天室等场景中，需要从用户输入中快速过滤掉大量的敏感词、广告词，AC 自动机是实现这个功能的理想选择。</span></p></li><li><p><span leaf="" mpa-font-style="mhsvb17ep4t" style="font-size: 15px;">网络入侵检测系统（NIDS）：通过维护一个包含大量已知攻击模式（如病毒签名、恶意代码片段）的字典，AC 自动机可以实时地在网络流量中检测是否存在这些攻击模式。</span></p></li><li><p><span leaf="" mpa-font-style="mhsvb17e1tvu" style="font-size: 15px;">生物信息学：在 DNA 或蛋白质序列中快速查找多个特定的基因序列模式。</span></p></li><li><p><span leaf="" mpa-font-style="mhsvb17ehwh" style="font-size: 15px;">拼写检查和自动纠错：将正确的单词作为模式串，快速在文本中找到不存在于字典中的词。</span></p></li><li><p><span leaf="" mpa-font-style="mhsvb17ee8k" style="font-size: 15px;">多头借贷检测：在用户授权的短信中，将各个借贷平台的名称和对应关键词作为模式串集合，可以快速地一次性扫描所有短信，识别出多头借贷行为。</span></p></li></ul><p><span leaf="" mpa-font-style="mhsvb17eo0f" style="font-size: 15px;">而 ahocorasick 模块正是 Aho-Corasick 算法的具体实现，执行 pip install pyahocorasick 安装之后，来看一下它的使用方法。</span></p><pre style="box-sizing: border-box;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin: 10px 0px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;padding: 0px;color: rgb(0, 0, 0);font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"><code style="box-sizing: border-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;" data-mpa-action-id="mhsvcliye3c" data-pm-slice="0 0 []"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvclic17kc" style="font-size: 14px;">import</span></span><span leaf="" mpa-font-style="mhsvclic4i7" style="font-size: 14px;"> ahocorasick</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvclic1jyt" style="font-size: 14px;">A = ahocorasick.Automaton()</span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1g2t"><span leaf=""># 添加关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic28y" style="font-size: 14px;"><span leaf="">A.add_word(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;what&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;"><span leaf="">None</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicyr2" style="font-size: 14px;"><span leaf="">A.add_word(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;how&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;"><span leaf="">None</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic18ju" style="font-size: 14px;"><span leaf="">A.add_word(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;"><span leaf="">None</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1ru" style="font-size: 14px;"><span leaf="">A.add_word(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;then&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;"><span leaf="">None</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclicrht"><span leaf=""># 查找所有的关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvcliczij" style="font-size: 14px;"><span leaf="">print(list(A.keys()))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;how&#39;, &#39;when&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1gv6"><span leaf=""># 查找以 &#34;w&#34; 开头的关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1lma" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;w&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;when&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1s04"><span leaf=""># 查找长度为 4 的关键词，</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicjai" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;****&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;when&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1mhd"><span leaf=""># 第二个参数为通配符，一旦指定了，那么第一个参数就不再是前缀了</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic125v"><span leaf=""># 此时 &#34;*e&#34; 表示精确匹配长度为 2、以 &#34;e&#34; 结尾的关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic11pg" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*e&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># []</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1fm7" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;w&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># []</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclici61" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;**e&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># []</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicbt1" style="font-size: 14px;"><span leaf="">print(list(A.keys(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;**en&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;*&#34;</span></span><span leaf="">)))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;when&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic5n"><span leaf=""># 判断某个关键词是否存在（只能精确匹配）</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1wb8" style="font-size: 14px;"><span leaf="">print(A.exists(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;whe&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># False</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicswi" style="font-size: 14px;"><span leaf="">print(A.exists(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># True</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclicqp1"><span leaf=""># 判断是否存在具有指定前缀的关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic15bs" style="font-size: 14px;"><span leaf="">print(A.match(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;whe&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># True</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1xxg" style="font-size: 14px;"><span leaf="">print(A.match(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># True</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclict3x"><span leaf=""># 获取某个关键词的 value（只能精确匹配）</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1fcr"><span leaf=""># 关键词不存在会报 KeyError，但可以指定默认值</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicpv9" style="font-size: 14px;"><span leaf="">print(A.get(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># None</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1vlf"><span leaf=""># 这个不好解释，我们直接用代码说明</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1n1v"><span leaf=""># &#34;when&#34; 长度为 4，能命中已有关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1w8b" style="font-size: 14px;"><span leaf="">print(A.longest_prefix(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># 4</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclic1wrw"><span leaf=""># &#34;while&#34; 长度为 5，但只有前两个字符能和已有关键词具有相同的前缀</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclicupu" style="font-size: 14px;"><span leaf="">print(A.longest_prefix(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;while&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># 2</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1dno" style="font-size: 14px;"><span leaf="">print(A.longest_prefix(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;w&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># 1</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclickv8"><span leaf=""># 没有任何一个关键词的前缀是 &#34;ow&#34;</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclic1y9c" style="font-size: 14px;"><span leaf="">print(A.longest_prefix(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;ow&#34;</span></span><span leaf="">))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># 0</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclicn7q"><span leaf=""># 如果不想要某个关键词了，那么也可以移除</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvcliczhg" style="font-size: 14px;"><span leaf="">A.pop(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclid1nqt" style="font-size: 14px;"><span leaf="">print(list(A.keys()))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;how&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclid1ss5"><span leaf=""># pop 方法在 key 不存在时会跑抛出 KeyError</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclidj8o"><span leaf=""># 如果希望不报错，那么可以使用 remove_word 方法，成功移除返回 True，关键词不存在返回 False</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclid1da2" style="font-size: 14px;"><span leaf="">A.remove_word(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;how&#34;</span></span><span leaf="">)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclidt91" style="font-size: 14px;"><span leaf="">print(list(A.keys()))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvclid1hme" style="font-size: 14px;"><span leaf="">print(list(A.keys()))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># [&#39;then&#39;, &#39;what&#39;]</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvclid51o"><span leaf=""># 清空所有关键词</span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvclid177d" style="font-size: 14px;">A.clear()</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvclid3y1" style="font-size: 14px;">print(list(A.keys()))  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvclid1upm" style="font-size: 14px;"># []</span></span></code></pre><p><span leaf="" mpa-font-style="mhsvculls5q" style="font-size: 15px;" data-mpa-action-id="mhsvcum47ah" data-pm-slice="0 0 []">以上这些方法只是开胃菜，因为我们用字典也可以实现，下面来看它最重要的一个用法。</span></p><pre style="box-sizing: border-box;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin: 10px 0px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;padding: 0px;color: rgb(0, 0, 0);font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"><code style="box-sizing: border-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;" data-mpa-action-id="mhsvdbkzmr3" data-pm-slice="0 0 []"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvdbke162v" style="font-size: 14px;">import</span></span><span leaf="" mpa-font-style="mhsvdbkemgl" style="font-size: 14px;"> ahocorasick</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvdbke1qw1" style="font-size: 14px;">A = ahocorasick.Automaton()</span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbkeu5h"><span leaf=""># 添加关键词</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke24k5" style="font-size: 14px;"><span leaf="">key_words = [</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;what&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;how&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">]</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke1qxs" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> index, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> enumerate(key_words):</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbke1ltp"><span leaf=""># value 可以是任何对象，这里我们用 (index, key_word) 元组</span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvdbkecje" style="font-size: 14px;">    A.add_word(key_word, (index, key_word))</span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbke1dn4"><span leaf=""># 构建自动机，只有在构建完自动机之后，才可以搜索</span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvdbkekbe" style="font-size: 14px;">A.make_automaton()</span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbkelbj"><span leaf=""># 原始文本</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke1lba" style="font-size: 14px;"><span leaf="">original_text = </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;what are you doing? how do you do? when i was young, i&#39;d listen to the radio.&#34;</span></span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbkexfo"><span leaf=""># 调用 iter 方法进行搜索，该方法会返回一个迭代器，迭代出的每个元素是一个元组 (end_index, value)</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbke1oi"><span leaf=""># 其中 end_index 表示匹配到的关键词在文本中的结束位置（包含），value 表示添加关键词时指定的 value</span></span><span leaf=""><br/></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbkelpx"><span leaf=""># 我们看到没有返回关键词，所以在 add_word 时要将关键词体现在 value 中，当前的 value 是 (index, key_word)</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke1moi" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> end_index, (index, key_word) </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> A.iter(original_text):</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke23pj" style="font-size: 14px;"><span leaf="">    print(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">f&#34;original_text[</span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{end_index + </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span><span leaf=""> - len(key_word)}</span></span><span leaf="">: </span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{end_index + </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span><span leaf="">}</span></span><span leaf="">] 对应关键词 </span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{key_word}</span></span><span leaf="">，&#34;</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbke14a4" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">f&#34;即 key_words[</span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{index}</span></span><span leaf="">]&#34;</span></span><span leaf="">)</span></span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;font-size: 14px;" mpa-font-style="mhsvdbkef1n"><span leaf="">&#34;&#34;&#34;</span><span leaf=""><br/></span><span leaf="">original_text[0: 4] 对应关键词 what，即 key_words[0]</span><span leaf=""><br/></span><span leaf="">original_text[20: 23] 对应关键词 how，即 key_words[1]</span><span leaf=""><br/></span><span leaf="">original_text[35: 39] 对应关键词 when，即 key_words[2]</span><span leaf=""><br/></span><span leaf="">&#34;&#34;&#34;</span></span><span mpa-font-style="mhsvdbkeb4b" style="font-size: 14px;"><span leaf="">print(original_text[</span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">0</span></span><span leaf="">: </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">4</span></span><span leaf="">])  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># what</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbkedzj" style="font-size: 14px;"><span leaf="">print(original_text[</span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">20</span></span><span leaf="">: </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">23</span></span><span leaf="">])  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf=""># how</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvdbkei8f" style="font-size: 14px;"><span leaf="">print(original_text[</span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">35</span></span><span leaf="">: </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">39</span></span><span leaf="">])  </span></span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvdbkef3n" style="font-size: 14px;"># when</span></span></code></pre><p><span leaf="" mpa-font-style="mhsvdopdup3" style="font-size: 15px;" data-mpa-action-id="mhsvdopr1pe6" data-pm-slice="0 0 []">通过这种方式，只需一次遍历，就可以找到所有匹配的关键词。当然啦，大部分时候我们不关心匹配的单词在原文中的位置，只是想统计数量。</span></p><pre style="box-sizing: border-box;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin: 10px 0px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;padding: 0px;color: rgb(0, 0, 0);font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"><code style="box-sizing: border-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;" data-mpa-action-id="mhsve3w4k9n" data-pm-slice="0 0 []"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsve3vo89z" style="font-size: 14px;">import</span></span><span leaf="" mpa-font-style="mhsve3vokqz" style="font-size: 14px;"> ahocorasick</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsve3vo1vla" style="font-size: 14px;">A = ahocorasick.Automaton()</span><span leaf=""><br/></span><span mpa-font-style="mhsve3volz4" style="font-size: 14px;"><span leaf="">key_words = [</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;what&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;how&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;when&#34;</span></span><span leaf="">]</span></span><span leaf=""><br/></span><span mpa-font-style="mhsve3vo20b" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> index, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> enumerate(key_words):</span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsve3vo1qk0" style="font-size: 14px;">    A.add_word(key_word, key_word)</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsve3vorri" style="font-size: 14px;">A.make_automaton()</span><span leaf=""><br/></span><span mpa-font-style="mhsve3vo1jnk" style="font-size: 14px;"><span leaf="">original_text = </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;what how when when how how how what&#34;</span></span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsve3vobh2" style="font-size: 14px;">result = {}</span><span leaf=""><br/></span><span mpa-font-style="mhsve3vo3bb" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> _, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> A.iter(original_text):</span></span><span leaf=""><br/></span><span mpa-font-style="mhsve3vopnv" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">if</span></span><span leaf=""> key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">not</span></span></span><span mpa-font-style="mhsve3vo1qx5" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> result:</span></span><span leaf=""><br/></span><span mpa-font-style="mhsve3vo1c7d" style="font-size: 14px;"><span leaf="">        result[key_word] = </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">0</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsve3vo1quq" style="font-size: 14px;"><span leaf="">    result[key_word] += </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsve3voatf" style="font-size: 14px;">print(result)  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsve3vo11m3" style="font-size: 14px;"># {&#39;what&#39;: 2, &#39;how&#39;: 4, &#39;when&#39;: 2}</span></span></code></pre><p><span leaf="" mpa-font-style="mhsvecdo22b3" style="font-size: 15px;" data-mpa-action-id="mhsvece9x8y" data-pm-slice="0 0 []">需要注意的是，这些关键词在原文中都是分开的，即使它们连在一起也没问题。</span></p><pre style="box-sizing: border-box;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin: 10px 0px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;padding: 0px;color: rgb(0, 0, 0);font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"><code style="box-sizing: border-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;" data-mpa-action-id="mhsves8s1ikt" data-pm-slice="0 0 []"><span mpa-font-style="mhsves87sgm" style="font-size: 14px;"><span leaf="">original_text = </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;whathowwhenwhenhowhowhowwhat&#34;</span></span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsves871cbi" style="font-size: 14px;">result = {}</span><span leaf=""><br/></span><span mpa-font-style="mhsves879sr" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> _, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> A.iter(original_text):</span></span><span leaf=""><br/></span><span mpa-font-style="mhsves871cst" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">if</span></span><span leaf=""> key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">not</span></span></span><span mpa-font-style="mhsves87hm5" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> result:</span></span><span leaf=""><br/></span><span mpa-font-style="mhsves87ugp" style="font-size: 14px;"><span leaf="">        result[key_word] = </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">0</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsves871eb2" style="font-size: 14px;"><span leaf="">    result[key_word] += </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsves8715ra" style="font-size: 14px;">print(result)  </span><span style="box-sizing: border-box;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsves871dvl" style="font-size: 14px;"># {&#39;what&#39;: 2, &#39;how&#39;: 4, &#39;when&#39;: 2}</span></span></code></pre><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mhsvf0he22vl" style="font-size: 15px;">结果是一样的，因为 AC 自动机在遍历时不在乎关键词的右边是否是空格。</span></p><p><span leaf="" mpa-font-style="mhsvf0heh4y" style="font-size: 15px;">另外 AC 自动机在匹配时，如果原始字符串的一个范围能匹配多个单词，那么这些单词都会返回。</span></p><pre style="box-sizing: border-box;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin: 10px 0px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;padding: 0px;color: rgb(0, 0, 0);font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;background-color: rgb(255, 255, 255);text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;"><code style="box-sizing: border-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;" data-mpa-action-id="mhsvfdux1dix" data-pm-slice="0 0 []"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvfdudu0m" style="font-size: 14px;">import</span></span><span leaf="" mpa-font-style="mhsvfdud187m" style="font-size: 14px;"> ahocorasick</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdud1esx" style="font-size: 14px;">A = ahocorasick.Automaton()</span><span leaf=""><br/></span><span mpa-font-style="mhsvfdud142u" style="font-size: 14px;"><span leaf="">key_words = [</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;he&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;she&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;his&#34;</span></span><span leaf="">, </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;hers&#34;</span></span><span leaf="">]</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvfdudy9e" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> index, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> enumerate(key_words):</span></span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdud1ov7" style="font-size: 14px;">    A.add_word(key_word, key_word)</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdud237b" style="font-size: 14px;">A.make_automaton()</span><span leaf=""><br/></span><span mpa-font-style="mhsvfdud1kxu" style="font-size: 14px;"><span leaf="">original_text = </span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">&#34;ushers&#34;</span></span></span><span leaf=""><br/></span><span mpa-font-style="mhsvfdud19cr" style="font-size: 14px;"><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">for</span></span><span leaf=""> end_index, key_word </span><span style="box-sizing: border-box;color: rgb(198, 120, 221);font-weight: 700;cursor: pointer;line-height: 26px;"><span leaf="">in</span></span><span leaf=""> A.iter(original_text):</span></span><span leaf=""><br/></span><span mpa-font-style="mhsvfdudwtb" style="font-size: 14px;"><span leaf="">    print(</span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="">f&#34;original_text[</span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{end_index + </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span><span leaf=""> - len(key_word)}</span></span><span leaf="">: </span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{end_index + </span><span style="box-sizing: border-box;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;"><span leaf="">1</span></span><span leaf="">}</span></span><span leaf="">] 对应关键词 </span><span style="box-sizing: border-box;color: rgb(224, 108, 117);font-weight: 400;cursor: pointer;line-height: 26px;"><span leaf="">{key_word}</span></span><span leaf="">&#34;</span></span><span leaf="">)</span></span><span style="box-sizing: border-box;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;"><span leaf="" mpa-font-style="mhsvfdud250h" style="font-size: 14px;">&#34;&#34;&#34;</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdudh4k" style="font-size: 14px;">original_text[1: 4] 对应关键词 she</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdudwwd" style="font-size: 14px;">original_text[2: 4] 对应关键词 he</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdudpur" style="font-size: 14px;">original_text[2: 6] 对应关键词 hers</span><span leaf=""><br/></span><span leaf="" mpa-font-style="mhsvfdud14k" style="font-size: 14px;">&#34;&#34;&#34;</span></span></code></pre><p><span leaf="">以上就是 ahocorasick 的用法，当然它还有一些其它方法，不过不常用。我们使用这个模块，主要就是做关键词匹配。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>


<p><a href="%27%27">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=0b064c3b&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532290%26idx%3D1%26sn%3D888cf5abac10a63e34f666cc5641680f">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Thu, 01 Jan 2026 14:49:00 +0800</pubDate>
    </item>
    <item>
      <title>聊一聊信贷领域中的特征分箱</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532278&amp;idx=1&amp;sn=34ab11adf4fb8e2b9340b2c769c6e088</link>
      <description>楔子最近收到不少小伙伴的私信，在这里统一回复：谢谢大家关心，我还活着，只是不更新了而已。</description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-09-04 00:22</span> <span style="display: inline-block;">上海</span>
</p>




<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=f8c1a704&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeoMvqHCia10rMIQcXyzh0CqXZzbia0nUQbibNO5QotYFlUVuib8UBdqRyFYg%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="0 0 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">楔子</span></span></p></div></div></div></div></div><p><span leaf="" mpa-font-style="mf46018tuqw" style="font-size: 15px;" data-mpa-action-id="mf46019fcsd" data-pm-slice="0 0 []">最近收到不少小伙伴的私信，在这里统一回复：谢谢大家关心，我还活着，只是不更新了而已。然后也有小伙伴从几个月前就一直问我啥时候更新，我的回答是没啥好更新的了，建议大家一定要拥抱 AI，生产力绝对会成倍提升。</span></p><p><span leaf="" mpa-font-style="mf46018tuqw" style="font-size: 15px;" data-mpa-action-id="mf46019fcsd" data-pm-slice="0 0 []">但话都说到这儿了，不更新一篇也不合适。由于目前正在从事风控领域，就更新一篇风控相关的文章吧。</span></p><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="0 0 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">什么是特征分箱？</span></span></p></div></div></div></div></div><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf44izu01vde" style="font-size: 15px;">在信贷风控领域，特征分箱（Feature Binning）是一项至关重要且应用广泛的特征工程技术，它的作用是将连续型特征（如年龄、收入）或多取值的离散型特征（如职业）进行分组、装箱，将其转化为一系列离散的区间或类别。这一看似简单的操作，在构建稳定、可解释且高效的信用评分卡等风控模型中，扮演着定海神针般的关键角色。</span></p><p><span leaf="" mpa-font-style="mf44izu0hi6" style="font-size: 15px;">而之所以要特征分箱，是因为在原始的连续型特征上直接建模，往往会遇到诸多挑战。而特征分箱通过将数据离散化，带来了多方面的显著优势。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44jguh24pj" style="font-size: 15px;">提升模型的稳定性和鲁棒性：分箱操作能够有效降低特征中 &#34;噪声&#34; 的干扰。例如借款人年龄的微小变动（从 30 岁变为 31 岁）可能并不会对其信用风险产生实质性影响，分箱后，这些细微的波动被平滑处理，能有效防止模型因过度拟合个别数据点而变得不稳定，从而增强模型的泛化能力，使其在面对新数据时表现更稳健。</span></p></li><li><p><span leaf="" mpa-font-style="mf44jguhndc" style="font-size: 15px;">增强模型的可解释性：将连续特征转化为有限的几个箱，极大地增强了业务人员对模型决策逻辑的理解。例如，相比于 &#34;年龄每增加一岁，违约概率下降 0.5%&#34; 这种复杂的线性关系，&#34;年龄在 30 ~ 40岁之间的客群，其信用风险较低&#34; 这样的结论显然更直观，更易于向决策层和业务部门解释和沟通。</span></p></li><li><p><span leaf="" mpa-font-style="mf44jguh6uo" style="font-size: 15px;">有效处理非线性关系：现实世界中，许多特征与信用风险之间并非是简单的线性关系。例如，年龄与违约率可能呈现 U 型关系，即年轻人和老年人的风险相对较高，中年人风险较低。分箱操作可以将这种非线性关系转化为分段的线性关系，使得逻辑回归等线性模型也能有效捕捉到这种复杂的模式。</span></p></li><li><p><span leaf="" mpa-font-style="mf44jguh7xf" style="font-size: 15px;">便捷地处理缺失值和异常值：在进行分箱时，可以将缺失值或异常值作为一个独立的 &#34;箱&#34; 来处理。这不仅避免了复杂的缺失值填充或异常值剔除操作，还能将 &#34;缺失&#34; 或 &#34;异常&#34; 本身作为一种信息纳入模型考量，有时这些特殊值本身就蕴含着特定的风险信息。</span></p></li><li><p><span leaf="" mpa-font-style="mf44jguh10km" style="font-size: 15px;">为后续的 WOE 转换和评分卡创建奠定基础：在构建信用评分卡时，特征分箱是计算证据权重（Weight of Evidence，WOE）和信息价值（Information Value，IV）的前提。WOE 可以衡量每个分箱内好坏客户的比例差异，从而揭示该分箱对风险的预测能力。</span></p></li></ul><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="0 0 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">常见的特征分箱方法</span></span></p></div></div></div></div></div><p data-pm-slice="0 0 []"><span style="font-size: 15px;" mpa-font-style="mf44lg097j"><span leaf="">特征分箱的方法主要有以下几种：</span></span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44lg091n7v" style="font-size: 15px;">等宽分箱（Equal Width Binning）</span></p></li><li><p><span leaf="" mpa-font-style="mf44lg09p4s" style="font-size: 15px;">等频分箱（Equal Frequency Binning）</span></p></li><li><p><span leaf="" mpa-font-style="mf44lg09108s" style="font-size: 15px;">卡方分箱（Chi-Merge Binning）</span></p></li><li><p><span style="font-size: 15px;" mpa-font-style="mf44lg098w0"><span leaf="">决策树分箱（Decision Tree Binning）</span></span></p></li></ul><p data-pm-slice="0 0 []"><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">等宽分箱</span></span></strong></p><p><span leaf="" mpa-font-style="mf44mf4c1bh9" style="font-size: 15px;">核心思想：将特征的取值范围（最大值 ~ 最小值）等分成 N 个区间。</span></p><p><span leaf="" mpa-font-style="mf44mf4cnv4" style="font-size: 15px;">这种做法实现简单、快速，但缺点也很明显。如果特征值分布不均，容易产生某些箱内样本量过多，而另一些箱内样本量过少的情况，导致分箱结果无意义。</span></p><p><span leaf="" mpa-font-style="mf44mf4c1dwr" style="font-size: 15px;">因此等宽分箱一般用于数据分布比较均匀的特征，或作为快速探索性分析的初步分箱方法。</span></p><p><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">等频分箱</span></span></strong></p><p><span leaf="" mpa-font-style="mf44mn951uqs" style="font-size: 15px;">核心思想：将特征按从小到大的顺序排列，然后切分成 N 个部分，使得每个部分（箱）内的样本数量大致相等。</span></p><p><span leaf="" mpa-font-style="mf44mn952s8" style="font-size: 15px;">这种做法保证了每个箱内都有足够的样本量，避免了等宽分箱的缺点。但它自身也有缺点，因为可能会将数值上非常接近的样本点强行切分到不同的箱中，对于某些业务场景，这种纯粹基于频率的切分可能破坏了原有的业务逻辑。</span></p><p><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">卡方分箱</span></span></strong></p><p><span leaf="" mpa-font-style="mf44mv9m171s" style="font-size: 15px;">等款分箱和等频分箱针对的是无监督样本，而卡方分箱针对的是有监督样本。其核心思想是利用卡方检验（Chi-Square Test）来衡量相邻两个区间的分布相似性，从最小的箱开始，不断合并卡方值最小的相邻箱，直到满足停止条件（如分箱数、卡方阈值）。</span></p><p><span leaf="" mpa-font-style="mf44mv9m2k7" style="font-size: 15px;">这种做法是一种基于目标变量（如是否违约）进行分箱的监督方法，能保证分箱后的每个箱都有显著不同的风险水平。至于缺点是计算相对复杂，对某些值域的样本量敏感。</span></p><p><span leaf="" mpa-font-style="mf44mv9mq0b" style="font-size: 15px;">卡方分箱在构建信用评分卡时非常常用，因为它能确保分箱结果与风险有很强的关联性。</span></p><p><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">决策树分箱</span></span></strong></p><p><span leaf="" mpa-font-style="mf44n7e211r2" style="font-size: 15px;">针对有监督样本，核心思想是利用单特征构建一个简单的决策树模型来拟合目标变量，树的每个叶子节点就代表一个分箱。</span></p><p><span leaf="" mpa-font-style="mf44n7e23w2" style="font-size: 15px;">这种做法能自动找到最优的分割点，并且可以很好地处理非线性关系，分箱结果与目标变量强相关。但缺点是容易过拟合，需要通过剪枝或限制树的深度来控制，因此对于强线性关系的特征可能不是最优选择。</span></p><p><span leaf="" mpa-font-style="mf44n7e2qai" style="font-size: 15px;">决策树分箱主要用于需要深度挖掘特征与风险之间非线性关系的场景。</span></p><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="0 0 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">卡方分箱</span></span></p></div></div></div></div></div><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf44q0ig21mv" style="font-size: 15px;">下面来单独说一下卡方分箱（Chi-Merge Binning）的原理，因为它不像等宽和等频那么简单。</span></p><p><span leaf="" mpa-font-style="mf44q0ig6hw" style="font-size: 15px;">卡方分箱的哲学可以用一句老话来概括：&#34;物以类聚，人以群分&#34;，它的核心目标是将特征值相近且目标值（如好坏客户）分布也相似的区间进行合并。最终保留下来的每个箱子，其内部的客户风险水平应该尽可能一致，而不同箱子之间的风险水平则要有显著的差异。</span></p><p><span leaf="" mpa-font-style="mf44q0igmkx" style="font-size: 15px;">它是个自底向上的过程，和之后要介绍的决策树正好相反，先让每个特征值都自成一派（一个箱），然后通过一个标准去衡量哪些&#34;派别&#34;最相似，并将它们合并，不断重复这个过程，直到&#34;派别&#34;数量减少到我们满意的程度。而衡量派别相似的标准，就是统计学中著名的卡方检验（Chi-Square Test）。</span></p><p><span leaf="" mpa-font-style="mf44q0igw0d" style="font-size: 15px;">所以要理解卡方分箱，就必须先理解它的数学引擎 - 卡方独立性检验。这个检验的目的是判断两个分类变量是否相互独立，在当前的场景中，这两个变量是：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44q0igiwl" style="font-size: 15px;">分箱变量：即样本属于哪个箱子，例如箱子 A 还是箱子 B。</span></p></li><li><p><span leaf="" mpa-font-style="mf44q0igc4o" style="font-size: 15px;">目标变量：即客户是好客户还是坏客户。</span></p></li></ul><p data-mpa-action-id="mf44rf5byxs" data-pm-slice="0 0 []"><span mpa-font-style="mf44rf4v14ci" style="font-size: 15px;"><span leaf="">卡方检验会提出一个零假设（H</span><sub leaf=""><span leaf="">0</span></sub><span leaf="">）：分箱变量与目标变量相互独立。换句话说，零假设认为 &#34;一个客户在箱子 A 还是箱子 B&#34; 与 &#34;他是一个好客户还是坏客户&#34; 这两件事没有关系。如果这个假设成立，那么箱子 A 和箱子 B 中好坏客户的比例应该是没有显著差异的。</span></span></p><p><span leaf="" mpa-font-style="mf44rqlg1son" style="font-size: 15px;" data-mpa-action-id="mf44rqlx8rx" data-pm-slice="0 0 []">卡方检验会计算出一个卡方统计量（χ2 值），这个值衡量了实际观测值与期望值之间的差距。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048623" class="rich_pages wxw-img" data-ratio="0.24897959183673468" data-s="300,640" data-type="png" data-w="490" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=dbec13cb&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeocB1OicVRico5nDUe4wgFIsVrw6iaj7Tt57Joic8sNRJWbzf1VdKrtfl73A%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44ti231ysw" style="font-size: 15px;">O（Observed）：观测频数，即我们实际统计到的、落在每个箱子里的样本数量。</span></p></li><li><p><span leaf="" mpa-font-style="mf44ti23soi" style="font-size: 15px;">E（Expected）：期望频数，即如果零假设成立，我们理论上期望的样本数量。</span></p></li></ul><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf44uanp1smb" style="font-size: 15px;">如果 χ2 值很小，说明观测值（O）与期望值（E）非常接近，这意味着数据非常符合 &#34;两个变量相互独立&#34; 的零假设，因此我们没有理由拒绝零假设。在分箱场景下，这代表着这两个相邻的箱子，其好坏客户的分布非常相似，它们是合并的首要候选者。</span></p><p><span leaf="" mpa-font-style="mf44uanpwki" style="font-size: 15px;">如果 χ2 值很大：说明观测值（O）与期望值（E）差距巨大，这强烈地表明 &#34;两个变量相互独立&#34; 的假设是错误的，因此我们可以拒绝零假设。在分箱场景下，这代表着这两个相邻的箱子，其好坏客户的分布有显著差异，它们应该被分开，不能合并。</span></p><hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"/><p><span leaf="" mpa-font-style="mf44n7e2qai" style="font-size: 15px;">下面通过一个例子，来实际感受一下运算过程。</span></p><p><span leaf="" mpa-font-style="mf44v6pz1ah5" style="font-size: 15px;">假设有以下数据，并且已经初始化，每个年龄段是一个独立的箱。我们想看看相邻的 20 ~ 30 岁和 31 ~ 40 岁这两个箱是否应该合并。</span></p><div><div><div><p><table style="min-width: 100px;"><tbody><tr><td><p><span style="font-size: 15px;"><span leaf="">年龄段</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">好客户（is_bad = 0）</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">坏客户（is_bad = 1）</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">总计</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">20 ~ 30</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">80</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">10</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">90</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">31 ~ 40</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">150</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">20</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">170</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">总计</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">230</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">30</span></span></p></td><td><p data-mpa-action-id="mf44vliexc5"><span style="font-size: 15px;"><span leaf="">260</span></span></p></td></tr></tbody></table></p></div></div></div><p><span leaf="" mpa-font-style="mf44w0ug2nh" style="font-size: 15px;" data-mpa-action-id="mf44w0uw13nc" data-pm-slice="0 0 []">以上是我们的观测值，这是通过实际的统计得到的，是真实数据。而接下来要计算期望值，这是通过数学理论得到的。</span></p><p data-pm-slice="0 0 []"><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">第一步：计算期望值（Expected）</span></span></strong></p><p><span leaf="" mpa-font-style="mf44wh5wq59" style="font-size: 15px;">如果年龄段和是否坏客户无关（即零假设成立），那么 $20 \sim 30$ 岁年龄段的坏客户比例应该和总体的坏客户比例一样。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44wh5w1t9g" style="font-size: 15px;">总坏客户比例 = 30 / 260 ≈ 11.54%</span></p></li><li><p><span leaf="" mpa-font-style="mf44wh5w1t9g" style="font-size: 15px;">总好客户比例 = 230 / 260 ≈ 88.46%</span></p></li></ul><p><span leaf="" mpa-font-style="mf44wh5wh8y" style="font-size: 15px;">现在我们可以计算每个格子的期望值 E = 行总计 * 列总计 / 总计。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf44wh5wnma" style="font-size: 15px;">E(20 ~ 30, 好客户) = 90 * 230 / 260 ≈ 79.6</span></p></li><li><p><span leaf="" mpa-font-style="mf44wh5w1jod" style="font-size: 15px;">E(20 ~ 30, 坏客户) = 90 * 30 / 260 ≈ 10.4</span></p></li><li><p><span leaf="" mpa-font-style="mf44wh5wij1" style="font-size: 15px;">E(31 ~ 40, 好客户) = 170 * 230 / 260 ≈ 150.4</span></p></li><li><p><span leaf="" mpa-font-style="mf44wh5w18e1" style="font-size: 15px;">E(31 ~ 40, 坏客户) = 170 * 30 / 260 ≈ 19.6</span></p></li></ul><p data-pm-slice="0 0 []"><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">第二步：计算卡方值</span></span></strong></p><p><span leaf="" mpa-font-style="mf44y2zj44" style="font-size: 15px;">现在，我们将观测值和期望值代入公式。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048624" class="rich_pages wxw-img" data-ratio="0.09711684370257967" data-s="300,640" data-type="png" data-w="1318" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=ecc828f1&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeoJ5jHCocLR21GNyW47v9ic9zmKqD3Rp3Ip3HM5wZCNn841TM2MUJRD4A%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="" mpa-font-style="mf44y2zj1h5x" style="font-size: 15px;">此时我们便算出了 20 ~ 30 和 30 ~ 40 的卡方值 χ2，这个值为 0.026。</span></p><p><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);font-weight: bold;">第三步：决策</span></span></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45121n751" style="font-size: 15px;">我们会对所有相邻的箱子重复上述计算，假设区间再多一些，我们可能还会计算 31 ~ 40 和 41 ~ 50 之间的 χ2 值，比如计算出的结果为 2.5。</span></p><p><span leaf="" mpa-font-style="mf45121n1585" style="font-size: 15px;">因为 0.026 &lt; 2.5，所以 20 ~ 30 和 31 ~ 40 这对箱子的相似度更高，在这一轮迭代中，算法会优先选择合并 20 ~ 30 和 31 ~ 40，形成一个新的箱 20 ~ 40。</span></p><p><span leaf="" mpa-font-style="mf45121n4sz" style="font-size: 15px;">然后算法进入下一轮迭代，新的箱 20 ~ 40 会和它的邻居（如 41 ~ 50）再次计算卡方值，并寻找全局最小的卡方值进行合并。这个过程会一直持续下去，直到满足以下停止条件。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45121n1fp" style="font-size: 15px;">达到目标箱数（Max Bins）：这是最常用的方法，预先设定一个最终的分箱数量（例如 10 箱），当合并到只剩下 10 个箱子时，算法停止。</span></p></li><li><p><span leaf="" mpa-font-style="mf45121n211i" style="font-size: 15px;">卡方阈值（Chi-Square Threshold）：设定一个卡方值的阈值，如果计算出的所有相邻箱的最小卡方值都大于这个阈值，说明即使是差异最小的两个箱，差异也已经很显著了，不应该再合并，算法停止。</span></p></li><li><p><span leaf="" mpa-font-style="mf45121n1xdp" style="font-size: 15px;">最小箱体样本量（Minimum Bin Size）：要求每个箱子必须包含最少 N 个样本。如果合并后会导致某个箱子过大，而另一个箱子样本过少，则可以调整策略。或者在初始化时就预先合并一些样本极少的箱。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45121n18do" style="font-size: 15px;">综上所述，卡方分箱的原理就是巧妙地利用卡方独立性检验，通过一个迭代合并的过程，不断地将目标变量分布最相似的相邻区间进行合并，从而找出最优的分箱切分点，确保最终的分箱结果在统计上具有区分度。</span></p><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="5 2 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">决策树分箱</span></span></p></div></div></div></div></div><p data-pm-slice="0 0 []"><span mpa-font-style="mf452ln012hs" style="font-size: 14px;"><span style="font-size: 15px;" mpa-font-style="mf452vm91fho"><span leaf="">决策树分箱的核心思想是：如果一个决策树模型能用某个特征很好地预测客户的好坏，那么这棵树为了做出判断而找到的分割点，不就是天然的、最佳的分箱边界吗？因此它把分箱问题转成了一个简单的模型训练问题。</span></span></span></p><p><span leaf="" mpa-font-style="mf452vm9cpb" style="font-size: 15px;">我们针对单个特征建立一棵决策树来预测目标，然后把这棵树的决策逻辑（即分割规则）直接翻译过来，作为分箱规则。因此决策树分箱的过程可以分解为以下几个步骤：</span></p><p><span leaf="" mpa-font-style="mf452vm9u5w" style="font-size: 15px;">1）选择一个你想要分箱的连续型特征（例如 age）作为自变量 X，选择你的目标变量（例如 is_bad）作为因变量 y。</span></p><p><span leaf="" mpa-font-style="mf452vm9cgj" style="font-size: 15px;">2）使用 X 和 y 来训练一个决策树分类器（DecisionTreeClassifier），但我们不能让这棵树无限生长，否则它会为了拟合每一个数据点而制造出无数个细碎的箱子，导致严重的过拟合。所以必须对它进行预剪枝，限制其复杂度，常用的剪枝方法如下。</span></p><ul class="list-paddingleft-1"><li><p><span mpa-font-style="mf452vm91xer" style="font-size: 15px;"><span leaf="">限制最大叶子节点数（</span><span mpa-font-style="mf453rk820fj" style="font-size: 15px;" data-pm-slice="2 2 [&#34;para&#34;,{&#34;tagName&#34;:&#34;p&#34;,&#34;attributes&#34;:{&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><span leaf=""><span textstyle="" style="font-style: normal;">max_</span></span><em><span leaf=""><span textstyle="" style="font-style: normal;">leaf_</span></span></em><span leaf=""><span textstyle="" style="font-style: normal;">nodes</span></span></span><span leaf="">），这是最直观的方法，如果想分成5个箱，就设定 </span><span mpa-font-style="mf453rk820fj" style="font-size: 15px;" data-pm-slice="2 2 [&#34;para&#34;,{&#34;tagName&#34;:&#34;p&#34;,&#34;attributes&#34;:{&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><span leaf=""><span textstyle="" style="font-style: normal;">max_</span></span><em><span leaf=""><span textstyle="" style="font-style: normal;">leaf_</span></span></em><span leaf=""><span textstyle="" style="font-style: normal;">nodes</span></span></span><span leaf="">=5。</span></span></p></li><li><p><span leaf="" mpa-font-style="mf452vm91p4n" style="font-size: 15px;">限制树的最大深度（max_depth），例如深度为 3 的树最多能产生 2^3=8 个箱。</span></p></li><li><p><span mpa-font-style="mf452vm9dlr" style="font-size: 15px;"><span leaf="">限制叶子节点的最小样本量（<span textstyle="" style="font-style: normal;">min_</span></span><em><span leaf=""><span textstyle="" style="font-style: normal;">samples_</span></span></em><span leaf=""><span textstyle="" style="font-style: normal;">leaf</span>），确保每个箱子，或者说每个叶子节点都包含足够多的样本，避免产生不稳定的、由少数样本构成的箱。</span></span></p></li></ul><p><span leaf="" mpa-font-style="mf452vm91wyh" style="font-size: 15px;">3）当决策树训练完成后，其内部已经包含了所有用于决策的分割点（也称为阈值）。我们可以从训练好的树模型中，把所有内部节点（非叶子节点）的分割阈值（threshold）提取出来。</span></p><p><span leaf="" mpa-font-style="mf452vm91bmf" style="font-size: 15px;">4）将提取出的所有分割点进行去重和排序，在排序后的分割点列表的开头加上负无穷（-inf），在结尾加上正无穷（+inf），这就构成了一套完整的分箱边界。</span></p><p><span mpa-font-style="mf452ln1135m" style="font-size: 14px;"><span style="font-size: 15px;" mpa-font-style="mf452vm9qgj"><span leaf="">5）使用这些边界，通过类似 pandas.cut 的功能，将原始的连续特征映射到其对应的分箱区间中。</span></span></span></p><hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"/><p data-pm-slice="0 0 []"><span mpa-font-style="mf453rk820fj" style="font-size: 15px;"><span leaf="">假设我们用&#34;年龄&#34;特征训练了一棵决策树（<span textstyle="" style="font-style: normal;">max_</span></span><em><span leaf=""><span textstyle="" style="font-style: normal;">leaf_</span></span></em><span leaf=""><span textstyle="" style="font-style: normal;">nodes</span> = 4），它最终的决策逻辑可能是这样的。</span></span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf453rk8t3q" style="font-size: 15px;">训练决策树：根节点以 age &lt;= 55.5 为条件分裂，左子树（age &lt;= 55.5）继续以 age &lt;= 28.5 分裂，右子树（age &gt; 55.5）不再分裂，成为一个叶子节点，age &lt;= 28.5 的分支也不再分裂，最终形成了 4 个叶子节点（即 4 个箱）。</span></p></li><li><p><span leaf="" mpa-font-style="mf453rk8owh" style="font-size: 15px;">提取分割点：树中用到的分割点是 55.5 和 28.5。</span></p></li><li><p><span leaf="" mpa-font-style="mf453rk83lz" style="font-size: 15px;">形成边界：排序后为 [28.5, 55.5]，加上无穷边界后，完整的边界列表是 [-inf, 28.5, 55.5, +inf]。</span></p></li><li><p><span mpa-font-style="mf453rk81r6i" style="font-size: 15px;"><span leaf="">最终分箱结果：箱 1 为 (-inf, 28.5]，箱 2 为 (28.5, 55.5]，箱 3 为 (55.5, +inf]。注意：这里因为树的结构，最终只产生了 3 个箱，即使我们设定了</span><span mpa-font-style="mf453rk820fj" style="font-size: 15px;" data-pm-slice="2 2 [&#34;para&#34;,{&#34;tagName&#34;:&#34;p&#34;,&#34;attributes&#34;:{&#34;data-pm-slice&#34;:&#34;0 0 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><span leaf=""><span textstyle="" style="font-style: normal;">max_</span></span><em><span leaf=""><span textstyle="" style="font-style: normal;">leaf_</span></span></em><span leaf=""><span textstyle="" style="font-style: normal;">nodes</span></span></span><span leaf="">=4。</span></span></p></li></ul><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf455q0vbp2" style="font-size: 15px;" data-mpa-action-id="mf455q1d1pyl" data-pm-slice="0 0 []">那么决策树分箱有哪些优缺点呢？</span></p><p><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">优点</span></span></strong></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf455yh42wx" style="font-size: 15px;">自动寻找最佳分割点：无需人工干预，算法能基于信息增益或基尼不纯度自动找到最优的分割边界，最大化箱内的纯度。</span></p></li><li><p><span leaf="" mpa-font-style="mf455yh4fm8" style="font-size: 15px;">能捕捉非线性关系：这是它相较于卡方分箱等方法的一个巨大优势，如果特征与目标之间存在 U 型、倒 U 型等复杂关系，决策树可以通过多次分割来有效捕捉这种模式。</span></p></li><li><p><span leaf="" mpa-font-style="mf455yh4161x" style="font-size: 15px;">处理速度快：决策树的训练速度非常快，尤其是在特征维度很低时（我们每次只用一个特征）。</span></p></li><li><p><span leaf="" mpa-font-style="mf455yh41til" style="font-size: 15px;">鲁棒性强：对于异常值不敏感，因为异常值只会被分到某个叶子节点，不会像等宽分箱那样严重影响整体的边界划分。</span></p></li></ul><p><strong><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);">缺点</span></span></strong></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf4567kce8w" style="font-size: 15px;">容易过拟合：如果不对树的复杂度进行严格限制（即剪枝），它会生成过多且不稳定的分箱，导致模型在测试集上表现很差。控制复杂度是使用此方法的关键。</span></p></li><li><p><span leaf="" mpa-font-style="mf4567kc1qpq" style="font-size: 15px;">结果可能不稳定：训练数据的微小变动可能会导致生成一棵完全不同的树，从而得到一套不同的分箱规则。</span></p></li><li><p><span leaf="" mpa-font-style="mf4567kc1a9x" style="font-size: 15px;">不保证单调性：决策树只关心如何最大化信息增益，它产出的分箱结果对应的风险率（或WOE值）不保证是单调的。这在需要强业务可解释性的评分卡场景中可能是一个问题，可能需要后续手动调整。</span></p></li></ul><hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"/><p data-mpa-action-id="mf4594dhp0m" data-pm-slice="0 0 []"><span mpa-font-style="mf4594cx1ofh" style="font-size: 15px;"><span leaf="">然后是这几种分箱的实现方式，我们可以借助 toad 库来实现，这</span><span leaf="">是一个广受好评的 Python 库，尤其在评分卡建模领域，它提供了包括卡方分箱在内的一整套解决方案。比如除了卡方分箱，还集成了计算 WOE、IV、特征筛选、评分卡转换等一系列风控建模常用功能。</span></span></p><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="5 2 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;5 2 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="" data-pm-slice="1 1 [&#34;para&#34;,{&#34;tagName&#34;:&#34;p&#34;,&#34;attributes&#34;:{},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]">特征分箱与 WOE、IV 的联动</span></span></p></div></div></div></div></div><p><span leaf="" mpa-font-style="mf45aq0d6d0" style="font-size: 15px;" data-mpa-action-id="mf45aq0uidr" data-pm-slice="0 0 []">在信贷风控中，特征分箱的最终目的往往是为了计算 WOE 和 IV，进而构建信用评分卡。那什么是 WOE 和 IV 呢？</span></p><p><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);font-weight: bold;">证据权重（WOE）</span></span></p><p><span leaf="" mpa-font-style="mf45b6laiui" style="font-size: 15px;" data-mpa-action-id="mf45b6lt10kk" data-pm-slice="0 0 []">WOE 是对一个分箱内好客户（非逾期）与坏客户（逾期）比例的衡量，其计算公式为如下。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048625" class="rich_pages wxw-img" data-ratio="0.11693548387096774" data-s="300,640" data-type="png" data-w="992" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=6455cba6&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeo7UoW5bibtltIVR4icXtSibyhzgf6yTEkib0Y7sVjO7oBBjMWoeL92yUO8g%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="" mpa-font-style="mf45bspxhrf" style="font-size: 15px;" data-mpa-action-id="mf45bsqe10bn" data-pm-slice="0 0 []">所以每个分箱的好客户占整体好客户的比例，除以坏客户占整体坏客户的比例，再取对数，便是对应分箱的 WOE。</span></p><p><span leaf="" mpa-font-style="mf45c3071qsy" style="font-size: 15px;" data-mpa-action-id="mf45c30pp5z" data-pm-slice="0 0 []">举个例子，我们有 10000 个客户，8000 个好客户，2000 个坏客户。某个分箱里面好客户是 800 人，坏客户是 400 人，那么：</span></p><ul class="list-paddingleft-1"><li><p data-mpa-action-id="mf45ctdj1ynr" data-pm-slice="0 0 []"><span mpa-font-style="mf45evgegr5" style="font-size: 15px;"><span leaf="">Good</span><sub leaf=""><span leaf="">i</span></sub><span leaf=""> / Good</span><sub leaf=""><span leaf="">total</span></sub><span leaf=""> = 800 / 8000 = 10%</span></span></p></li><li><p data-mpa-action-id="mf45ctdj1ynr" data-pm-slice="2 2 [&#34;list&#34;,{&#34;type&#34;:&#34;ul&#34;,&#34;style&#34;:&#34;&#34;,&#34;class&#34;:&#34;list-paddingleft-1&#34;,&#34;start&#34;:null},&#34;listitem&#34;,{&#34;style&#34;:&#34;&#34;}]"><span mpa-font-style="mf45evge13x3" style="font-size: 15px;"><span leaf="">Bad</span><sub leaf=""><span leaf="">i</span></sub><span leaf=""> / Bad</span><sub leaf=""><span leaf="">total</span></sub><span leaf=""> = 400 / 2000 = 20%</span></span></p></li></ul><p data-mpa-action-id="mf45dxtpuxh" data-pm-slice="0 0 []"><span mpa-font-style="mf45evgep9u" style="font-size: 15px;"><span leaf="">所以 WOE</span><sub leaf=""><span leaf="">i</span></sub><span leaf=""> = ln(10% / 20%) = ln(0.5) = -0.693。</span></span></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45evgelan" style="font-size: 15px;">而基于 WOE 值，我们可以做出如下结论。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45evgeulj" style="font-size: 15px;">如果 WOE &gt; 0：该分箱中好客户占比高于坏客户占比，属于低风险区间。</span></p></li><li><p><span leaf="" mpa-font-style="mf45evge1lf8" style="font-size: 15px;">如果 WOE &lt; 0：该分箱中坏客户占比高于好客户占比，属于高风险区间。</span></p></li><li><p><span leaf="" mpa-font-style="mf45evgewld" style="font-size: 15px;">如果 WOE = 0：该分箱中好坏客户占比相等，风险中性。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45evge8x7" style="font-size: 15px;">在上面的例子中，WOE = -0.693 &lt; 0，说明这个分箱是高风险的，因为坏客户的相对集中度（20%）高于好客户的相对集中度（10%）。</span></p><p><span leaf="" mpa-font-style="mf45evgejx7" style="font-size: 15px;">所以通过 WOE，可以标准化不同分箱的比较基准，消除样本不平衡的影响，以及提供直观的风险度量。</span></p><p><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);font-weight: bold;">信息价值（IV）</span></span></p><p><span leaf="" mpa-font-style="mf45fycxvkb" style="font-size: 15px;" data-mpa-action-id="mf45fydc18d0" data-pm-slice="0 0 []">IV 是用来衡量一个特征对目标变量的总体预测能力的指标，它是所有分箱的 WOE 值的加权和，其计算公式如下。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048626" class="rich_pages wxw-img" data-ratio="0.1752873563218391" data-s="300,640" data-type="png" data-w="696" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=5ba52c83&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeoDE1w4wv6Lx7lSTcfvt2UPmwtLfeMl0vuOdgbyCGzmmJJ3P9AickY2aA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="" mpa-font-style="mf45glza1s6" style="font-size: 15px;" data-mpa-action-id="mf45glzsw7t" data-pm-slice="0 0 []">所以 IV 值实际上衡量的是好客户和坏客户在各分箱中分布差异的加权总和，权重就是各分箱的 WOE 值，差异越大，权重越大，IV 值就越高。而 IV 值越高，说明该特征对区分好坏客户的能力越强，通常 IV 值的评判标准如下。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45gvxu9kj" style="font-size: 15px;">&lt; 0.02：无预测能力</span></p></li><li><p><span leaf="" mpa-font-style="mf45gvxulji" style="font-size: 15px;">0.02 ~ 0.15：预测能力正常。</span></p></li><li><p><span leaf="" mpa-font-style="mf45gvxu22qa" style="font-size: 15px;">0.15 ~ 0.3：预测能力强</span></p></li><li><p><span leaf="" mpa-font-style="mf45gvxu20yw" style="font-size: 15px;">0.3 ~ 0.4：预测能力极强（需要警惕过拟合）</span></p></li><li><p><span leaf="" mpa-font-style="mf45gvxu1ci0" style="font-size: 15px;">&gt; 0.4：直接看是不是过拟合了</span></p></li></ul><p><span leaf="" mpa-font-style="mf45jqytnvt" style="font-size: 15px;" data-mpa-action-id="mf45jqzb1sjj" data-pm-slice="0 0 []">举个例子，假设我们对特征 &#34;月收入&#34; 进行分箱，得到以下结果。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048627" class="rich_pages wxw-img" data-ratio="0.26173708920187794" data-s="300,640" data-type="png" data-w="1704" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=ce6ead5d&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEPCia6rejrMibR7foeaQRVeoibrCLANqZ5O1kLbUqhjMhIMAo6UzdwSOLF9e5lOMmh1tErK0ZSJqxZA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45kqfthn1" style="font-size: 15px;">显然 IV 值等于 (17.8% - 40%) * -0.81 + (50% - 50%) * 0 + (32.2% - 10%) * 1.17 = 0.44，而 0.44 表示月收入这个特征具有极强的预测能力（当然 0.44 过于高了，这里只是随便举例）。而通过观察 WOE 值，也可以证明这一点。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45kqft177z" style="font-size: 15px;">&lt; 5000元：WOE = -0.811 &lt; 0，高风险群体。</span></p></li><li><p><span leaf="" mpa-font-style="mf45kqft181f" style="font-size: 15px;">5000 ~ 10000元：WOE = 0，风险中性。</span></p></li><li><p><span leaf="" mpa-font-style="mf45kqftf75" style="font-size: 15px;">&gt; 10000元：WOE = 1.173 &gt; 0，低风险群体。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45kqft1pot" style="font-size: 15px;">这个结果符合业务直觉，收入越高，信用风险越低。</span></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45ll0cgjp" style="font-size: 15px;">然后是 IV 值的大小问题，我们说 IV 值如果大于 0.4 反而是个不好的现象，因为大于 0.4 表示特征和目标结果拟合的太完美了，这说明了以下几个问题。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45ll0c1nnk" style="font-size: 15px;">数据泄露：变量可能包含了未来信息或与目标变量直接相关的信息。</span></p></li><li><p><span leaf="" mpa-font-style="mf45ll0c1egs" style="font-size: 15px;">样本偏差：训练样本可能存在某种特殊的数据分布，不能代表真实总体。</span></p></li><li><p><span leaf="" mpa-font-style="mf45ll0cmfm" style="font-size: 15px;">数据质量问题：变量定义不当，可能直接或间接包含了标签信息。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45ll0c327" style="font-size: 15px;">0.1 ~ 0.4 通常是 IV 值最理想的范围，既有足够的预测能力，又保持了良好的稳定性和可解释性。&#34;适度&#34; 往往比 &#34;极端&#34; 更可靠，这在风控建模中尤其重要。</span></p><p><span leaf="" mpa-font-style="mf45ll0csum" style="font-size: 15px;">另外可能有人会产生误区，还是不理解为什么 IV 值不能太大，这是因为我们说的都是单个特征的 IV 值。而单个特征本身就只能解释数据的一部分信息，任何单一维度都无法完全刻画客户的信用风险。</span></p><p><span leaf="" mpa-font-style="mf45ll0c1u5q" style="font-size: 15px;">而实际的特征工程中，客户会有大量特征，每个特征都有对应的 IV 值。通过多个特征组合，不同特征捕获不同维度的信息，并且特征之间存在互补，从而实现预测能力远超单个特征。</span></p><ul style="list-style-type: disc;" class="list-paddingleft-1"><li><p><code><span leaf="" mpa-font-style="mf45no8n1mpz" style="font-size: 15px;">收入高 + 年龄大 + 工作稳定 → 低风险</span></code></p></li><li><p><code mpa-font-style="mf45no8n15dy" style="font-size: 15px;"><span leaf="">收入高 + 年龄小 + 工作不稳定 → 中等风险</span></code></p></li><li><p><code mpa-font-style="mf45no8o5s8" style="font-size: 15px;"><span leaf="">收入低 + 年龄大 + 工作稳定 → 中等风险</span></code></p></li><li><p><code><span leaf="" mpa-font-style="mf45no8o20mi" style="font-size: 15px;">收入低 + 年龄小 + 工作不稳定 → 高风险</span></code></p><p><code></code></p></li></ul><p><code><span leaf="" mpa-font-style="mf45nv431sc1" style="font-size: 15px;">如果一个特征就能实现精准预测，那还要其它特征做什么？如果出现这样的特征（IV 值超过了 0.4、甚至更大），那么就要好好检测一下是不是出现了过拟合现象。</span></code></p><p><span leaf="" mpa-font-style="mf45nv431zjb" style="font-size: 15px;">假设有以下几个特征。</span></p><div><div><p><table style="min-width: 75px;"><tbody><tr><td><p><span style="font-size: 15px;"><span leaf="">特征</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">IV值</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">单独使用时的 AUC</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">月收入</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.35</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.65</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">年龄</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.25</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.62</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">信用卡使用率</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.30</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.64</span></span></p></td></tr><tr><td><p><span style="font-size: 15px;"><span leaf="">工作年限</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">0.20</span></span></p></td><td><p data-mpa-action-id="mf45o6ptsup"><span style="font-size: 15px;"><span leaf="">0.61</span></span></p></td></tr></tbody></table></p></div></div><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45oif71pkx" style="font-size: 15px;">组合后的效果：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45oif71tnz" style="font-size: 15px;">4  个特征组合：AUC 可能达到 0.75 ~ 0.80。</span></p></li><li><p><span leaf="" mpa-font-style="mf45oif717nj" style="font-size: 15px;">10 个特征组合：AUC 可能达到 0.80 ~ 0.85。</span></p></li><li><p><span leaf="" mpa-font-style="mf45oif7ucz" style="font-size: 15px;">50 个特征组合：AUC 可能达到 0.85 ~ 0.90。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45oif719ed" style="font-size: 15px;">所以在实际建模时，我们更关注特征组合后的整体效果，不会因为某个特征 IV 只有 0.15 就丢弃。宁可选择 10 个 IV=0.2 的不同维度特征，也不选择 3 个 IV=0.4 但高度相关的特征。</span></p><p><span leaf="" mpa-font-style="mf45oif78zw" style="font-size: 15px;">关于 IV 值，我们要记住以下几个结论。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45oif79v4" style="font-size: 15px;">IV 是筛选工具，不是绝对标准。</span></p></li><li><p><span leaf="" mpa-font-style="mf45oif720zs" style="font-size: 15px;">特征组合的整体效果比单个特征更重要。</span></p></li><li><p><span leaf="" mpa-font-style="mf45oif72pe" style="font-size: 15px;">多维度的中等强度特征组合往往比少数高 IV 特征的效果更好。</span></p></li><li><p><span leaf="" mpa-font-style="mf45oif715s5" style="font-size: 15px;">最终评判标准是模型在验证集上的综合表现。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45oif71m7" style="font-size: 15px;">这就是为什么经验丰富的风控建模师会说：&#34;特征工程是艺术，不只是科学&#34;。</span></p><div data-mpa-template="t" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(34, 34, 34);font-family: &#34;PingFang SC&#34;, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;" data-pm-slice="5 2 []"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;display: flex;justify-content: center;align-items: center;visibility: visible;" data-pm-slice="2 3 [&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mpa-template&#34;:&#34;t&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(34, 34, 34); font-family: \&#34;PingFang SC\&#34;, -apple-system, BlinkMacSystemFont, \&#34;Helvetica Neue\&#34;, \&#34;Hiragino Sans GB\&#34;, \&#34;Microsoft YaHei UI\&#34;, \&#34;Microsoft YaHei\&#34;, Arial, sans-serif; letter-spacing: 0.544px; caret-color: rgba(0, 0, 0, 0); background-color: rgb(255, 255, 255); visibility: visible;&#34;,&#34;data-pm-slice&#34;:&#34;5 2 []&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: center; align-items: center; width: 578px; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;},&#34;para&#34;,{&#34;tagName&#34;:&#34;section&#34;,&#34;attributes&#34;:{&#34;data-mid&#34;:&#34;&#34;,&#34;mpa-from-tpl&#34;:&#34;t&#34;,&#34;style&#34;:&#34;-webkit-tap-highlight-color: transparent; margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; display: flex; justify-content: flex-start; align-items: center; flex-direction: column; visibility: visible;&#34;},&#34;namespaceURI&#34;:&#34;http://www.w3.org/1999/xhtml&#34;}]"><p data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px -36px 0px 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 26px;height: 55px;z-index: 1;visibility: visible;" nodeleaf=""><img data-imgfileid="100048324" alt="图片" class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-type="png" data-w="52" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;vertical-align: bottom;height: auto !important;display: block;visibility: visible !important;width: 26px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=2b8daa32&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%23imgIndex%3D9"/></p><div data-mid="" mpa-from-tpl="t" style="-webkit-tap-highlight-color: transparent;margin: 0px 0px 0px -12px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;text-align: center;visibility: visible;"><p data-mid="" style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px 12px 0px 18px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;clear: both;min-height: 1em;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="-webkit-tap-highlight-color: transparent;margin: 0px;padding: 0px;outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;"><span leaf="">IV 值在建模时应用</span></span></p></div></div></div></div></div><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45psxaaxt" style="font-size: 15px;">既然提到了 IV 值，那么继续补充一下它在建模时的应用。</span></p><p><span leaf="" mpa-font-style="mf45psxa1xtz" style="font-size: 15px;">在实际的特征工程中，会为每个客户刻画数万（甚至数十万、数百万）个特征，然后用这些特征来建模。但如果特征过多，不仅耗费资源，甚至一些无用特征反而会降低准确率，那么这时候就要进行特征筛选，从数万个特征中筛选出最有用的部分特征。而 &#34;最有用&#34; 三个字的量化方式就是通过 IV 值，IV 值越大，说明它对样本预测的重要性越大。</span></p><p><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);font-weight: bold;">和 PCA 降维的关系</span></span></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45qnuntjm" style="font-size: 15px;">有过机器学习经验的朋友会发现，整个过程就类似于 PCA 降维，因为不是所有特征都是有用的。而 PCA 会将原始数据映射到新的空间，每个轴都是原始特征的加权组合，如果该轴的可解释方差比例越大，证明这个轴就越有用。当然一个轴是不够的，我们需要可解释方差比例最大的 k 个轴，如果它们的可解释方差比例加起来接近 1，那么只需要这 k 个轴即可。</span></p><p><span leaf="" mpa-font-style="mf45qnun1g84" style="font-size: 15px;">因此通过 PCA 降维，将具有 n 个特征的原始数据映射到新的空间之后，只需 k 个特征即可进行预测，并且 k 是远小于 n 的。而建模这个过程与之类似，虽然原理不一样，但做的事情是一样的，也是从全量特征中筛选出部分最有用的特征，只是量化标准是 IV 值。</span></p><div><div><p><table style="min-width:177px;"><tbody><tr><td data-colwidth="127"><p><span style="font-size: 15px;"><span leaf="">维度</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">特征筛选（基于 IV）</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">PCA 降维</span></span></p></td></tr><tr><td data-colwidth="127"><p><span style="font-size: 15px;"><span leaf="">选择方式</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">从原特征中选择子集</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">创造新的组合特征</span></span></p></td></tr><tr><td data-colwidth="127"><p><span style="font-size: 15px;"><span leaf="">可解释性</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">保持业务含义</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">失去原始特征含义</span></span></p></td></tr><tr><td data-colwidth="127"><p><span style="font-size: 15px;"><span leaf="">评价标准</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">与目标变量的关联性（IV 值）</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">数据本身的方差贡献</span></span></p></td></tr><tr><td data-colwidth="127"><p><span style="font-size: 15px;"><span leaf="">特征关系</span></span></p></td><td><p><span style="font-size: 15px;"><span leaf="">考虑特征与 y 的关系</span></span></p></td><td><p data-mpa-action-id="mf45r0lvks1" data-pm-slice="0 0 []"><span style="font-size: 15px;"><span leaf="" style="">只考虑特征间的关</span><span leaf="">系</span></span></p></td></tr></tbody></table></p></div></div><p><span leaf="" mpa-font-style="mf45rcom1x7t" style="font-size: 15px;" data-mpa-action-id="mf45rcp61ou4" data-pm-slice="0 0 []">那么问题来了，为啥不用 PCA 降维呢？原因有以下几个。</span></p><p data-pm-slice="0 0 []"><strong><span leaf="" mpa-font-style="mf45ropyw2y" style="font-size: 15px;"><span textstyle="" style="font-weight: normal;">1）监管合规要求</span></span></strong></p><p><span leaf="" mpa-font-style="mf45ropy8g9" style="font-size: 15px;">监管机构如果问，为什么拒绝这个客户的贷款申请？</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45ropy1hng" style="font-size: 15px;">PCA 降维：因为他的 xx 值较低 …（无法解释）</span></p></li><li><p><span leaf="" mpa-font-style="mf45ropyr00" style="font-size: 15px;">特征筛选：因为他的月收入过低且信用卡使用率过高（可解释）</span></p></li></ul><p><strong mpa-font-style="mf45ropy12ob" style="font-size: 15px;"><span leaf=""><span textstyle="" style="font-weight: normal;">2）业务策略制定</span></span></strong></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45ropy23fh" style="font-size: 15px;">PCA 方法：xx &lt; -0.5 且 yy &gt; 1.2 的客户需要额外审核（难以操作）</span></p></li><li><p><span leaf="" mpa-font-style="mf45ropy1eq0" style="font-size: 15px;">传统方法：月收入 &lt; 5000 且年龄 &lt; 25 岁的客户需要额外审核（易操作）</span></p></li></ul><p><strong mpa-font-style="mf45ropyzz5" style="font-size: 15px;"><span leaf=""><span textstyle="" style="font-weight: normal;">3）模型监控和调试</span></span></strong></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45ropz1gnq" style="font-size: 15px;">当模型表现下降时，可以直接分析具体特征的变化</span></p></li><li><p><span leaf="" mpa-font-style="mf45ropzzm8" style="font-size: 15px;">容易发现数据漂移和特征失效</span></p></li></ul><p><span leaf="" mpa-font-style="mf45ropz374" style="font-size: 15px;">所以无论是 PCA 降维还是 IV 值筛选，本质都是在高维空间中寻找最有价值的信息维度。PCA 是通过寻找方差最大的方向，而 IV 值筛选是寻找与目标最相关的特征，两者做的事情都是在有限的维度预算下，最大化保留信息。</span></p><p><span leaf="" mpa-font-style="mf45ropz23zy" style="font-size: 15px;">但在金融风控场景下，可解释性往往比信息压缩更重要，所以过滤特征时会使用 IV 值筛选，而不是 PCA 降维。</span></p><p><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);font-weight: bold;">如何通过 IV 值实现特征筛选</span></span></p><p data-pm-slice="0 0 []"><span leaf="" mpa-font-style="mf45tfg224r5" style="font-size: 15px;" data-mpa-action-id="mf45tfgn28p" data-pm-slice="0 0 []">好，再来看看通过 IV 值实现特征筛选的具体流程。</span></p><p><strong><span style="font-weight: normal;font-size: 15px;" mpa-font-style="mf45tr7927n"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);">第一阶段：数据准备与预处理</span></span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr796w5" style="font-size: 15px;">1）原始数据质量检查</span></p><p><span leaf="" mpa-font-style="mf45tr7921gt" style="font-size: 15px;">统计每个特征的缺失率、唯一值数量、数据类型，识别常数列（所有值都相同的特征），检查数据分布的基本情况。</span></p><p><span leaf="" mpa-font-style="mf45tr79zzw" style="font-size: 15px;">2）基础数据清晰</span></p><p><span leaf="" mpa-font-style="mf45tr79o5k" style="font-size: 15px;">剔除缺失率过高的特征（通常 &gt; 80%），删除方差过小的数值型特征（几乎没有变化），移除唯一值过少的特征（如只有 1 ~ 2 个取值）。</span></p><p><strong mpa-font-style="mf45tr7918c2" style="font-size: 15px;"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);font-weight: normal;">第二阶段：特征分箱处理</span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr791rqc" style="font-size: 15px;">如果是数值型特征：对于每个特征进行分箱，一般选择卡方分箱，并且确保每个分箱有足够的样本量（通常要求每箱 &gt; 5%总样本）。检查分箱后的单调性，每个箱的 WOE 值应该单调递增或递减。</span></p><p><span leaf="" mpa-font-style="mf45tr79125v" style="font-size: 15px;">如果是非数值型（字符串）特征：将该特征可能出现的值进行分类，每个类别也要有足够的样本量。如果是非常少见的值，那么整体合并为 &#34;其它&#34;。</span></p><p><strong mpa-font-style="mf45tr79v0l" style="font-size: 15px;"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);font-weight: normal;">第三阶段：IV 值批量计算</span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr79jfj" style="font-size: 15px;">按照 WOE 和 IV 的公式，为每个特征计算 IV 值。并且在此过程中，要记录出现的异常情况（如某个分箱没有正样本或负样本），并且建立详细的计算日志，便于后续排查问题。</span></p><p><span leaf="" mpa-font-style="mf45tr791gp9" style="font-size: 15px;">然后将所有特征的 IV 值汇总到一张表中，按 IV 值从高到低排序，统计不同 IV 值区间的特征数量分布。</span></p><p><strong mpa-font-style="mf45tr791zto" style="font-size: 15px;"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);font-weight: normal;">第四阶段：多层次特征筛选</span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr79210h" style="font-size: 15px;">1）IV 阈值筛选</span></p><p><span leaf="" mpa-font-style="mf45tr7921d4" style="font-size: 15px;">设置 IV 值的上下限，剔除 IV 值过低的特征（预测能力不足），标记 IV 值过高的特征（可能存在数据问题）。</span></p><p><span leaf="" mpa-font-style="mf45tr791r1" style="font-size: 15px;">2）可疑特征检查</span></p><p><span leaf="" mpa-font-style="mf45tr799ex" style="font-size: 15px;">对 IV 值大于 0.3 的特征进行深度检查，主要包含以下方面。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45tr791n1i" style="font-size: 15px;">数据泄露检查：是否包含未来信息或目标变量的直接信息。</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr791et6" style="font-size: 15px;">业务合理性验证：特征与目标变量的关系是否符合业务逻辑。</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr791ls3" style="font-size: 15px;">时间稳定性验证：在不同时间段上 IV 值是否稳定。</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr79jsq" style="font-size: 15px;">样本分布检查：是否存在极端的样本分布导致 IV 值虚高。</span></p></li></ul><p><span leaf="" mpa-font-style="mf45tr791i42" style="font-size: 15px;">3）特征多样性保证</span></p><p><span leaf="" mpa-font-style="mf45tr79eyu" style="font-size: 15px;">确保不同业务维度的特征都有覆盖（如收入、年龄、信用历史等），避免选择的特征过于集中在某个业务领域，平衡高 IV 特征和中等 IV 特征的数量。</span></p><p><strong mpa-font-style="mf45tr79hc8" style="font-size: 15px;"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);font-weight: normal;">第五阶段：特征稳定性验证</span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr793t" style="font-size: 15px;">1）时间稳定性测试</span></p><p><span leaf="" mpa-font-style="mf45tr791qm4" style="font-size: 15px;">在不同的时间窗口上重新计算 IV 值，检查 IV 值的波动幅度（波动 &lt; 20% 认为稳定），剔除在不同时期表现差异过大的特征。</span></p><p><span leaf="" mpa-font-style="mf45tr79z7d" style="font-size: 15px;">2）跨样本验证</span></p><p><span leaf="" mpa-font-style="mf45tr791hgh" style="font-size: 15px;">在训练集、验证集、测试集上分别计算 IV 值，确保特征的预测能力不是因为某个特定样本导致的。</span></p><p><strong mpa-font-style="mf45tr79r7" style="font-size: 15px;"><span leaf=""><span textstyle="" style="color: rgb(217, 33, 66);font-weight: normal;">第六阶段：最终特征集确定</span></span></strong></p><p><span leaf="" mpa-font-style="mf45tr7910e7" style="font-size: 15px;">1）将多个维度的评估结果综合</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45tr791j6u" style="font-size: 15px;">IV 值大小（权重 40%）</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr794wg" style="font-size: 15px;">稳定性评分（权重 30%）</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr791cbz" style="font-size: 15px;">业务重要性评分（权重 20%）</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr7913b1" style="font-size: 15px;">数据可得性评分（权重 10%）</span></p></li></ul><p><span leaf="" mpa-font-style="mf45tr79ajv" style="font-size: 15px;">2）根据模型类型和业务需求确定最终特征数量</span></p><hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);"/><p><span leaf="" mpa-font-style="mf45tr79i1b" style="font-size: 15px;">所以整个流程非常清晰：</span></p><p><code mpa-font-style="mf45tr79ph8" style="font-size: 15px;"><span leaf="">原始特征 → 基础清洗 → IV 计算 → 初步筛选 → 深度验证 → 稳定性测试 → 最终选择</span></code></p><p><span leaf="" mpa-font-style="mf45tr79gja" style="font-size: 15px;">而筛选标准也很简单：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" mpa-font-style="mf45tr79181o" style="font-size: 15px;">数据质量：缺失率 &lt; 20%，异常值 &lt; 5%</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr79y08" style="font-size: 15px;">IV 值合理性：0.02 ≤ IV ≤ 0.4</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr791oxm" style="font-size: 15px;">稳定性要求：不同时期 IV 值波动 &lt; 30%</span></p></li><li><p><span leaf="" mpa-font-style="mf45tr791nb5" style="font-size: 15px;">业务合理性：特征逻辑符合业务常识</span></p></li></ul><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532278">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=973024d1&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532278%26idx%3D1%26sn%3D34ab11adf4fb8e2b9340b2c769c6e088">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Thu, 04 Sep 2025 00:22:00 +0800</pubDate>
    </item>
    <item>
      <title>请以下同学私信我地址，准备赠书啦。。。</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532269&amp;idx=1&amp;sn=79ecb8fd79171e33853ced05bc639ada</link>
      <description>我（用 Claude）写了一个抽奖模拟器，随机挑选了 10 位同学。请这些同学私信我地址+姓名+手机号，我联系极客时间给你们赠书。</description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-07-19 19:16</span> <span style="display: inline-block;">北京</span>
</p>

<p>我（用 Claude）写了一个抽奖模拟器，随机挑选了 10 位同学。请这些同学私信我地址+姓名+手机号，我联系极客时间给你们赠书。</p>
<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=7906765f&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsGCg72FcNzTb7T6FqZViaI9Ma0Gwc53PXvF4D9MFvEFGpjQNBiahH9rZMPrCvZ9rYfzyQzDPRlJnkuA%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p><span leaf="">我（用 Claude）写了一个抽奖模拟器，随机挑选了 10 位同学。</span></p><p style="text-align: left;" nodeleaf=""><img data-imgfileid="100048616" class="rich_pages wxw-img" data-ratio="0.812037037037037" data-s="300,640" data-type="png" data-w="1080" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=6b0ca3f9&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGCg72FcNzTb7T6FqZViaI9MibFBwtEl7qdianBupJg0rIeJPuwF7COTr7z19WD6B4Ezaw0trFicGpBrw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span leaf="">请这些同学私信我<span textstyle="" style="font-weight: bold;">地址+姓名+手机号</span>，我联系极客时间给你们赠书。</span></p><p><span leaf="">本人在此声明：你们的信息绝不会泄露，只会拷贝发给极客时间的工作人员，由他们负责赠书。</span></p><p><span leaf="">至于具体的抽奖过程，我录制了一个视频，本人承诺没有任何的暗箱操作。</span></p><p nodeleaf=""></p><p><span leaf="">最后在这里感谢极客时间给大家赠与的福利，如果大家想学习技术，也请多多支持极客时间，里面不仅有海量的技术专栏，并且价格实惠，福利多多哦。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532269">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=4bf50398&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532269%26idx%3D1%26sn%3D79ecb8fd79171e33853ced05bc639ada">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Sat, 19 Jul 2025 19:16:00 +0800</pubDate>
    </item>
    <item>
      <title>极客时间发福利啦，留言赠书！！！</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532255&amp;idx=1&amp;sn=43e5ec1371bbd7ab850d9df92668bcc4</link>
      <description>不得不说，极客时间真是最懂程序员的！最近新项目里碰到一堆AI难题，正发愁呢，结果发现他们出了《AI 百问百答》📚100+ AI 热点问题，50+专家解答。</description>
      <content:encoded><![CDATA[<p>
<span>古明地觉</span> <span>2025-07-18 09:02</span> <span style="display: inline-block;">北京</span>
</p>

<p>不得不说，极客时间真是最懂程序员的！最近新项目里碰到一堆AI难题，正发愁呢，结果发现他们出了《AI 百问百答》📚100+ AI 热点问题，50+专家解答。</p>
<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=a53a6f4d&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIEsO5X3HqjpibDGBf8Yziax7kfbwBMeoogA1RVyAod7pmW5JvA14VkKAsg%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p><span leaf="">不得不说，极客时间真是最懂程序员的！最近新项目里碰到一堆AI难题，正发愁呢，结果发现他们出了《AI 百问百答》</span></p><p><span leaf="">📚100+ AI 热点问题，50+专家解答。</span></p><p><span leaf="">🎓线上课程+配套实体书，两种方式任选领取：</span></p><ul style="list-style-type: disc;" class="list-paddingleft-1"><li><p><span leaf="">19.9元直接购买，省时省力，戳 <a href="http://gk.link/a/12Ao8 " target="_blank">http://gk.link/a/12Ao8 </a></span></p></li><li><p><span leaf="">邀请5位好友助力，0元到手，戳 </span><span leaf=""><a href="http://gk.link/a/12As9" target="_blank">http://gk.link/a/12As9</a></span></p><p><span leaf=""><br/></span></p></li></ul><p style="text-align: center;" nodeleaf=""><img class="rich_pages wxw-img" data-imgfileid="100048599" data-ratio="1.9657407407407408" data-s="300,640" type="block" data-type="jpeg" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=dd2ba832&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIEje29dR20r92PfdAAGXoUE2VzD4KNcgxpQaldpILY5xTIG2TsDwZc1w%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p><span leaf="">或者还有第三种方式，</span><span leaf="">评论区留言点赞抽 10 位朋友免费送，1 人 1 本。</span></p><p style="text-align: center;" nodeleaf=""><img class="rich_pages wxw-img" data-imgfileid="100048601" data-ratio="0.7496991576413959" data-s="300,640" type="block" data-type="png" data-w="831" src="https://wechat2rss.xlab.app/img-proxy/?k=1d011bc9&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIExNFgLmDRXZBZXh9M6pPSoeGhkmNG0VX0j0EkuOKphUDgqwL3xDnyoA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p style="text-align: center;" nodeleaf=""><img class="rich_pages wxw-img" data-imgfileid="100048602" data-ratio="2.7947686116700203" data-s="300,640" type="block" data-type="png" data-w="497" src="https://wechat2rss.xlab.app/img-proxy/?k=71e5cb63&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIEOl6khoVHawHpPL2wS4JPFl2195uG1HPaqqph82x6hKkgmQkVAVP7EA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p style="text-align: center;" nodeleaf=""><img data-imgfileid="100048603" class="rich_pages wxw-img" data-ratio="0.3783132530120482" data-s="300,640" data-type="png" data-w="830" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=cdd13781&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIEh0czaAI2EZz0ZC1MuzCLX0y5jy1mhy3siaLPsHaYMIZKmpnjhibicwEUA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p style="text-align: center;" nodeleaf=""><img data-imgfileid="100048604" class="rich_pages wxw-img" data-ratio="0.39397590361445783" data-s="300,640" data-type="png" data-w="830" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=35699b9d&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG63GjgT2icJuTibEgMX4iaeIEsKNW7rcDdx37Eibbggffd2sib43qXbq99GQe47F9oRWT38hL2Bz4S0wg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532255">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=568dd702&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532255%26idx%3D1%26sn%3D43e5ec1371bbd7ab850d9df92668bcc4">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Fri, 18 Jul 2025 09:02:00 +0800</pubDate>
    </item>
    <item>
      <title>关于最近网络上某位白女士的遭遇</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532245&amp;idx=1&amp;sn=335ce7264464727c4e14a0ab1fc87879</link>
      <description>车祸是不幸的，愿白女士能早日康复。&#xA;&#xA;当然我发这篇文章主要是为了回答后台的一些私信，兄弟们不要再问我啥时候更新技术文章了，更不下去了。你能想象吗，一个不懂 Vue 的人，一行 Vue 代码没写，靠着和 Claude Code 聊聊天，就能用 Vue 搭建一个具有一定规模的前端项目。&#xA;&#xA;这太可怕了，前端如此，后端也好不到哪里去。AI 大模型的出现，让技术这条护城河轰然决堤。虽说近几年大环境差，但前两年我是不担心的，不过现在也有点蕉绿了。&#xA;&#xA;最后，祝大家都能把 AI 用起来，抓住风口（话说目前还有风口吗）。</description>
      <content:encoded><![CDATA[<p>
<span>古明地觉</span> <span>2025-07-14 21:36</span> <span style="display: inline-block;">北京</span>
</p>

<p>车祸是不幸的，愿白女士能早日康复。</p>
<p></p>
<p>当然我发这篇文章主要是为了回答后台的一些私信，兄弟们不要再问我啥时候更新技术文章了，更不下去了。你能想象吗，一个不懂 Vue 的人，一行 Vue 代码没写，靠着和 Claude Code 聊聊天，就能用 Vue 搭建一个具有一定规模的前端项目。</p>
<p></p>
<p>这太可怕了，前端如此，后端也好不到哪里去。AI 大模型的出现，让技术这条护城河轰然决堤。虽说近几年大环境差，但前两年我是不担心的，不过现在也有点蕉绿了。</p>
<p></p>
<p>最后，祝大家都能把 AI 用起来，抓住风口（话说目前还有风口吗）。</p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=cedeac0a&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZawzA37n09QasG7glOBbuXfzT0WDeP7o10icQ4URNoZXWctvWliclwcOA%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<div id="js_image_content" class="image_content "><h1 class="rich_media_title ">关于最近网络上某位白女士的遭遇</h1> <p id="js_image_desc" class="share_notice js_underline_content "></p> <!---->   <!----> <!----> <!----> <!----> <!----> <!----> <!----> <div class="rich_media_tool "><div class="rich_media_info weui-flex policy_tips js_ad_policy_tips tips_global_primary "><!----></div></div> <div id="js_end_poi_area" class="end_poi_area "></div> </div>


<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=1683ddde&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZcCCsKUqyu4CzjL7a24ib7L4wsev2O8wQ8eA0JuT09XIZ3f6aeiaxFW6A%2F0%3Fwx_fmt%3Djpeg"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=5bcf745f&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZnTs5Wic3FJVibBW1FN1weJmuKSnX8HERgovD9WGRVYblfy0jEiaOiaOpCA%2F0%3Fwx_fmt%3Djpeg"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=1831af13&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZE5UJaUlkqQfwzfDxnbyfEknSJd3xF5zprDMUsmFDulG8Qco18kkG6Q%2F0%3Fwx_fmt%3Djpeg"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=b7049189&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZpSQevtVDQjAKAEWQgRkOKFMHb9QkRpibSBhuKnXHhGz7W08rCibwYHWw%2F0%3Fwx_fmt%3Djpeg"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=4a484382&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZ19zVrQiafrQh8lZCAicDNfgKsxiaIoSj5BZOiaZP3SiciazCKNeo1ib0XN4Rw%2F0%3Fwx_fmt%3Djpeg"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=8892ffcc&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsHcxJJsiaWjp7fyrErFVuMnZLngdASFGDJSSQj5vN6Eqq5poxnibzxD9icaS875VIp7BSqyLS1moLMGA%2F0%3Fwx_fmt%3Djpeg"/></p>




<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=1f36b053&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532245%26idx%3D1%26sn%3D335ce7264464727c4e14a0ab1fc87879">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 14 Jul 2025 21:36:00 +0800</pubDate>
    </item>
    <item>
      <title>一文让你搞懂什么是 RAG</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532231&amp;idx=1&amp;sn=a473ebb6581cff53f4ba5b63e560a721</link>
      <description>鸡哥是一个优秀且苦逼的程序员，每天都被领导无情地摧残着，就在他感觉身体即将被掏空时，领导又让他充当交际花，利用</description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-06-03 14:35</span> <span style="display: inline-block;">北京</span>
</p>

<p>鸡哥是一个优秀且苦逼的程序员，每天都被领导无情地摧残着，就在他感觉身体即将被掏空时，领导又让他充当交际花，利用</p>
<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=90cfb115&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFNBYQp9bDVP4TbhsDVg8lj0eff0q8ea4WuxZrx81WrJuj3HMfqKyEZ0ztY8AAHNBMaTMYskaYQSQ%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p data-pm-slice="0 0 []" mpa-font-style="mbcglm9zjtw" style="font-size: 15px;" data-mpa-action-id="mbcglmad29g"><span leaf="">鸡哥是一个优秀且苦逼的程序员，每天都被领导无情地摧残着，就在他感觉身体即将被掏空时，领导又让他充当交际花，利用美色从某个女老板手里拿到投资。正所谓工欲善其事，必先利其器，领导给鸡哥一份文档，是从女老板秘书手里买来的，里面记录了女老板近一年的生活轨迹等，让鸡哥在脑海中构建女老板的画像。</span></p><p data-pm-slice="0 0 []" mpa-font-style="mbcgluq4arr" style="font-size: 15px;" data-mpa-action-id="mbcgluqg3zz"><span leaf="">没过多久，女老板要求在饭桌上洽谈合作的事情，那么鸡哥自然要打扮一番，可是该穿什么样的衣服呢？于是他询问大语言模型。</span></p><p data-pm-slice="0 0 []" style="text-align: left;" nodeleaf=""><img alt="绘图2_backup_06221e_backup_09049_backup_111758.png" class="rich_pages wxw-img" data-ratio="0.2074074074074074" data-type="png" data-w="1080" data-imgfileid="100048578" style="height: auto !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=d646c7c0&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLPFIjNTOsFVzYO1RQY1a2AM2Pqt8sBoX92lwYWvgoqDUMQSPK1qsEWQ%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><div mpa-font-style="mbcgoms11evc" style="font-size: 15px;" data-mpa-action-id="mbcgomsg1uyi" data-pm-slice="0 0 []"><div data-inlined-content="{&#34;items&#34;:[{&#34;type&#34;:&#34;text&#34;,&#34;content&#34;:&#34;鸡哥发现大模型的回答很官方，都是一堆正确的废话，不是自己想要的答案。但鸡哥很快又想到了新的办法，把文档和问题一起发给大语言模型不就行了&#34;,&#34;length&#34;:67,&#34;format&#34;:{&#34;dashed&#34;:false,&#34;overline&#34;:false,&#34;color&#34;:&#34;&#34;,&#34;customs&#34;:{}}}],&#34;length&#34;:67}" data-pm-slice="0 0 []"><p data-inlined-content="{&#34;items&#34;:[{&#34;type&#34;:&#34;text&#34;,&#34;content&#34;:&#34;鸡哥发现大模型的回答很官方，都是一堆正确的废话，不是自己想要的答案。但鸡哥很快又想到了新的办法，把文档和问题一起发给大语言模型不就行了。&#34;,&#34;length&#34;:68,&#34;format&#34;:{&#34;dashed&#34;:false,&#34;overline&#34;:false,&#34;color&#34;:&#34;&#34;,&#34;customs&#34;:{}}}],&#34;length&#34;:68}" data-pm-slice="0 0 []"><span data-pm-slice="0 0 []"><span leaf="" style="color: rgba(0, 0, 0, 0.9);font-family: mp-quote, &#34;PingFang SC&#34;, system-ui, -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height: 1.6;letter-spacing: 0.034em;font-style: normal;font-weight: normal;">鸡哥发现大模型的回答很官方，都是一堆正确的废话，不是自己想要的答案。但鸡哥很快又想到了新的办法，把文档和问题一起发给大语言模型不就行了。</span></span></p></div></div><p nodeleaf=""><img class="rich_pages wxw-img" data-ratio="0.3574074074074074" data-type="png" data-w="1080" data-imgfileid="100048579" style="height: auto !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=000380f1&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLklMHy7yjcDiaGbAf0xOPV2LFPygaHqzElJlWXQcUhTFAO6fWHYy66IQ%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><div><div data-inlined-content="{&#34;items&#34;:[{&#34;type&#34;:&#34;text&#34;,&#34;content&#34;:&#34;鸡哥按照大模型的建议，一上来就给女老板留下了好印象，然后又凭借三寸不烂之舌侃侃而谈，哄得女老板面色红润、手舞足蹈，最终顺利拿下了 1000 万的投资。而领导为了表扬鸡哥的功绩，大手一挥，送给他一面锦旗和 500 块钱奖金。&#34;,&#34;length&#34;:111,&#34;format&#34;:{&#34;dashed&#34;:false,&#34;overline&#34;:false,&#34;color&#34;:&#34;&#34;,&#34;customs&#34;:{}}}],&#34;length&#34;:111}" data-pm-slice="0 0 []" data-mpa-action-id="mbch9ur11kf9"><p data-pm-slice="0 0 []" mpa-font-style="mbch6bam1lsy" style="font-size: 15px;" data-mpa-action-id="mbch6bb11n3e"><span leaf="">鸡哥按照大模型的建议，一上来就给女老板留下了好印象，然后又凭借三寸不烂之舌侃侃而谈，哄得女老板面色红润、手舞足蹈，最终顺利拿下了 1000 万的投资。而领导为了表扬鸡哥的功绩，大手一挥，送给他一面锦旗和 500 块钱奖金。</span></p><p mpa-font-style="mbch6hxvjwy" style="font-size: 15px;" data-mpa-action-id="mbch6hy7xia" data-pm-slice="0 0 []"><span leaf="">正所谓逮住蛤蟆攥出尿，领导又让鸡哥去从另一个女老板手里拿到投资。鸡哥还是像之前一样先从穿搭入手，但这次大语言模型却没有给出精确的回答，因为文档太大了，信息太多、太杂，而答案可能只隐藏在文档的一个不起眼的角落，导致大模型没有抓住重点。</span></p><p mpa-font-style="mbch6lo5aiw" style="font-size: 15px;" data-mpa-action-id="mbch6loh1z35" data-pm-slice="0 0 []"><span leaf="">于是聪明的鸡哥又想到了，那能否不把整个文档发过去，而是只发和问题相关的部分呢？显然是可以的，这就是 RAG（Retrieval Augmented Generation）要解决的问题。所以 RAG 的中文翻译是检索增强生成，它是一种结合了信息检索和文本生成的 AI 技术架构，其核心思想是在生成回答之前，先从外部知识库中检索相关信息，然后基于这些检索到的信息来生成更准确、更有根据的回答。</span></p><p data-pm-slice="0 0 []"><span leaf="" data-mpa-action-id="mbch7dgubcx" style="font-size: 15px;">简单来说，RAG 的工作原理如下：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" style="font-size: 15px;">检索 (Retrieval)： 当用户提出一个问题或给出一段提示时，RAG 系统首先会从外部的知识库中检索相关信息。这个知识库可以包含各种数据，例如公司的内部文档、产品手册、最新的新闻文章、数据库记录等等。</span></p></li><li><p><span leaf="" style="font-size: 15px;">增强 (Augmented)： 检索到的相关信息会和用户的原始问题一起，被 &#34;增强&#34; 或 &#34;补充&#34; 到输入给大语言模型的内容中。</span></p></li><li><p><span leaf="" data-mpa-action-id="mbch7dgufgy" style="font-size: 15px;">生成 (Generation)： 大语言模型在接收到这些增强的上下文信息后，会生成一个更加准确、相关和基于事实的答案。</span></p></li></ul><p data-pm-slice="0 0 []"><span leaf="" data-mpa-action-id="mbch7zkrvrc" style="font-size: 15px;">那么问题来了，<span textstyle="" style="color: rgb(172, 57, 255);">只发和问题相关的部分</span>说起来简单，但<span textstyle="" style="color: rgb(172, 57, 255);">相关</span>这两个字要如何量化呢？或者说我们要怎么判断一段文字和用户的问题是否有关系呢？如果你对机器学习有所了解的话，很容易想到可以像 k 近邻算法一样，将文本抽象成空间中的一个点，通过计算两个点的距离，来判断它们之间是否相关。</span></p><p><span leaf="" style="font-size: 15px;">比如有以下四句话：</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" style="font-size: 15px;">&#34;鸡哥的穿衣风格咋样？&#34;，将这句话映射之后的点记作 A。</span></p></li><li><p><span leaf="" style="font-size: 15px;">&#34;鸡哥穿衣很时尚、很有品味&#34;，将这句话映射之后的点记作 B。</span></p></li><li><p><span leaf="" style="font-size: 15px;">&#34;鸡哥很注重穿搭&#34;，将这句话映射之后的点记作 C。</span></p></li><li><p><span leaf="" style="font-size: 15px;">&#34;鸡哥的宝剑也未尝不锋利&#34;，将这句话映射之后的点记作 D。</span></p></li></ul><p><span leaf="" data-mpa-action-id="mbch7zkr1hfq" style="font-size: 15px;">显然 AB 的距离最近，其次是 AC，最后是 AD。</span></p><p data-pm-slice="0 0 []"><span leaf="" data-mpa-action-id="mbch94nk85q" style="font-size: 15px;">所以我们需要一种新的模型，它的输入也是一段文字，但输出是一个数组，存储了映射之后的点在空间中的坐标，这种模型叫做 Embedding 模型。比如 OpenAI 的 text-embedding-3-small 模型会将文本映射成长度为 1536 的数组，text-embedding-3-large 模型会将文本映射成长度为 3072 的数组，而数组的长度就是空间的维度，维度越高，理论上能够捕捉到的语义信息就越丰富和细致。</span></p><p><span leaf="" data-mpa-action-id="mbch94nk14pf" style="font-size: 15px;">不难发现，数组就是文本的一种量化，当然这个过程是有损失的，数组长度越短，损失的信息就越多。</span></p><p style="text-align: left;" nodeleaf=""><img class="rich_pages wxw-img" data-ratio="0.1287037037037037" data-s="300,640" data-type="png" data-w="1080" type="block" data-imgfileid="100048580" style="height: auto !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=edc1d7df&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLsfdBEj1bSJ5mibNIK7mGxDHvEib1WOlaIg0ZVcTeCgD2x3vicWNlr0gNQ%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p data-pm-slice="0 0 []"><span leaf="" data-mpa-action-id="mbch9uqe1moo" style="font-size: 15px;">文本越相似，它们在空间中的距离就越近，通过两段文字的距离即可判断它们是否相关。</span></p><p><span leaf="" style="font-size: 15px;">回到之前的问题，如果文档太长了，大语言模型不好处理该怎么办？显然这个问题的解决方案已经有了。</span></p><ul class="list-paddingleft-1"><li><p><span leaf="" style="font-size: 15px;">1）将文档进行切分，至于方式可以按字数切分、按段落切分、按句子切分等等，这个过程叫做 Chunking。</span></p></li><li><p><span leaf="" style="font-size: 15px;">2）对 Chunking 后的每一段文字都做 Embedding，得到固定长度的数组，更专业的说法叫向量，因此这个过程叫做&#34;向量化&#34;或&#34;嵌入&#34;。</span></p></li><li><p><span leaf="" style="font-size: 15px;">3）将原始文本和对应的向量保存在向量数据库中，当输入一个向量时，数据库就会返回和输入向量最近的 n 条数据。</span></p></li></ul><p><span leaf="" data-mpa-action-id="mbch9uqex5b" style="font-size: 15px;">这样当用户输入问题时，先用同样的 Embedding 模型将问题转成向量，然后再从向量数据库中选择距离最近的 n 条数据，将对应的内容和问题一起发给大模型。到此，一个完整的 RAG 架构就完成了。</span></p></div></div><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>


<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=bca7f131&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLPFIjNTOsFVzYO1RQY1a2AM2Pqt8sBoX92lwYWvgoqDUMQSPK1qsEWQ%2F640%3Fwx_fmt%3Dpng"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=b5123942&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLklMHy7yjcDiaGbAf0xOPV2LFPygaHqzElJlWXQcUhTFAO6fWHYy66IQ%2F640%3Fwx_fmt%3Dpng"/></p>
<p><img src="https://wechat2rss.xlab.app/img-proxy/?k=f81d794f&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEr5ZPJNqdH1czmLW3CazNLsfdBEj1bSJ5mibNIK7mGxDHvEib1WOlaIg0ZVcTeCgD2x3vicWNlr0gNQ%2F640%3Fwx_fmt%3Dpng"/></p>



<p><a href="2247532231">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=11220db8&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532231%26idx%3D1%26sn%3Da473ebb6581cff53f4ba5b63e560a721">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Tue, 03 Jun 2025 14:35:00 +0800</pubDate>
    </item>
    <item>
      <title>我的群，没了</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532224&amp;idx=1&amp;sn=9b28575bb61eb31710e7ad077114ae18</link>
      <description>今天早上醒来，有群友给我反馈说群没了，我一看还在啊，难道说···果然，群虽然还在，但每个人发的消息，其他人都看</description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-05-25 11:17</span> <span style="display: inline-block;">北京</span>
</p>

<p>今天早上醒来，有群友给我反馈说群没了，我一看还在啊，难道说···果然，群虽然还在，但每个人发的消息，其他人都看</p>
<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=446169ec&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsGs2mRcLfuibRrqrOLb4QiaMgkun2sYmaDTiaibuibmV0LauCSaSU20OXjBcrl2MicD5tzicibgsn1ic4ia1DYQ%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p><span leaf="">今天早上醒来，有群友给我反馈说群没了，我一看还在啊，难道说···</span></p><p><span leaf="">果然，群虽然还在，但每个人发的消息，其他人都看不到，可以认为群没了。至于没的原因，相信大家也能猜出来。</span></p><p><span leaf="">任何一个交流群，一开始大家都讨论技术，但到最后都会无可避免地变成吹水群，里面充斥着打拳和键政。当然很大一部分原因在我，因为我基本不怎么管理。</span></p><p><span leaf="">至于新群我就不建了，愿各位群友都生活幸福、工作顺利。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532224">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=3c5e9faa&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532224%26idx%3D1%26sn%3D9b28575bb61eb31710e7ad077114ae18">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Sun, 25 May 2025 11:17:00 +0800</pubDate>
    </item>
    <item>
      <title>我把 Python 实用技巧放在 github.io 上面了</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532218&amp;idx=1&amp;sn=46d8e27773cc5bdfeb88230e29d8a55f</link>
      <description>我之前的源码剖析、用 C 写 Python、以及 Cython 系列都放在 github 上面了，这些算是比较</description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-03-31 12:08</span> <span style="display: inline-block;">北京</span>
</p>

<p>我之前的源码剖析、用 C 写 Python、以及 Cython 系列都放在 github 上面了，这些算是比较</p>
<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=0d72f110&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsF7oc7SYo3cVfarCsViadKzPGYXMX1iaHQmAkFA6uQFiafpuEblDy2Hg5NCicq2s5QQdibwttaRMa2GMSQ%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section><span leaf="">我之前的源码剖析、用 C 写 Python、以及 Cython 系列都放在 github 上面了，这些算是比较成体系的，也是比较有阅读价值的。</span></section><section><span leaf="">除了以上这些，我还更新了一些 Python 实用技巧，比如一些第三方库之类的。由于比较零碎，内容跨度大，想到啥写啥，所以一开始就没放在上面。</span></section><section><span leaf="">不过最近我把这些文章整理了一下，也放在上面了，大家可以选择感兴趣的阅读。后续我如果发现有意思的内容，也会更新在上面。</span></section><section><span leaf="">源码剖析：</span><span leaf=""><a href="https://satori1995.github.io/cpython-internal/" target="_blank">https://satori1995.github.io/cpython-internal/</a> ，里面也包含了 Cython 系列。</span></section><section><span leaf="">Python 实用技巧：</span><span leaf=""><a href="https://satori1995.github.io/python-tech/" target="_blank">https://satori1995.github.io/python-tech/</a></span></section><section><span leaf="">话说最近公众号转型了，估计让很多人感到诧异，其实我自己也不知道这个号该更新啥了。目前这个号对我来说，属于食之无味，弃之可惜的那种。之前都准备注销了。。。</span></section><section><span leaf="">不过有小伙伴建议我继续更新 Python，可以考虑把一些热度话题和 Python 一块更，我觉得是个好主意，希望不远的将来，我还能继续更新吧。</span></section><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532218">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=3b43f75e&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532218%26idx%3D1%26sn%3D46d8e27773cc5bdfeb88230e29d8a55f%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 31 Mar 2025 12:08:00 +0800</pubDate>
    </item>
    <item>
      <title>穿比基尼的雾雨魔理沙，你遭不遭得住。</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532161&amp;idx=1&amp;sn=025229d77b0c48e93dd0b373251de976</link>
      <description>不管什么模型，文字和手永远都是重灾区，要么手指头的长度比例不对，要么就是 6 根、甚至 7 根手指头。&#xA;&#xA;那么多好看的图片，就因为手部特征的问题，直接 GG。&#xA;&#xA;GPU 都快跑冒烟了。。。。。。</description>
      <content:encoded><![CDATA[<p>
<span>古明地觉</span> <span>2025-02-26 08:30</span> <span style="display: inline-block;">北京</span>
</p>

<p>不管什么模型，文字和手永远都是重灾区，要么手指头的长度比例不对，要么就是 6 根、甚至 7 根手指头。</p>
<p></p>
<p>那么多好看的图片，就因为手部特征的问题，直接 GG。</p>
<p></p>
<p>GPU 都快跑冒烟了。。。。。。</p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=d76e5b52&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJo7eq4JxgHiaoCsGDT51oBOX9L0nibsGSVULfzP6LkibuR7BCDGYWubzDw%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<div id="js_image_content" class="image_content "><h1 class="rich_media_title ">穿比基尼的雾雨魔理沙，你遭不遭得住。</h1>    <!----> <!----> <!----> <div class="wx_album_area js_album_wrap " style=""></div> <div class="rich_media_tool "><div class="rich_media_info weui-flex policy_tips js_ad_policy_tips tips_global_primary "><!----></div></div> </div>


<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJFmRsLsgkQQtl0BPL3qwicou826pJwI5wC6EISxTFBOmR58L9eYImMPA/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJDLwGw5cRnbgRKZIaKnCncB5q5rdkXOInfOAqIIvFDdzCTI8Y8JTDOg/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJIjtw195xbFU7ugRFsb64JDzeJGm5TPqeLhr6KPJwqqxG3iaGOWNCq2g/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJclovK8npyQooDNl32Mdlyl80dRoupcIibgKgM4Yjj0v46PD2ZL6UfdQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJrtoaLVk5ClAo1sfeT0BzGSwswH4hdrbzLxFkUXgsMssxvl0Eic5f6ag/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJepM7blQeCGb0ueFLo0O1uJzQncfb4TGwD3YBichcI42acKlyISfVoMQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJXVLbMlfHEbaWHIYnGZXCrEQI5ricXsMG69YUfagZkF6EUvibxTUN647Q/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJBqmjRYHwI5GYljtRibcYfdUlcB1ntJictRic5xVGkadhnT4ibHVn1ha9Fg/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJSqMAIDjYuoIZvjUfIve7lyASKE2ObuHmsltyuMfFfjI4V44Rt5j2qg/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJkwzKwL0ibnCU4rOw1ExrwLlshHr68d5XX8BoFGfTvkthJJWjnpo9ibWA/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJpnMib2nPpS1qyZl5MyHppQOZfdibibv0QSHFibN8xxZJ6bB4XBXGH1Nt6g/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJ4AaG2VhLoubiaX2Wzb3m4Vgicic7IzZGd6zI9lKyI50VcrBibVtvVlZWMw/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJ5ibs2zmvbUibxxooMm1uGpPgcnh0uat4XZBpC430hsLlDAj6PhAicGRvA/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJT9F7noPNEACx69QFjBQthHxYkyrjXwLGtcia8KXUQL6yD4YictxjvmfQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJSUNO7Tb3cCohaIuc9m87cFbzCzfdvDpRHE9PYTAPyxd8YDzj0icFJew/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJRHEJU3oXMaxVzibE4qPVlDjhVmzloFbQDDCCfD04dqL5cYFSqwDQrDw/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJrTXuU5wM2ZFShvRyWnwtpLDia8icp9NNB1Kcebd1BuqaVTicPDHGiaVAIQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJD2HAfRodzpM2PjxoWIrK57HIT3lQDUaENGial3aCDCYNBLdUkqExUgA/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsFVTslDMlUby4GgJxHiaTvVJFArSNvryUiaRSYtOdfb4CZlFicEABE3ey3eDug1tcFPqoQNh2uHo4R7w/0?wx_fmt=jpeg"/></p>




<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=0aede254&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532161%26idx%3D1%26sn%3D025229d77b0c48e93dd0b373251de976%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Wed, 26 Feb 2025 08:30:00 +0800</pubDate>
    </item>
    <item>
      <title>穿黑丝的古明地恋，你遭不遭得住。</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532099&amp;idx=1&amp;sn=439d225edbc092e70b8d81431a406526</link>
      <description>&#34;可爱吗？&#34;&#xA;&#34;可爱&#34;&#xA;&#xA;&#34;恋上了吗？&#34;&#xA;&#34;怎么理解这几个字，是 s[0: 1]，s[1: ]，还是 s[:]，我不懂唉&#34;&#xA;&#xA;&#34;我问你恋上她了吗？&#34;&#xA;&#34;OK，恋上了&#34;</description>
      <content:encoded><![CDATA[<p>
<span>古明地觉</span> <span>2025-02-25 08:30</span> <span style="display: inline-block;">北京</span>
</p>

<p>"可爱吗？"</p>
<p>"可爱"</p>
<p></p>
<p>"恋上了吗？"</p>
<p>"怎么理解这几个字，是 s[0: 1]，s[1: ]，还是 s[:]，我不懂唉"</p>
<p></p>
<p>"我问你恋上她了吗？"</p>
<p>"OK，恋上了"</p>
<p>\x22可爱吗？\x22</p>
<p>\x22可爱\x22</p>
<p></p>
<p>\x22恋上了吗？\x22</p>
<p>\x22怎么理解这几个字，是 s[0: 1]，s[1: ]，还是 s[:]，我不懂唉\x22</p>
<p></p>
<p>\x22我问你恋上她了吗？\x22</p>
<p>\x22OK，恋上了\x22</p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=f4d879a5&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEw9mySeYdeON4gHQAwOKwYcVib20GohibmsGhN5a0ibT4d16CG8ThLrMjPybfV3021vMI0DbDX3WtPg%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<div id="js_image_content" class="image_content "><h1 class="rich_media_title ">穿黑丝的古明地恋，你遭不遭得住。</h1>    <!----> <!----> <!----> <div class="wx_album_area js_album_wrap " style=""></div> <div class="rich_media_tool "><div class="rich_media_info weui-flex policy_tips js_ad_policy_tips tips_global_primary "><!----></div></div> </div>


<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYzV3RiaTkflKB82skOpk9nQryKQEiaibHI7VXZBmE7S9UArt7h6s3nm2hQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYAJiak3BGz8YAhyJxA1y55MZ3VALXcWMFbN1cAfDL5vFvHx9q4XNyuAw/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYvAOhARcuoMtHFZegG7fAmMOff3rJboGnbdK0CPaOLF7IiaOlQpeDrYg/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYqb26JkztTQWxZwtGJzDic3mPb30YLKplxWTkDExyI0tRor6pKAmiao0Q/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwY7J4JB7CdcyI4tMvJOCDdiaAcTzsH2pHOMpcVTFP2j61ibkVvnKDZ8xgw/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYaW885UXWHfyd9ia0MfwWu0NyzyIXqo2OMuaeYSIericQuLibAEdMK0hSQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYjaWGJslphJo7bk63OmmYvrrGjiaxaD4RQyhUTqrURkDa0HvdEiaAsO8A/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYOiacKBVib28Qrobur9ecDQic6kHYFIXSrW0PXZK9LKHW1Z8sw3otmWKlQ/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYjYibwNiaZmEk9S5KcNpribY0VkMH6HulMqsckibiaNSppciaTM51iaiaN2mt2w/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYCYibbjBVKyc4BkhC4upjrOicX72gecndlia2xfZ5l21aMcBoMibBHIt3lg/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYgiaicu3l5Yut9c8SnvBE6ZkdiaYsSXOJ9awjshj5Dys86xc5XoQDABd1A/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYibnLiaUssdia6sickrWdh96x0ncibrOe2ViaPRbbTbicBYecDBKjyibV37db1A/0?wx_fmt=jpeg"/></p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/ib8ibwulXslsEw9mySeYdeON4gHQAwOKwYic3ROQCQdqJs6icqAp9TZChFibGfH4UkvdyhKaSpYmibgicPiaqvDm8g45wQ/0?wx_fmt=jpeg"/></p>




<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=b96701ed&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532099%26idx%3D1%26sn%3D439d225edbc092e70b8d81431a406526%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Tue, 25 Feb 2025 08:30:00 +0800</pubDate>
    </item>
    <item>
      <title>我不再更新 Python 了。。。。</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532078&amp;idx=1&amp;sn=adb9b9c728edb3bf718818327c7031a4</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-02-24 08:30</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=5ef78f7f&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEV5iahjfJSvsXeibNzCDicEkG19ibK5Tco0OqJ0ym7pHJEzauhSjPia2Qf1cnZXXalFdNAybutMZup2ZQ%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section><span leaf="">跟大家说件事，这个号从创建以来，一直在努力输出技术文章，将近三年的时光，我早已枯燥。</span></section><section><span leaf="">另外随着大模型的火热，我深感专研技术的无力，学技术两三天，不如和 Claude 对话五分钟。所以从现在起，我不更新 Python 了，不更新技术类文章了。</span></section><blockquote><p><span leaf="">卷 Python 容易饿死。</span></p></blockquote><section><span leaf="">以后会蹭热度，更新一些热门话题，搞搞抽象，发发色图啥的。所以还想学 Python 的可以取消关注了😂。</span></section><section><span leaf="">至于之前的文章，个人认为有价值的也就是 Python 源码分析和 Cython，这些我都放在 github 上面了。</span></section><section><span leaf=""><a href="https://satori1995.github.io/cpython-internal/" target="_blank">https://satori1995.github.io/cpython-internal/</a></span></section><section><span leaf="">最后，谢谢大家一直以来的关注。</span></section><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532078">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=0185f9d2&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532078%26idx%3D1%26sn%3Dadb9b9c728edb3bf718818327c7031a4%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 24 Feb 2025 08:30:00 +0800</pubDate>
    </item>
    <item>
      <title>CPython 源码探秘系列文章整理完毕啦~~~</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247532074&amp;idx=1&amp;sn=64fb7322b9b8a8fd0d51e71c7a4bb3ec</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2025-01-27 08:30</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=e06f759b&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFO4wy4hzYlZb8Fbdjs44cxzmJn1q1YFrfUoa5x9GibMVetmO2ibDlmDoA7VO1KaofWu6ftp0oNHpicw%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section><span leaf=""><img data-imgfileid="100048420" class="rich_pages wxw-img" data-ratio="0.5574074074074075" data-s="300,640" data-type="png" data-w="1080" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=ba14c012&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsFO4wy4hzYlZb8Fbdjs44cx4lCw9dsgfNeeudXypdXia9gymrMDyD7DlFicfQNO6QCxaoTicMt6ia7uTA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></span></section><section><span leaf="">哈喽，好久不见，在这里提前祝大家新年快乐啊。</span></section><section><span leaf="">消失了将近两个月，不过这两个月我没有闲着，我将 CPython 源码探秘的文章全部整理出来了。</span></section><section><span leaf="">兄弟们应该记得我曾经分析过 Python3.8 版本的源码，但是后来全部删除了，为什么要删除呢？</span></section><section><span leaf="">因为之前看猫哥创办了一个潮流周刊，想要阅读的话可以采用订阅方式。当时觉得不错，就想着照猫画虎也整一个，由于当时 Python3.12 已经出来了，所以准备把 Python3.12 的源码剖析一遍。</span></section><section><span leaf="">为了让大家订阅 3.12，我就把 3.8 相关的文章全删了，感觉这个做法有点蠢了。结果因为精力有限，3.12 的源码读不下去了。</span></section><section><span leaf="">不过幸好微信公众号的文章即便删除，也可以从历史记录里面找到，所以我将 3.8 的文章又重新整理了一遍，并且补充了很多新内容，放在了 github.io 上面。</span></section><section><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);"><a href="https://satori1995.github.io/cpython-internal/" target="_blank">https://satori1995.github.io/cpython-internal/</a></span></span></section><section style="text-align: center;" nodeleaf=""><img data-imgfileid="100048425" class="rich_pages wxw-img" data-ratio="1.9597989949748744" data-s="300,640" data-type="png" data-w="796" type="block" src="https://wechat2rss.xlab.app/img-proxy/?k=c985fb85&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsFO4wy4hzYlZb8Fbdjs44cxq967bns3lCubUszyUBpNINM018MT0CUggUCIomNd8dH7HKKFCcic54Q%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></section><section><span leaf="">文章总共 106 篇，大概一百四十多万字，画了 401 张图。</span></section><section><span leaf="">这里之所以没有选择放在公众号上面，是因为有两个原因让我难以接受。</span></section><ul style="list-style-type: disc;" class="list-paddingleft-1"><li><section><span leaf="">公众号会对图片进行压缩，本来图片是高清的，结果上传之后就变模糊了。</span></section></li><li><section><span leaf="">公众号用于展示内容的区域太窄了，当一张图片的元素比较多时，根本看不清。</span></section><section><span leaf=""><br/></span></section></li></ul><section><span leaf="">因此我决定放在 github.io 上面，并且阅读也方便，想看哪一篇直接点击即可。</span></section><section><span leaf=""><span textstyle="" style="color: rgb(0, 82, 255);"><a href="https://satori1995.github.io/cpython-internal/" target="_blank">https://satori1995.github.io/cpython-internal/</a></span></span></section><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">话说这整理起来属实有点痛苦，不过总算在过年前肝出来了，算是送给技术人的新年礼物吧。回头望去，除了一些零碎的文章之外，我总共分享了三个系列：</span></section><ul style="list-style-type: disc;" class="list-paddingleft-1"><li><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">用 C 写 Python</span></section></li><li><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">Cython 从入门到精通</span></section></li><li><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">CPython 源码探秘</span></section><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;"><br/></span></section></li></ul><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">所以要是哪一天我不在了，还请兄弟们记得，有一个叫古明地觉的人，他来过。</span></section><section><span leaf="" style="color:rgba(0, 0, 0, 0.9);font-size:17px;font-family:&#34;mp-quote&#34;, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;line-height:1.6;letter-spacing:0.034em;font-style:normal;font-weight:normal;">最后祝大家新年快乐，蛇年大吉。</span></section><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247532074">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=7c7aa8e0&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247532074%26idx%3D1%26sn%3D64fb7322b9b8a8fd0d51e71c7a4bb3ec%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 27 Jan 2025 08:30:00 +0800</pubDate>
    </item>
    <item>
      <title>一文让你搞懂 Python 的生成器，以及我和一个奇葩之间的恩怨情仇</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531983&amp;idx=1&amp;sn=2d05ea71d3f45cf2ab08151f640a496c</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2024-12-02 13:51</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=f0fc7bbe&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFSegQ2XLIDRHriaSqHJgp3RibMaqiaC6cj82u1lMHtUCicr3ontLwtELTNBDLDJKzgZMQR2ere5DmQsA%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t"><br/></section></section></section></section></section><p><span style="font-size: 15px;">我之前向大家介绍了某个神奇的物种，这里再把原因解释一下。有个网名叫 chao 的人，自称超哥，在我的群里引战。由于我当初不怎么看群，中间也只是简单劝过他，直到后面越来越多的人被他气到退群，我没办法，只能将他移出群聊，移出的时候还说了句抱歉。</span><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="font-size: 15px;">本来是一件非常简单的事情，但他莫名对我产生了恨意，特别是当我整了个付费合集之后，在后台私信追着我骂。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048325" data-ratio="1.6916666666666667" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=73049a4a&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZvDyR2klVBujzETEqdQ8iaMLibnD65rEUIqUicwkQH1FokiaOXpvfxDhiamw%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048326" data-ratio="4.792592592592593" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=d4e5e78a&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZpbXUyGO1oygpI7EdicjtfmUibtv8cyqYmNoScjiahSA08q9oHibdoT3c3w%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048327" data-ratio="1.7898148148148147" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=dd273b77&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZP2h1vKgIBic29O64Mia6YSTKHZMT4q2SIQ3k8Jo0ysTjgTeutKDCtiaKw%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048328" data-ratio="2.0481481481481483" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=c76d5738&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZicVmaibYdpyicQhvVJnoVmpBweodRmFaO6osicgAVoibS6zSLsRv07K57iag%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">类似的记录还有一大堆，更加的污言秽语，这些我就不贴了。</span>至于我当初为什么不拉黑他，主要是我想看看一个人能莫名产生多大的恨意。另外我也一直在跟他复盘，希望他能明白整个事情的经过，但他似乎只专注于骂人。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048333" data-ratio="2.337962962962963" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=b04520f4&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZuzQX37gHDMm9ElomcfFkptOUSchkNbnpB13VSooHKic0ZZ5ra6mzAqw%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048334" data-ratio="2.2194444444444446" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=7308e02e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZJIrNIzz4BKiaHrrIMgSa3pVycibmscZDlfhUoibyVAX5aswcMicfzp7JLQ%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.578px;">如果只是简单的脏话，我倒还无所谓，关键的是它一直在污蔑我，说我教唆别人骂他，说我干了很多黑心事，问题是我怎么不知道。</span><span style="font-size: 15px;letter-spacing: 0.578px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;"></span></p><p><span style="font-size: 15px;">当然以上这些都不是重点，重点是它说要发文章打败我。</span></p><p style="text-align: left;"><span style="font-size: 15px;"><img class="rich_pages wxw-img" data-imgfileid="100048330" data-ratio="0.8768518518518519" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=f6365b69&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGuRibLxtKahSvO1P5W9iavL3nDH5pyZWph7UgJEwcVGeuKQ98Wu9eibCYoyC7jgC78uicpM1DDwNByYw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg%26tp%3Dwebp%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1"/></span></p><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">他之前评论也说了，要让大家明白我的文章其实一文不值。</span>听到这句话我还挺开心的，我也好奇并期待他能写出什么样的文章，但是之后就没影了······。</span></p><p><span style="font-size: 15px;">它还建立了一个微信群，我有一个群友加进去了，这是它的豪言壮语。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048331" data-ratio="2.158333333333333" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=e2837972&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZ62PDXKhPXcRP4RtRoH8PsgGdmMiccft1Zdic6lxehfne8ghRHlUK1lOQ%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">现如今已经 4 个月过去了，它依旧没什么动静，并且说完这话没几天，群友就被踢出去了？？？？？？</span></p><p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048332" data-ratio="0.31006160164271046" data-s="300,640" style="" data-type="png" data-w="974" src="https://wechat2rss.xlab.app/img-proxy/?k=ee704f36&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsEevSjnGwFibicSV4zr8hO4KZ4MzxcKEcN0AXjNKvZW88EYAibia55sERyWwVdASTVfJcjiccoz61iavDEA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">可能是我和这个人之间产生了深厚的感情，<span style="letter-spacing: 0.578px;">昨晚睡觉的时候</span>想起来自己还没有介绍生成器，今天专门请了半天假，把文章肝出来了。超哥没有完成的事情，就由我来替他完成吧，毕竟</span><span style="letter-spacing: 0.578px;font-size: 15px;">谢幕的时候，它作为主角应该站在舞台上，闪闪发光，熠熠生辉</span><span style="font-size: 15px;">。</span></p><p><span style="font-size: 24px;">❤️守护全世界最好的超哥<span style="font-size: 24px;letter-spacing: 0.578px;">❤️</span></span></p><p><span style="font-size: 24px;"><span style="font-size: 24px;letter-spacing: 0.578px;"><span style="font-size: 24px;letter-spacing: 0.578px;">❤️即使全世界都抛弃了你</span><span style="font-size: 24px;letter-spacing: 0.578px;">❤️</span></span></span></p><p><span style="font-size: 24px;"><span style="font-size: 24px;letter-spacing: 0.578px;"><span style="font-size: 24px;letter-spacing: 0.578px;"><span style="font-size: 24px;letter-spacing: 0.578px;">❤️<span style="font-size: 24px;letter-spacing: 0.578px;">我也会坚定的</span></span><span style="font-size: 24px;letter-spacing: 0.578px;">❤️</span></span></span></span></p><p><span style="font-size: 24px;"><span style="font-size: 24px;letter-spacing: 0.578px;"><span style="font-size: 24px;letter-spacing: 0.578px;"><span style="font-size: 24px;letter-spacing: 0.578px;">❤️和全世界站在一起</span><span style="font-size: 24px;letter-spacing: 0.578px;">❤️</span></span></span></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048324" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">楔子</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">本次来聊一聊 Python 的生成器，它是我们后续理解协程的基础（对不起，没有后续了）。生成器的话，估计大部分人在写程序的时候都不怎么用，但其实生成器一旦用好了，确实能给程序带来性能上的提升，那么下面就来看一看吧。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048294" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">生成器的基础知识</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">我们知道，如果函数的内部出现了 yield 关键字，那么它就不再是普通的函数了，而是一个生成器函数，调用之后会返回一个生成器对象。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">生成器对象一般用于处理循环结构，应用得当的话可以极大优化内存使用率。比如：我们读取一个大文件。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">read_file</span><span style="cursor: pointer;line-height: 26px;">(file)</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> open(file, encoding=<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;utf-8&#34;</span>).readlines()<br style="cursor: pointer;"/>print(read_file(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;假装是大文件.txt&#34;</span>))<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>[&#39;人生是什么?\n&#39;, &#39;大概是闪闪发光的同时\n&#39;, &#39;又让人感到痛苦的东西吧&#39;]<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">这个版本的函数，直接将里面的内容全部读取出来了，返回了一个列表。如果文件非常大，那么内存的开销可想而知。于是我们可以通过 yield 关键字，将普通函数变成一个生成器函数。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> typing <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> Iterator, Generator<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">read_file</span><span style="cursor: pointer;line-height: 26px;">(file)</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">with</span> open(file, encoding=<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;utf-8&#34;</span>) <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> f:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> line <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> f:<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> line<br style="cursor: pointer;"/>data = read_file(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;假装是大文件.txt&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 返回一个生成器对象</span><br style="cursor: pointer;"/>print(data)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>&lt;generator object read_file at 0x0000019B4FA8BAC0&gt;<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 使用 for 循环遍历</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> line <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> data:<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 文件每一行自带换行符, 所以这里的 print 就不用换行符了</span><br style="cursor: pointer;"/>    print(line, end=<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>人生是什么?<br style="cursor: pointer;"/>大概是闪闪发光的同时<br style="cursor: pointer;"/>又让人感到痛苦的东西吧<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">由于生成器是一种特殊的迭代器，所以也可以使用它的 __next__ 方法。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">789</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用生成器函数时，会创建一个生成器</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器虽然创建了，但是里面的代码并没有执行</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用 __next__ 方法时才会执行</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 当遇到 yield，会将生成器暂停、并返回 yield 后面的值</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时生成器处于暂停状态，如果我们不驱动它的话，它是不会前进的</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 再次执行 __next__，生成器恢复执行，并在下一个 yield 处暂停</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 456</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器会记住自己的执行进度，它总是在遇到 yield 时暂停</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用 __next__ 时恢复执行，直到遇见下一个 yield</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 789</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 显然再调用 __next__ 时，已经找不到下一个 yield 了</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 那么生成器会抛出 StopIteration，并将返回值设置在里面</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;返回值：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{e.value}</span>&#34;</span>)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 返回值：result</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">可以看到，基于生成器，我们能够实现惰性求值。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">当然啦，生成器不仅仅有 __next__ 方法，它还有 send 和 throw 方法，我们先来说一说 send。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res1 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;yield 1&#34;</span><br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;***** <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{res1}</span> *****&#34;</span>)<br style="cursor: pointer;"/>    res2 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;yield 2&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> res2<br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时程序在第一个 yield 处暂停</span><br style="cursor: pointer;"/>print(g.__next__())<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>yield 1<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用 g.send(val) 依旧可以驱动生成器执行</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 同时还可以传递一个值，交给第一个 yield 左边的 res1</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 然后寻找第二个 yield</span><br style="cursor: pointer;"/>print(g.send(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;嘿嘿&#34;</span>))<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>***** 嘿嘿 *****<br style="cursor: pointer;"/>yield 2<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 上面输出了两行，第一行是生成器里面的 print 打印的</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时生成器在第二个 yield 处暂停，调用 g.send 驱动执行</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 同时传递一个值交给第二个 yield 左边的 res2，然后寻找第三个 yield</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 但是生成器里面没有第三个 yield 了，于是抛出 StopIteration</span><br style="cursor: pointer;"/>    g.send(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;蛤蛤&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;返回值：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{e.value}</span>&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>返回值：蛤蛤<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">生成器永远在 yield 处暂停，并将 yield 后面的值返回。如果想驱动生成器继续执行，可以调用 __next__ 或 send，会去寻找下一个 yield，然后在下一个 yield 处暂停。依次往复，直到找不到 yield 时，抛出 StopIteration，并将返回值包在里面。</span></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">但是这两者的不同之处在于，send 可以接收参数，假设生成器在 <span style="color: rgb(0, 82, 255);">res = yield 123</span> 这里停下来了。</span></p><p style="margin-bottom: 0px;"><br style="letter-spacing: 0.578px;"/></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">当调用 __next__ 和 send 的时候，都可以驱动执行，但调用 send 时可以传递一个 value，并将 value 赋值给变量 res。而 __next__ 没有这个功能，如果是调用 __next__ 的话，那么 res 得到的就是一个 None。</span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">所以 <span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">res = yield 123</span> 这一行语句需要两次驱动生成器才能完成，第一次驱动会让生成器执行到 yield 123，然后暂停执行，将 123 返回。第二次驱动才会给变量 res 赋值，此时会寻找下一个 yield 然后暂停。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048295" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">生成器的预激</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">刚创建生成器的时候，里面的代码还没有执行，它的 f_lasti 是 -1。关于什么是 f_lasti，需要解释一下。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">首先随着 CPython 版本的升级，一些数据结构的底层实现也在发生改变，比如栈帧等等。在之前的版本中，栈帧有一个字段叫 f_lasti，它表示最近一条执行完毕的字节码指令的偏移量。而在 3.12 里面，这个字段已经没了。<br/></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">虽然解释器内部结构会发生变化，但暴露出来的 Python 接口是不变的，所以我们依旧可以访问该字段。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res1 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    res2 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器函数和普通函数一样，执行时也会创建栈帧</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 通过 g.gi_frame 可以很方便的获取</span><br style="cursor: pointer;"/>print(g.gi_frame.f_lasti)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># -1</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">f_lasti 是 -1，表示生成器刚被创建，还没有执行任何指令。而第一次驱动生成器执行，叫做生成器的预激。但</span><span style="letter-spacing: 0.578px;font-size: 15px;">在生成器还没有被预激时，我们调用 send，里面只能传递一个 None，否则报错。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res1 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    res2 = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    g.send(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;小云同学&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> TypeError <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(e)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>can&#39;t send non-None value to a just-started generator<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">对于尚未被预激的生成器，我们只能传递一个 None，也就是 g.send(None)。或者调用 g.__next__()，因为不管何时它传递的都是 None。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">其实也很好理解，我们之所以传值是为了赋给 yield 左边的变量，这就意味着生成器必须至少被驱动一次、在某个 yield 处停下来才可以。而未被预激的生成器，它里面的代码压根就没有执行，所以第一次驱动的时候只能传递一个 None 进去。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">如果查看生成器的源代码的话，也能证明这一点：</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048296" data-ratio="0.48055555555555557" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=8b2035f4&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsFicv27v3HwYsMmoDr9tyqU0vp6AHpB0T72HekU9icEtHJI3Dk3xORKfpm3gibKm6RoWfx4BPvb536lA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">在之前的版本中，判断条件是 f_lasti 是否等于 -1，而在 3.12 中引入了 gi_frame_state 字段，表示生成器的状态。如果生成器刚创建，并且接收的参数 arg 不为 None，那么报错。</span></p><p><span style="font-size: 15px;">那么生成器的状态都有哪些呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// Include/internal/pycore_frame.h</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">typedef</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">enum</span> _framestate {<br style="cursor: pointer;"/>    FRAME_CREATED = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-2</span>,<br style="cursor: pointer;"/>    FRAME_SUSPENDED = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>,<br style="cursor: pointer;"/>    FRAME_EXECUTING = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>,<br style="cursor: pointer;"/>    FRAME_COMPLETED = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>,<br style="cursor: pointer;"/>    FRAME_CLEARED = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span><br style="cursor: pointer;"/>} PyFrameState;</span></code></pre><p><span style="font-size: 15px;">状态总共有五种。</span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li><p><span style="font-size: 15px;">FRAME_CREATED：生成器刚创建。</span></p></li><li><p><span style="font-size: 15px;">FRAME_SUSPENDED：生成器被挂起，也就是执行到某个 yield 之后返回了。</span></p></li><li><p><span style="font-size: 15px;">FRAME_EXECUTING：生成器执行中。</span></p></li><li><p><span style="font-size: 15px;">FRAME_COMPLETED：生成器执行完毕，但栈帧对象还未被清理。</span></p></li><li><p><span style="font-size: 15px;">FRAME_CLEARED：生成器的栈帧对象被清理。</span></p><p><span style="font-size: 15px;"><br/></span></p></li></ul><p><span style="letter-spacing: 0.578px;font-size: 15px;">相关源码细节下一篇文章（对不起，没有下一篇了）会分析。<br/></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048297" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">生成器的 throw 方法</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">除了 __next__ 和 send 方法之外，生成器还有一个 throw 方法，该方法的作用和前两者类似，也是驱动生成器执行，并在下一个 yield 处暂停。但它在调用的时候，需要传递一个异常进去。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> ValueError <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>        print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;异常：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{e}</span>&#34;</span>)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器在 yield 123 处暂停</span><br style="cursor: pointer;"/>g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 向生成器传递一个异常</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 如果当前生成器的暂停位置处无法捕获传递的异常，那么会将异常抛出来</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 如果能够捕获，那么会驱动生成器执行，并在下一个 yield 处暂停</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 当前生成器位于 yield 123 处，而它所在的位置能够捕获异常</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 所以不会报错，结果就是 456 会赋值给 val</span><br style="cursor: pointer;"/>val = g.throw(ValueError(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;一个 ValueError&#34;</span>))<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>异常：一个 ValueError<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/>print(val)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>456<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">关于生成器的 __next__、send、throw 三个方法的用法我们就说完了，还是比较简单的。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048298" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">关闭生成器</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">生成器也是可以关闭的，我们来看一下。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器在 yield 123 处停止</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 关闭生成器</span><br style="cursor: pointer;"/>g.close()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器一旦关闭，就代表执行完毕了，它的栈帧会被重置为 None</span><br style="cursor: pointer;"/>print(g.gi_frame)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># None</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 再次调用 __next__，会抛出 StopIteration</span><br style="cursor: pointer;"/>    g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时 e.value 为 None</span><br style="cursor: pointer;"/>    print(e.value)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># None</span></span></code></pre><p><span style="font-size: 15px;">无论是显式地关闭生成器，还是正常情况下生成器执行完毕，内部的栈帧都会被重置为 None。而<span style="letter-spacing: 0.578px;">驱动</span>一个已经执行结束的生成器，会抛出 </span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">StopIteration 异常，并且异常的 value 属性为 None。</span></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048299" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">GeneratorExit 异常</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">这里再来说一说 GeneratorExit 这个异常，如果我们关闭一个生成器（或者生成器被删除时），那么会往里面扔一个 GeneratorExit 进去。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> GeneratorExit <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;生成器被删除了&#34;</span>)<br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 生成器在 yield 123 处暂停</span><br style="cursor: pointer;"/>g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 关闭生成器，会往里面扔一个 GeneratorExit</span><br style="cursor: pointer;"/>g.close()<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>生成器被删除了<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">这里我们捕获了传递的 GeneratorExit，所以 print 语句执行了，但如果没有捕获呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/>g.__next__()<br style="cursor: pointer;"/>g.close()</span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">此时无事发生，但是注意：如果是手动调用 throw 方法扔一个 GeneratorExit 进去，异常还是会抛出来的。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">那么问题来了，生成器为什么要提供这样一种机制呢？直接删就完了，干嘛还要往生成器内部丢一个异常呢？答案是为了资源的清理和释放。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">在 Python 还未提供原生协程，以及 asyncio 还尚未流行起来的时候，很多开源的协程框架都是基于生成器实现的协程。而创建连接的逻辑，一般都会写在 yield 后面。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">_create_connection</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 一些逻辑</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> conn<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 一些逻辑</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">但是这些连接在不用的时候，要不要进行释放呢？答案是肯定的，所以便可以这么做。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">_create_connection</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 一些逻辑</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>: <br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> conn<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> GeneratorExit:<br style="cursor: pointer;"/>        conn.close()<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 一些逻辑</span></span></code></pre><p style="margin-bottom: 0px;"><span style="font-size: 15px;">这样当我们关闭或删除生成器的时候，就能够自动对连接进行释放了。</span><br/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">不过还有一个需要注意的点，就是在捕获 GeneratorExit 之后，不可以再执行 yield，否则会抛出 RuntimeError。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> GeneratorExit:<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;生成器被删除&#34;</span>)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/>g.__next__()<br style="cursor: pointer;"/>g.close()<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>生成器被删除<br style="cursor: pointer;"/>Traceback (most recent call last):<br style="cursor: pointer;"/>  File &#34;...&#34;, line 10, in &lt;module&gt;<br style="cursor: pointer;"/>    g.close()<br style="cursor: pointer;"/>RuntimeError: generator ignored GeneratorExit<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">调用 close 方法时，如果没有成功捕获 GeneratorExit，那么生成器会直接关闭，不会有任何事情发生。但如果捕获了 GeneratorExit，那么可以在对应的语句块里做一些资源清理逻辑，但不应该再出现 yield。</span></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;letter-spacing: 0.578px;">而上面的例子中出现了 yield，所以解释器会抛出 RuntimeError，因为没捕获 GeneratorExit 还好，解释器不会有什么抱怨。但如果捕获了 GeneratorExit，说明我们知道生成器是被关闭了，既然知道，那里面还出现 yield 的意义何在呢？</span></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;letter-spacing: 0.578px;"><br/></span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">当然啦，如果出现了 yield，但没有执行到，则不会抛 RuntimeError。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> GeneratorExit:<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;生成器被删除&#34;</span>)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/>g.__next__()<br style="cursor: pointer;"/>g.close()<br style="cursor: pointer;"/>print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;------------&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>生成器被删除<br style="cursor: pointer;"/>------------<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">遇见 yield 之前就返回了，所以此时不会出现 RuntimeError。</span></p><blockquote style="margin-top: 20px;margin-bottom: 20px;cursor: pointer;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"><p><span style="font-size: 15px;">注意：GeneratorExit 继承自 BaseException，它无法被 Exception 捕获。</span></p></blockquote><p><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048300" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">yield from 的用法</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">当函数内部出现了 yield 关键字，那么它就是一个生成器函数，对于 yield from 而言亦是如此。那么问题来了，这两者之间有什么区别呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> typing <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> Generator<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen1</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>]<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen2</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>]<br style="cursor: pointer;"/>g1 = gen1()<br style="cursor: pointer;"/>g2 = gen2()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 两者都是生成器</span><br style="cursor: pointer;"/>print(isinstance(g1, Generator))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># True</span><br style="cursor: pointer;"/>print(isinstance(g2, Generator))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># True</span><br style="cursor: pointer;"/>print(g1.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># [1, 2, 3]</span><br style="cursor: pointer;"/>print(g2.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 1</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">结论很清晰，yield 对后面的值没有要求，会直接将其返回。而 yield from 后面必须跟一个可迭代对象（否则报错），然后每次返回可迭代对象的一个值。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>]<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 1</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 2</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 3</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(e.value)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># result</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">除了要求必须跟一个可迭代对象，然后每次只返回一个值之外，其它表现和 yield 是类似的。而对于当前这个例子来说，</span><span style="letter-spacing: 0.578px;font-size: 15px;color: rgb(0, 82, 255);">yield from [1, 2, 3] </span><span style="letter-spacing: 0.578px;font-size: 15px;">等价于 </span><span style="letter-spacing: 0.578px;font-size: 15px;color: rgb(0, 82, 255);">for item in [1, 2, 3]: yield item</span><span style="font-size: 15px;letter-spacing: 0.578px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;">。</span></p><p><span style="font-size: 15px;letter-spacing: 0.578px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;">所以有人觉得 yield from 貌似没啥用啊，它完全可以用 for 循环加 yield 进行代替。很明显不是这样的，yield from 背后做了非常多的事情，我们稍后说。</span></p><p><span style="font-size: 15px;letter-spacing: 0.578px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;">这里先出一道思考题：<br/></span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048301" data-ratio="0.17685185185185184" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=433e0fb3&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsFicv27v3HwYsMmoDr9tyqU0cwAU61wFkJdj7I3rsKwAEsVfricW1qfySoBD9zoPp8FqEPTQOjsRdjw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">这时候便可以通过<span style="letter-spacing: 0.578px;"> yield 和 yield from 来实现这一点。</span></span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">flatten</span><span style="cursor: pointer;line-height: 26px;">(data)</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> item <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> data:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> isinstance(item, list):<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> flatten(item)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">else</span>:<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> item<br style="cursor: pointer;"/>data = [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, [[[[[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>], <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">5</span>]]], [[[[[[[[[[[[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span>]]]]], <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">8</span>]]], <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;aaa&#34;</span>]]]], <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">250</span>]]<br style="cursor: pointer;"/>print(list(flatten(data)))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># [1, 3, 3, 5, 6, 8, &#39;aaa&#39;, 250]</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">怎么样，是不是很简单呢？</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048302" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">委托生成器</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">如果单从语法上来看的话，会发现 yield from 貌似没什么特殊的地方，但其实 yield from 还可以作为委托生成器。委托生成器会在调用方和子生成器之间建立一个双向通道，什么意思呢？我们举例说明。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;接收到子生成器的返回值: <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{res}</span>&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># middle 里面出现了 yield from gen()</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时 middle() 便是委托生成器，gen() 是子生成器</span><br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 而 yield from 会在调用方和子生成器之间建立一个双向通道</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 两者是可以互通的，调用 g.send、g.throw 都会直接传递给子生成器</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 456</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 问题来了，如果再调用一次 __next__ 会有什么后果呢？</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 按照之前的理解，应该会抛出 StopIteration</span><br style="cursor: pointer;"/>print(g.__next__())<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>接收到子生成器的返回值: result<br style="cursor: pointer;"/>Traceback (most recent call last):<br style="cursor: pointer;"/>  File &#34;...&#34;, line 21, in &lt;module&gt;<br style="cursor: pointer;"/>    print(g.__next__())<br style="cursor: pointer;"/>StopIteration<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">在第三次调用 __next__ 的时候，确实抛了异常，但是委托生成器收到了子生成器的返回值。也就是说，委托生成器在调用方和子生成器之间建立了双向通道，两者是直接通信的，并且当子生成器出现 StopIteration 时，委托生成器还要负责兜底。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">委托生成器会将子生成器抛出的 StopIteration 里面的 value 取出来，然后赋值给左侧的变量 res，并在自己内部继续寻找 yield。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">换句话说，当子生成器 return 之后，委托生成器会拿到返回值，并将子生成器抛出的异常给捕获掉。但是还没完，因为还要找到下一个 yield，那么从哪里找呢？显然是从委托生成器的内部寻找，于是接下来就变成了调用方和委托生成器之间的通信。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;font-size: 16px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;">如果在委托生成器内部能找到下一个 yield，那么会将值返回给调用方。如果找不到，那么就重新构造一个 StopIteration，将异常抛出去。此时异常的 value 属性，就是委托生成器的返回值。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;委托生成器返回了子生成器的返回值：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{res}</span>&#34;</span><br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    g.__next__()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(e.value)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 委托生成器返回了子生成器的返回值：result</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;font-size: 15px;">大部分情况下，我们并不关注委托生成器的返回值，我们更关注的是子生成器。于是可以换种写法：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">456</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">789</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> (<span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen())<br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> v <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> g:<br style="cursor: pointer;"/>    print(v)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>123<br style="cursor: pointer;"/>456<br style="cursor: pointer;"/>789<br style="cursor: pointer;"/>result<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">所以委托生成器负责在调用方和子生成器之间建立一个双向通道，通道一旦建立，调用方可以和子生成器直接通信。虽然调用的是委托生成器的 __next__、send、throw 等方法，但影响的都是子生成器。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">并且委托生成器还可以对子生成器抛出的 StopIteration 异常进行兜底，会捕获掉该异常，然后拿到返回值，这样就无需手动捕获子生成器的异常了。但问题是委托生成器还要找到下一个 yield，并将值返回给调用方，此时这个重担就落在了它自己头上。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">如果找不到，还是要将异常抛出来的，只不过抛出的 StopIteration 是委托生成器构建的。而子生成器抛出的 StopIteration，早就被委托生成器捕获掉了。于是我们可以考虑在 yield from 的前面再加上一个 yield，这样就不会抛异常了。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048303" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">为什么要有委托生成器</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">我们上面已经了解了委托生成器的用法，不过问题来了，这玩意为啥会存在呢？上面的逻辑，即便不使用 yield from 也可以完成啊。<br/></span></p><p style="margin-bottom: 0px;"><br style="letter-spacing: 0.578px;"/></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">其实是因为我们上面的示例代码比较简单（为了演示用法），当需求比较复杂时，将生成器内部的部分操作委托给另一个生成器是有必要的，这也是委托生成器的由来。</span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">而委托生成器不仅要能保证调用方和子生成器之间直接通信，还要能够以一种优雅的方式获取子生成器的返回值，于是新的语法 yield from 就诞生了。</span></p><p style="margin-bottom: 0px;"><br style="letter-spacing: 0.578px;"/></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">但其实 yield from 背后为我们做得事情还不止这么简单，它不单单是建立双向通道、获取子生成器的返回值，它还会处理子生成器内部出现的异常，详细内容可以查看 <span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">PEP380</span>。</span></p><blockquote style="margin-top: 20px;margin-bottom: 20px;cursor: pointer;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"><p style="cursor: pointer;line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><span style="font-size: 15px;"><a href="https://peps.python.org/pep-0380/" target="_blank">https://peps.python.org/pep-0380/</a></span></p></blockquote><p><span style="letter-spacing: 0.578px;font-size: 15px;">这里我们直接给出结论，并通过代码演示一下。</span></p><p><span style="color: rgb(0, 122, 170);"><strong>1）子生成器 yield 后面的值，会直接返回给调用方；调用方 send 发送的值，也会直接传给子生成器。</strong></span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> [res]<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> (<span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen())<br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 子生成器 yield 后面的值，会直接返回给调用方</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用方 send 发送的值，也会直接传给子生成器</span><br style="cursor: pointer;"/>print(g.send(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;小云同学&#34;</span>))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># [&#39;小云同学&#39;]</span></span></code></pre><p><span style="font-size: 15px;">另外还要补充一个细节，如果 yield from 一个已经消耗完毕的生成器，会直接返回 None。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    sub = gen()<br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> sub<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> res + <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34; from gen()&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 到这里的话，sub = gen() 这个生成器已经被消耗完毕了</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 如果我们继续 yield from 的话，会直接返回 None</span><br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> sub<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;res: <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{res}</span>&#34;</span><br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># result from gen()</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此处执行 g.__next__() 时</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 委托生成器内部会执行第二个 res = yield from sub</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 但问题是 sub 之前就已经被消耗完了，所以会直接返回 None，然后寻找下一个 yield</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># res: None</span></span></code></pre><p><span style="font-size: 15px;">所以不要对生成器做二次消费。</span></p><p><span style="color: rgb(0, 122, 170);"><strong>2）子生成器结束时，最后的 return value 等价于 raise StopIteration(value)。然后该异常会被 yield from 捕获，并将 value 赋值给 yield from 左侧的变量。并且<span style="letter-spacing: 0.578px;">在拿到子生成器的返回值时，委托生成器会继续运行，寻找下一个 yield。</span></strong></span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    res = <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> res + <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34; from middle()&#34;</span><br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 子生成器 gen() 在 return 时会抛出 StopIteration</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 然后在委托生成器内部被捕获，并将返回值赋给 res</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 接着继续寻找下一个 yield</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># result from middle()</span></span></code></pre><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">另外补充一点，生成器在 return 时，等价于抛出一个 StopIteration。但异常必须在 return 的时候隐式抛出，如果是在生成器内部 raise </span><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;">StopIteration 则是不合法的。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">raise</span> StopIteration(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span>)<br style="cursor: pointer;"/>g = gen()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/>print(g.__next__())<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>Traceback (most recent call last):<br style="cursor: pointer;"/>  File &#34;......&#34;, line 3, in gen<br style="cursor: pointer;"/>    raise StopIteration(&#34;result&#34;)<br style="cursor: pointer;"/>StopIteration: result<br style="cursor: pointer;"/>The above exception was the direct cause of the following exception:<br style="cursor: pointer;"/>Traceback (most recent call last):<br style="cursor: pointer;"/>  File &#34;......&#34;, line 7, in &lt;module&gt;<br style="cursor: pointer;"/>    print(g.__next__())<br style="cursor: pointer;"/>RuntimeError: generator raised StopIteration<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="font-size: 15px;">此时会引发一个 RuntimeError。</span></p><p><span style="color: rgb(0, 122, 170);"><strong>3）如果子生成器在执行的过程中，内部出现了异常，那么会将异常丢给委托生成器。委托生成器会尝试处理该异常，如果处理不了，那么再调用子生成器的 throw 方法将异常扔回去。</strong></span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">raise</span> ValueError(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;出了个错&#34;</span>)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时子生成器会抛出 ValueError，而委托生成器没有异常捕获逻辑，无法处理</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 于是会调用子生成器的 throw 方法，将异常重新扔回去，最终由调用方来处理</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> ValueError <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(e)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 出了个错</span></span></code></pre><p><span style="font-size: 15px;">那如果委托生成器可以处理子生成器抛出的异常呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">raise</span> ValueError(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;出了个错&#34;</span>)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> ValueError <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;异常：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{e}</span>&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 当子生成器抛出异常时，它就已经结束了</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result from middle()&#34;</span><br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 异常：出了个错</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># result from middle()</span></span></code></pre><p><span style="font-size: 15px;">如果委托生成器可以处理子生成器抛出的异常，那么接下来就是调用方和委托生成器之间的事情了。</span></p><p><span style="font-size: 15px;">再比如我们将生成器 close 掉，看看结果会怎样，我们知道它会 throw 一个 GeneratorExit。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">gen</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">123</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">middle</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> gen()<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> GeneratorExit <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;子生成器结束了&#34;</span>)<br style="cursor: pointer;"/>g = middle()<br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 123</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 关闭子生成器，会 throw 一个 GeneratorExit</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 然后这个 GeneratorExit 会向上透传给委托生成器</span><br style="cursor: pointer;"/>g.close()<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>子生成器结束了<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 注意：委托生成器也是同理</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 一旦捕获了 GeneratorExit，后续不应该再出现 yield</span></span></code></pre><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">yield from 算是 Python 里面特别难懂的一个语法了，但如果理解了 yield from，后续理解 await 就会简单很多。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;color: rgb(34, 34, 34);outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048304" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="color: rgb(34, 34, 34);outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="color: rgb(217, 33, 66);font-size: 20px;">生成器表达式</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;color: rgb(34, 34, 34);outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">Python 里面还有一个生成器表达式，我们来看一下。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> typing <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> Generator<br style="cursor: pointer;"/>g = (x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> range(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span>))<br style="cursor: pointer;"/>print(isinstance(g, Generator))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># True</span><br style="cursor: pointer;"/>print(g)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;generator object &lt;genexpr&gt; at 0x...&gt;</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 0</span><br style="cursor: pointer;"/>print(g.__next__())  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 1</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">如果表达式是在一个函数里面，那么生成器表达式周围的小括号可以省略掉。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> random<br style="cursor: pointer;"/>d = [random.randint(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span>) <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> _ <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> range(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">100</span>)]<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 我们想统计里面大于 5 的元素的总和</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 下面两种做法都是可以的</span><br style="cursor: pointer;"/>print(<br style="cursor: pointer;"/>    sum((x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> d <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> x &gt; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">5</span>)),<br style="cursor: pointer;"/>    sum(x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> d <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> x &gt; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">5</span>)<br style="cursor: pointer;"/>)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 397 397</span></span></code></pre><p style="margin-bottom: 0px;"><span style="font-size: 15px;">这两种做法是等价的，字节码完全一样。</span><br/></p><p style="margin-bottom: 0px;"><br mpa-from-tpl="t" style="letter-spacing: 0.578px;"/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">但要注意，<span style="letter-spacing: 0.578px;">生成器表达式还存在一些陷阱，一不小心就可能踩进去。至于是什么陷阱呢？很简单，一句话：<span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">使用生成器表达式创建生成器的时候，in 后面的变量就已经确定了，但其它的变量则不会</span>。举个栗子：</span></span></p><p style="margin-bottom: 0px;"><br/></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">g = (巭孬嫑夯烎 <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>])</span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">执行这段代码不会报错，尽管 for 前面那一坨我们没有定义，但不要紧，因为生成器是惰性执行的。但如果我们调用了 g.__next__()，那么很明显就会报错了，会抛出 NameError。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">g = (x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> lst)</span></code></pre><p style="margin-bottom: 0px;"><span style="font-size: 15px;letter-spacing: 0.578px;">但是这段代码会报错：</span><span style="letter-spacing: 0.578px;font-size: 15px;color: rgb(0, 82, 255);">NameError: name &#39;lst&#39; is not defined</span><span style="font-size: 15px;letter-spacing: 0.578px;">，因为 in 后面的变量在创建生成器的时候就已经确定好了。而在创建生成器的时候，发现 lst 没有定义，于是抛出 NameError。</span></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">所以，陷阱就来了：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">i = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span><br style="cursor: pointer;"/>g = (x + i <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>])<br style="cursor: pointer;"/>i = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 输出的不是 (2, 3, 4)</span><br style="cursor: pointer;"/>print(tuple(g))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (11, 12, 13)</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">因为生成器只有在执行的时候，才会去确定变量 i 究竟指向谁，而调用 tuple(g) 的时候 i 已经被修改了。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">lst = [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>]<br style="cursor: pointer;"/>g = (x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> lst)<br style="cursor: pointer;"/>lst = [<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">5</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span>]<br style="cursor: pointer;"/>print(tuple(g))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (1, 2, 3)</span></span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">但这里输出的又是 <span style="font-size: 15px;color: rgb(0, 82, 255);">(1, 2, 3)</span>，因为在创建生成器的时候，in 后面的变量就已经确定了，这里会和 lst 指向同一个列表。而第三行改变的只是变量 lst 的指向，和生成器无关。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">g = (x <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> [<span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>, <span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>, <span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span>])<br style="cursor: pointer;"/><span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> i <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> [<span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span>]:<br style="cursor: pointer;"/>    g = (x + i <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> x <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">in</span> g)<br style="cursor: pointer;"/>print(tuple(g))</span></code></pre><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">思考一下，上面代码会打印啥？下面进行分析：</span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">初始的 g，可以看成是 <span style="letter-spacing: 0.578px;font-size: 15px;color: rgb(0, 82, 255);">(1, 2, 3, 4)</span>，因为 in 后面是啥，在创建生成器的时候就确定了；</span></p></li><li><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">第一次</span><span style="font-size: 15px;letter-spacing: 0.578px;">循环之后，g 就相当于 </span><span style="font-size: 15px;letter-spacing: 0.578px;color: rgb(0, 82, 255);">(1+i, 2+i, 3+i, 4+i)</span><span style="font-size: 15px;letter-spacing: 0.578px;">；</span></span></p></li><li style="font-size: 15px;"><p>第二次循环之后，g 就相当于 <span style="color: rgb(0, 82, 255);">(1+i+i, 2+i+i, 3+i+i, 4+i+i)</span>；</p></li></ul><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">而循环结束后，变量 i 会指向 10，所以打印结果就是 </span><span style="font-size: 15px;letter-spacing: 0.578px;color: rgb(0, 82, 255);">(21, 22, 23, 24)</span><span style="font-size: 15px;letter-spacing: 0.578px;">。</span></span></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;color: rgb(34, 34, 34);outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048305" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="color: rgb(34, 34, 34);outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="color: rgb(217, 33, 66);font-size: 20px;">生成器与协程</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;color: rgb(34, 34, 34);outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">在 Python 还没有引入原生协程的时候，很多开源框架都是基于生成器模拟的协程，最经典的莫过于 Tornado。然而事实上，即便是原生协程，<span style="font-size: 15px;letter-spacing: 0.578px;">在底层也是基于生成器实现的。</span></span></p><pre style="margin-top: 10px;margin-bottom: 10px;font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="padding: 15px 16px 16px;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">async</span> <span style="cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">native_coroutine</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">try</span>:<br style="cursor: pointer;"/>    native_coroutine().__await__().__next__()<br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">except</span> StopIteration <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">as</span> e:<br style="cursor: pointer;"/>    print(e.value)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 古明地觉</span></span></code></pre><p style="margin-bottom: 0px;"><span style="font-size: 15px;">这里没有创建事件循环，而是直接驱动协程执行。</span><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">我们再演示一段代码，看看让生成器协程和原生协程混合使用会是什么效果。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 14px;"><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> asyncio<br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> time<br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> types<br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">async</span> <span style="font-size: 14px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">some_task</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>    某个耗时较长的任务<br style="cursor: pointer;"/>    &#34;&#34;&#34;</span><br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">await</span> asyncio.sleep(<span style="font-size: 14px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>)<br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;task result&#34;</span><br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">async</span> <span style="font-size: 14px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">native_coroutine</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>    原生协程<br style="cursor: pointer;"/>    &#34;&#34;&#34;</span><br style="cursor: pointer;"/>    result = <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">await</span> some_task()<br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{result}</span> from native coroutine&#34;</span><br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">@types.coroutine  </span></span><span style="cursor: pointer;line-height: 26px;font-size: 14px;color: rgb(136, 136, 136);"># 或者使用 @asyncio.coroutine</span><span style="font-size: 14px;"><br style="cursor: pointer;"/><span style="font-size: 14px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">generator_coroutine</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>    生成器模拟的协程<br style="cursor: pointer;"/>    &#34;&#34;&#34;</span><br style="cursor: pointer;"/>    result = <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">yield</span> <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">from</span> some_task()<br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{result}</span> from generator coroutine&#34;</span><br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">async</span> <span style="font-size: 14px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">main</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    start = time.time()<br style="cursor: pointer;"/>    result = <span style="font-size: 14px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">await</span> asyncio.gather(<br style="cursor: pointer;"/>        native_coroutine(), generator_coroutine()<br style="cursor: pointer;"/>    )<br style="cursor: pointer;"/>    end = time.time()<br style="cursor: pointer;"/>    print(result)<br style="cursor: pointer;"/>    print(<span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;耗时：<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{end - start}</span>&#34;</span>)<br style="cursor: pointer;"/>asyncio.run(main())<br style="cursor: pointer;"/><span style="font-size: 14px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>[&#39;task result from native coroutine&#39;, &#39;task result from generator coroutine&#39;]<br style="cursor: pointer;"/>耗时：3.0016210079193115<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p style="margin-bottom: 0px;"><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">从效果上来看，两种方式是等价的。yield from 会驱动协程对象执行，当协程执行 return 的时候，会抛出一个 StopIteration 异常。然后 yield from 再将异常捕获掉，并取出里面的返回值。</span></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;">但使用装饰器 + yield from 这种方式不够优雅，并且 yield from 即用于生成器，又用于协程，容易给人造成困惑。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">为此 Python 从 3.5 开始引入了原生协程，使用 async def </span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;"> 定义协程，使用 await 驱动协程执行。</span></p><p style="margin-bottom: 0px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;"><br/></span></p><p style="margin-bottom: 0px;"><span style="font-size: 15px;letter-spacing: 0.51px;">关于协程的更多细节，后续在介绍协程的时候再说，总之我们现在应该使用原生协程，至于 yield from 就让它留在历史的尘埃中吧，我们只需要知道整个演进过程即可。</span></p><p style="margin-bottom: 0px;"><br/></p><p style="margin-bottom: 0px;"><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;color: rgb(34, 34, 34);outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048306" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="color: rgb(34, 34, 34);outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="color: rgb(217, 33, 66);font-size: 20px;">小结</span></p></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">以上我们就从 Python 的角度梳理了一遍生成器相关的知识，下一篇文章我们将从源码的角度来分析生成器的具体实现。</span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;"><span style="letter-spacing: 0.578px;font-size: 15px;">对不起，没有下一篇了，感谢大家对这个系列的支持。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247531983">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=cf117119&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531983%26idx%3D1%26sn%3D2d05ea71d3f45cf2ab08151f640a496c%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 02 Dec 2024 13:51:00 +0800</pubDate>
    </item>
    <item>
      <title>Python 解释器源码剖析系列，我决定暂停更新了</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531971&amp;idx=1&amp;sn=b32279b15eb7419fd26df8884300bd42</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2024-12-01 23:53</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=8615f87b&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFj7OW4xCm6etTnED290ic62HOTSPLjbz3JvlHouJ8As7mg5ibBw6gtWF3PKWklSsrtMOMPwcPnXRyA%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p data-mpa-powered-by="yiban.io">大家好，我是古明地觉，关于 Python 解释器源码剖析系列，我决定暂停更新了，微信豆已经退回。</p><p>至于我暂停更新的原因，主要有以下几点。<br/></p><p>1）Python 源码有大几十万行，阅读起来真的是费时又费力。再加上随着年纪的增长，琐事越来越多，对技术的热忱也越来越淡，早已不再是刚毕业那会儿了。现在看着密密麻麻的 C 代码，已经没有再阅读下去的动力。</p><p>2）研究 Python 解释器注定只是少数人的狂欢，因为这个除了在面试的时候能装一下之外，其实没啥大用，差不多就行了。<br/></p><p>3）程序猿如果只会技术的话，其实很难走得长远。按照国内的环境，免不了被裁员。</p><p>4）随着 AI 的普及，<span style="letter-spacing: 0.578px;">花大把的时间去</span><span style="letter-spacing: 0.578px;">研究源码，在现在这个时</span><span style="letter-spacing: 0.578px;">代</span><span style="letter-spacing: 0.578px;">已经得不偿失了。</span></p><p>基于以上几个原因，Python 源码剖析系列暂时不更新了，<span style="letter-spacing: 0.578px;">感谢大家一直以来的支持。真的有点累了，读不下去了。</span></p><p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">至于我接下来会做什么，</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">现在还没</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">有想好，不过可能会做一些比较有意思、吸引人眼球的话题吧。或者做一些基础内容，以及工作中会经常用到的，分享一些实用工具之类的。Python 解释器源码这种耗费精力的东西真的做不动了o(╥﹏╥)o。</span></p><p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">原来的合集已经删除，新的合集链接在这里：<a target="_blank" href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg3NTczMDU2Mg==&amp;action=getalbum&amp;album_id=3749286737353179142#wechat_redirect" textvalue="Python3.12 源码剖析" linktype="text" imgurl="" imgdata="null" tab="innerlink" data-linktype="2">Python3.12 源码剖析</a><br/></span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247531971">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=3d12bca3&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531971%26idx%3D1%26sn%3Db32279b15eb7419fd26df8884300bd42%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Sun, 01 Dec 2024 23:53:00 +0800</pubDate>
    </item>
    <item>
      <title>分享一个栏目《极客头条》</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531962&amp;idx=1&amp;sn=74e37e68dc463c6b3665dd7307c96dd9</link>
      <description></description>
      <content:encoded><![CDATA[<p>
<span>古明地觉</span> <span>2024-11-29 09:59</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=5e80173f&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFSegQ2XLIDRHriaSqHJgp3RiaIibmvHgnRSPaCZIuxuriaT9qiczHvEdcRIRY3hicttKPY777a3icQFibtvw%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p>技术人一定不能和时代脱节，信息差很重要👨‍💻</p><p>《极客头条》是专为技术人打造的 免 费 栏目～内容涵盖大模型开发生态、生成式 AI、元宇宙等热点话题。从李彦宏的洞见，到元宇宙的最新动态，这里都有！</p><p>📅 周一到周五，每天更新、永久有效，戳此领取：<a href="http://gk.link/a/12tye" target="_blank">http://gk.link/a/12tye</a></p><p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048310" data-ratio="1.7777777777777777" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=73a36784&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsFSegQ2XLIDRHriaSqHJgp3R423KKyzXROgaQP5frEVD8lCa4nnKOxFm5G89mDLxZpDhN7NllvQkXQ%2F640%3Fwx_fmt%3Djpeg%26from%3Dappmsg"/></p><p>详细链接可以点击原文链接。</p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="http://gk.link/a/12tye">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=4b49accc&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531962%26idx%3D1%26sn%3D74e37e68dc463c6b3665dd7307c96dd9%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Fri, 29 Nov 2024 09:59:00 +0800</pubDate>
    </item>
    <item>
      <title>装饰器是怎么实现的？</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531961&amp;idx=1&amp;sn=b46ef5c5e86a713bb23aead6bc57dc78</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2024-11-28 10:00</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=2ad89aeb&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsEdjxR8ds0ECA1SBOEcxe36zuSf2hI3wnA09pqQBBZA2Lgic0hPoKxb2X2Lqn6OHV5nKc3vI2CVjicw%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<p><span style="letter-spacing: 0.578px;font-size: 15px;">装饰器是 Python 的一个亮点，但并不神秘，因为它本质上就是高阶函数加上闭包，只不过给我们提供了一个优雅的语法糖。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">至于为什么要有装饰器，我觉得有句话说的非常好，装饰器存在的最大意义就是可以在不改动原函数的代码和调用方式的情况下，为函数增加一些新的功能。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco</span><span style="cursor: pointer;line-height: 26px;">(func)</span>:</span><br style="cursor: pointer;"/>    print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;都闪开，我要开始装饰了&#34;</span>)<br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">(*args, **kwargs)</span>:</span><br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;开始了&#34;</span>)<br style="cursor: pointer;"/>        ret = func(*args, **kwargs)<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;结束&#34;</span>)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> ret<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 这一步等价于 foo = deco(foo)</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 因此上来就会打印 deco 里面的 print</span><br style="cursor: pointer;"/><span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">@deco</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">(a, b)</span>:</span><br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;a = <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{a}</span>，b = <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{b}</span>&#34;</span>)<br style="cursor: pointer;"/>print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;---------&#34;</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>都闪开，我要开始装饰了<br style="cursor: pointer;"/>---------<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 此时再调用 foo，已经不再是原来的 foo 了</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 而是 deco 里面的闭包 inner</span><br style="cursor: pointer;"/>foo(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>开始了<br style="cursor: pointer;"/>a = 1，b = 2<br style="cursor: pointer;"/>结束<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span><br style="cursor: pointer;"/></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">如果不使用装饰器的话：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco</span><span style="cursor: pointer;line-height: 26px;">(func)</span>:</span><br style="cursor: pointer;"/>    print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;都闪开，我要开始装饰了&#34;</span>)<br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">(*args, **kwargs)</span>:</span><br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;开始了&#34;</span>)<br style="cursor: pointer;"/>        ret = func(*args, **kwargs)<br style="cursor: pointer;"/>        print(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;结束&#34;</span>)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> ret<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">(a, b)</span>:</span><br style="cursor: pointer;"/>    print(<span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;a = <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{a}</span>，b = <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{b}</span>&#34;</span>)<br style="cursor: pointer;"/>foo = deco(foo)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>都闪开，我要开始装饰了<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/>foo(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>, <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>)<br style="cursor: pointer;"/><span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>开始了<br style="cursor: pointer;"/>a = 1，b = 2<br style="cursor: pointer;"/>结束<br style="cursor: pointer;"/>&#34;&#34;&#34;</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">打印结果告诉我们，装饰器只是类似于 foo=deco(foo) 的一个语法糖罢了。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">至于字节码这里就不看了，还是那句话，<span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);"><strong>@ </strong></span>只是个语法糖，它和我们直接调用 foo=deco(foo) 是等价的，所以理解装饰器（decorator）的关键就在于理解闭包（closure）。</span></p><p><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;">另外函数在被装饰器装饰之后，整个函数其实就已经变了，而为了保留原始信息我们一般会从 functools 模块中导入一个 wraps 函数。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;">当然装饰器还可以写的更复杂，比如带参数的装饰器、类装饰器等等，不过这些都属于 Python 层级的东西了，我们就不说了。</span></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">另外装饰器还可以不止一个，如果一个函数被多个装饰器装饰，会有什么表现呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco1</span><span style="cursor: pointer;line-height: 26px;">(func)</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;&lt;deco1&gt;<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{func()}</span>&lt;/deco1&gt;&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco2</span><span style="cursor: pointer;line-height: 26px;">(func)</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;&lt;deco2&gt;<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{func()}</span>&lt;/deco2&gt;&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco3</span><span style="cursor: pointer;line-height: 26px;">(func)</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;&lt;deco3&gt;<span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{func()}</span>&lt;/deco3&gt;&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/><span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">@deco1</span><br style="cursor: pointer;"/><span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">@deco2</span><br style="cursor: pointer;"/><span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">@deco3</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span><br style="cursor: pointer;"/>print(foo())</span></code></pre><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;">解释器还是从上到下解释，当执行到 <span style="color: rgb(0, 82, 255);">@deco1 </span>的时候，肯定要装饰了，但它下面不是函数，也是一个装饰器，于是表示：要不哥们，你先装饰。然后执行 <span style="color: rgb(0, 82, 255);">@deco2</span>，但它下面还是一个装饰器，于是重复了刚才的话，把皮球踢给 <span style="color: rgb(0, 82, 255);">@deco3</span>。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;">当执行 </span><span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">@deco3 </span><span style="letter-spacing: 0.578px;">的时候，发现下面终于是一个普通的函数了，于是装饰了。</span></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;letter-spacing: 0.578px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;">deco3 装饰完毕之后，<span style="letter-spacing: 0.578px;color: rgb(0, 122, 170);">foo = deco3(foo)</span>。然后 deco2 发现 deco3 已经装饰完毕，那么会对 deco3 装饰的结果再进行装饰，此时 <span style="letter-spacing: 0.578px;color: rgb(0, 122, 170);">foo = deco2(deco3(foo))</span>；同理，再经过 deco1 的装饰，最终得到了 <span style="letter-spacing: 0.578px;color: rgb(0, 122, 170);">foo  =  deco1(deco2(deco3(foo)))</span>。</span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="letter-spacing: 0.578px;font-size: 15px;"><br/></span></p><p style="margin-bottom: 0px;letter-spacing: 0.578px;"><span style="font-size: 15px;letter-spacing: 0.578px;">于是最终输出：</span><span style="background-color: rgba(0, 0, 0, 0.05);color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"></span></p><blockquote style="margin-top: 20px;margin-bottom: 20px;cursor: pointer;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"><p><span style="font-size: 15px;">&lt;deco1&gt;&lt;deco2&gt;&lt;deco3&gt;古明地觉&lt;/deco3&gt;&lt;/deco2&gt;&lt;/deco1&gt;</span></p></blockquote><p><span style="font-size: 15px;letter-spacing: 0.578px;">所以当有多个装饰器的时候，会从下往上装饰；然后执行的时候，会从上往下执行。</span></p><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">以上就是装饰器相关的内容，可以说非常简单了，甚至有点水文章的嫌疑，因为核心都在上一篇介绍的</span><span style="font-size: 15px;color: rgb(0, 122, 170);">闭包</span><span style="font-size: 15px;letter-spacing: 0.578px;">当中。还是那句话，理解装饰器的关键就在于理解闭包。</span></span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247531961">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=340a11a5&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531961%26idx%3D1%26sn%3Db46ef5c5e86a713bb23aead6bc57dc78%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Thu, 28 Nov 2024 10:00:00 +0800</pubDate>
    </item>
    <item>
      <title>闭包是怎么实现的？</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531936&amp;idx=1&amp;sn=3894ab282272cdd7b7b1332a4ba9be32</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2024-11-25 10:43</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=44225c6d&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw6H4Bo1vEnIibaGic19fnqFT91V8y2YNAGjZLic2Xa94M6FHeB9r4Ng7NIg%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section data-mpa-template="t" mpa-from-tpl="t" data-mpa-powered-by="yiban.io" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048259" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">楔子</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;">在之前的文章中一直反复提到四个字：</span><span style="color: rgb(0, 122, 170);">名字空间</span><span style="letter-spacing: 0.578px;">。一段代码执行的结果不光取决于代码中的符号，更多的是取决于代码中符号的语义，而这个运行时的语义正是由名字空间决定的。</span></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">名字空间是由虚拟机在运行时动态维护的，但有时我们希望将名字空间静态化。换句话说，我们希望有的代码不受名字空间变化带来的影响，始终保持一致的功能该怎么办呢？随便举个例子：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">login</span><span style="cursor: pointer;line-height: 26px;">(user_name, password, user)</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">not</span> (user_name == <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">and</span> password == <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;123&#34;</span>):<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;用户名密码不正确&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">else</span>:<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;欢迎: <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{user}</span>&#34;</span><br style="cursor: pointer;"/>print(login(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;123&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span>))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 欢迎: 古明地觉</span><br style="cursor: pointer;"/>print(login(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;123&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地恋&#34;</span>))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 欢迎: 古明地恋</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">我们注意到每次都需要输入 username 和 password，于是可以通过使用嵌套函数来设置一个基准值。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">deco</span><span style="cursor: pointer;line-height: 26px;">(user_name, password)</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">login</span><span style="cursor: pointer;line-height: 26px;">(user)</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">not</span> (user_name == <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span> <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">and</span> password == <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;123&#34;</span>):<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;用户名密码不正确&#34;</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">else</span>:<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="font-size: 15px;color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">f&#34;欢迎: <span style="color: rgb(224, 108, 117);cursor: pointer;line-height: 26px;">{user}</span>&#34;</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> login<br style="cursor: pointer;"/>login = deco(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;123&#34;</span>)<br style="cursor: pointer;"/>print(login(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span>))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 欢迎: 古明地觉</span><br style="cursor: pointer;"/>print(login(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地恋&#34;</span>))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 欢迎: 古明地恋</span></span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">尽管函数 login 里面没有 user_name 和 password 这两个局部变量，但是不妨碍我们使用它，因为外层函数 deco 里面有。</span></p><p><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;">也就是说，函数 login 作为函数 deco 的返回值被返回的时候，有一个</span><span style="letter-spacing: 0.578px;color: rgb(0, 122, 170);">名字空间</span><span style="letter-spacing: 0.578px;">就已经和 login 紧紧地绑定在一起了。执行内层函数 login 的时候，对于自身 local 空间中不存在的变量，会从和自己绑定的 local 空间里面去找，这就是一种将名字空间静态化的方法。这个名字空间和内层函数捆绑之后的结果我们称之为闭包（closure）。</span></span></p><blockquote style="margin-top: 20px;margin-bottom: 20px;cursor: pointer;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"><p><span style="font-size: 15px;">为了描述方便，上面说的是 local 空间，但我们知道，局部变量不是从那里查找的，而是从 localsplus 里面。只是我们可以按照 LEGB 的规则去理解，这一点心理清楚就行。</span></p></blockquote><p><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;">也就是说：</span><span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">闭包=外部作用域+内层函数</span><span style="letter-spacing: 0.578px;">。并且在介绍函数的时候提到，PyFunctionObject 是虚拟机专门为字节码指令的传输而准备的大包袱，global 名字空间、默认参数都和字节码指令捆绑在一起，同样的，也包括闭包。</span></span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" data-mpa-powered-by="yiban.io" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048261" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">实现闭包的基石</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">闭包的创建通常是利用嵌套函数来完成的，我们说过局部变量是通过数组静态存储的，而闭包也是如此。这里再来回顾一下 PyCodeObject 里面的几个关键字段：</span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(0, 82, 255);">co_localsplusnames</span><span style="font-size: 15px;">：包含所有局部变量、cell 变量、free 变量的名称</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(0, 82, 255);">co_nlocalsplus</span><span style="font-size: 15px;">：co_localsplusnames 的长度，或者说这些变量的个数之和</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(172, 57, 255);">co_varnames</span><span style="font-size: 15px;">：包含所有局部变量的名称</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(172, 57, 255);">co_nlocals</span><span style="font-size: 15px;">：局部变量的个数</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(64, 118, 0);">co_cellvars</span><span style="font-size: 15px;">：包含所有 cell 变量的名称</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(64, 118, 0);">co_ncellvars</span><span style="font-size: 15px;">：cell 变量的个数</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;color: rgb(217, 33, 66);">co_freevars</span><span style="font-size: 15px;">：包含所有 free 变量的名称</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;"><span style="color: rgb(217, 33, 66);">co_nfreevars</span>：free 变量的个数</span></p><p><span style="font-size: 15px;"><br/></span></p></li></ul><p><span style="font-size: 15px;">因此不难得出它们之间的关系：</span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li style="font-size: 15px;"><p><span style="font-size: 15px;">co_localsplusnames = co_varnames + co_cellvars + co_freevars</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;">co_nlocalsplus = co_nlocals + co_ncellvars + co_nfreevars</span></p><p><br/></p></li></ul><p><span style="font-size: 15px;">那么这些变量的值都存在什么地方呢？没错就是栈帧的 localsplus 字段中。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-imgfileid="100048262" data-ratio="0.2675925925925926" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=bb37c070&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGLYPXgjg296iaURclbIPusiaic17gjF4ibRA4cEJMTyYlfVEakjajTmk10pUocibQwT4DZz5B4o65iaQug%2F640%3Fwx_fmt%3Dother%26from%3Dappmsg%26wxfrom%3D5%26wx_lazy%3D1%26wx_co%3D1%26tp%3Dwebp"/></p><p><span style="font-size: 15px;">我们看一段代码：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    name = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span><br style="cursor: pointer;"/>    age = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">17</span><br style="cursor: pointer;"/>    gender = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;female&#34;</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">bar</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">nonlocal</span> name<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">nonlocal</span> age<br style="cursor: pointer;"/>        print(gender)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> bar<br style="cursor: pointer;"/>print(foo.__code__.co_cellvars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (&#39;age&#39;, &#39;gender&#39;, &#39;name&#39;)</span><br style="cursor: pointer;"/>print(foo().__code__.co_freevars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (&#39;age&#39;, &#39;gender&#39;, &#39;name&#39;)</span><br style="cursor: pointer;"/>print(foo.__code__.co_freevars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># ()</span><br style="cursor: pointer;"/>print(foo().__code__.co_cellvars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># ()</span></span></code></pre><p><span style="font-size: 15px;">和闭包相关的两个字段是 co_cellvars 和 co_freevars。co_cellvars 保存了外层作用域中被内层作用域引用的变量的名字，co_freevars 保存了内层作用域中<span style="letter-spacing: 0.578px;">引用</span>的外层作用域的变量的名字。</span></p><p><span style="font-size: 15px;">所以对于外层函数来说，应该使用 <span style="font-size: 15px;letter-spacing: 0.578px;">co_cellvars，对于内层函数来说，应该使用 <span style="font-size: 15px;letter-spacing: 0.578px;">co_freevars。当然<span style="font-size: 15px;letter-spacing: 0.578px;">无论是外层函数还是内层函数都有 co_cellvars 和 co_freevars，这是肯定的，因为都是函数。</span></span></span></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">只不过外层函数需要使用 co_cellvars 获取，因为它包含的是外层函数中被内层函数<span style="letter-spacing: 0.578px;">引用</span>的变量的名称；内层函数需要使用 co_freevars 获取，它包含的是内层函数中<span style="letter-spacing: 0.578px;">引用</span>的外层函数的变量的名称。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">如果使用外层函数 foo 获取 co_freevars 的话，那么得到的结果显然就是个空元组了，除非 foo 也作为某个函数的内层函数，并且内部引用了外层函数的变量。同理内层函数 bar 也是一样的道理，它获取 co_cellvars 得到的也是空元组，因为对于 bar 而言不存在内层函数。</span></p><p><span style="font-size: 15px;">我们再看个例子：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    name = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;古明地觉&#34;</span><br style="cursor: pointer;"/>    age = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">17</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">bar</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">nonlocal</span> name<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">nonlocal</span> age<br style="cursor: pointer;"/>        gender = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;female&#34;</span><br style="cursor: pointer;"/>        <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">nonlocal</span> gender<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> bar<br style="cursor: pointer;"/>print(foo().__code__.co_cellvars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (&#39;gender&#39;,)</span><br style="cursor: pointer;"/>print(foo().__code__.co_freevars)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (&#39;age&#39;, &#39;name&#39;)</span></span></code></pre><p><span style="font-size: 15px;">对于函数 bar 而言，它是函数 inner 的外层函数，同时也是函数 foo 的内层函数。所以它在获取 co_cellvars 和 co_freevars 属性时，得到的元组都不为空。因为内层函数 inner 引用了函数 bar 里面的变量 gender，同时函数 bar 也作为内层函数引用了函数 foo 里的 name 和 age。</span></p><p><span style="font-size: 15px;letter-spacing: 0.578px;">那么问题来了，闭包变量所需要的空间申请在哪个地方呢？没错，显然是 localsplus。</span></p><blockquote style="margin-top: 20px;margin-bottom: 20px;cursor: pointer;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;color: rgb(0, 0, 0);font-family: Optima, &#34;Microsoft YaHei&#34;, PingFangSC-regular, serif;font-size: 16px;letter-spacing: normal;text-align: left;"><p><span style="font-size: 15px;">在以前的版本中，这个字段叫 f_localsplus，现在叫 localsplus。</span></p></blockquote><p><span style="font-size: 15px;letter-spacing: 0.578px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;">localplus 是一个柔性数组，它被分成了四份，分别用于：局部变量、cell 变量、free 变量、运行时栈。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">所以闭包变量同样是以静态的方式实现的。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgba(0, 0, 0, 0);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048263" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">闭包的实现过程</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><span style="letter-spacing: 0.578px;font-size: 15px;">介绍完实现闭包的基石之后，我们可以开始追踪闭包的具体实现过程了，当然还是要先看一下闭包对应的字节码。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> dis<br style="cursor: pointer;"/>code_string = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>def some_func():<br style="cursor: pointer;"/>    name = &#34;satori&#34;<br style="cursor: pointer;"/>    age = 17<br style="cursor: pointer;"/>    gender = &#34;female&#34;<br style="cursor: pointer;"/>    def inner():<br style="cursor: pointer;"/>        print(name, age)<br style="cursor: pointer;"/>    return inner<br style="cursor: pointer;"/>func = some_func()<br style="cursor: pointer;"/>func()<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/>dis.dis(compile(code_string, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&lt;file&gt;&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;exec&#34;</span>))</span></code></pre><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;background-color: rgb(255, 255, 255);visibility: visible;"><span style="font-size: 15px;">字节码指令如下，为了阅读方便，我们省略了源代码行号。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># ********** 模块对应的字节码 **********</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> RESUME                   <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载函数 some_func 对应的 PyCodeObject，压入运行时栈</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (&lt;code object some_func at ...&gt;)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 从栈顶弹出 PyCodeObject，构造 PyFunctionObject，并压入运行时栈</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> MAKE_FUNCTION            <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 从栈顶弹出 PyFunctionObject，然后使用变量 some_func 保存</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span> STORE_NAME               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (some_func)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">8</span> PUSH_NULL<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载全局变量 some_func</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span> LOAD_NAME                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (some_func)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">12</span> CALL                     <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 弹出栈顶的返回值，并使用变量 func 保存</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">20</span> STORE_NAME               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (func)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">22</span> PUSH_NULL<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载全局变量 func</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">24</span> LOAD_NAME                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (func)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">26</span> CALL                     <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 从栈顶弹出返回值，丢弃</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">34</span> POP_TOP<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 隐式地 return None</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">36</span> RETURN_CONST             <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (<span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">None</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># ********** 外层函数 some_func 对应的字节码 **********</span><br style="cursor: pointer;"/>Disassembly of &lt;code object some_func at ...&gt;:<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 创建 cell 对象 PyCellObject，该指令一会儿说</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> MAKE_CELL                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> (age)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> MAKE_CELL                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span> (name)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> RESUME                   <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载常量 &#34;satori&#34;</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#39;satori&#39;</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 注意这里不是 STORE_FAST，而是 STORE_DEREF</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 它的作用肯定是将符号 &#34;name&#34; 和字符串常量绑定起来</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># STORE_NAME、STORE_FAST、STORE_DEREF 做的事情是一样的</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 都是将符号和值绑定起来，只是绑定的方式不一样</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 比如 STORE_NAME 是通过字典完成绑定，STORE_FAST 是通过数组完成绑定</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 那么 STORE_DEREF 是怎么绑定的呢？稍后分析  </span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">8</span> STORE_DEREF              <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span> (name)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载常量 17</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> (<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">17</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 使用变量 age 保存</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">12</span> STORE_DEREF              <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> (age)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># name 和 age 被内层函数引用了，所以是 STORE_DEREF</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 但 gender 没有，所以它对应的是 STORE_FAST</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">14</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span> (<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#39;female&#39;</span>)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">16</span> STORE_FAST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (gender)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载 cell 变量，压入运行时栈</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">18</span> LOAD_CLOSURE             <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> (age)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">20</span> LOAD_CLOSURE             <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span> (name)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 弹出 cell 变量，构建元组</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">22</span> BUILD_TUPLE              <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载函数 inner 对应的 PyCodeObject</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">24</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> (&lt;code object inner at ...&gt;)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 构造函数</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">26</span> MAKE_FUNCTION            <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">8</span> (closure)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 将函数使用 inner 变量保存</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">28</span> STORE_FAST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (inner)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># return inner</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">30</span> LOAD_FAST                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (inner)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">32</span> RETURN_VALUE<br style="cursor: pointer;"/> <br style="cursor: pointer;"/> <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># ********** 内层函数 inner 对应的字节码 **********</span><br style="cursor: pointer;"/>Disassembly of &lt;code object inner at ...&gt;:<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> COPY_FREE_VARS           <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> RESUME                   <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载内置变量 print</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> LOAD_GLOBAL              <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (NULL + <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">print</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 显然它和 LOAD_NAME、LOAD_FAST 的关系也是类似的</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 也是负责加载变量，然后压入运行时栈  </span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">14</span> LOAD_DEREF               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (name)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">16</span> LOAD_DEREF               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (age)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 调用 print 函数</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">18</span> CALL                     <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 从栈顶弹出返回值，丢弃</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">26</span> POP_TOP<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">28</span> RETURN_CONST             <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (<span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">None</span>)</span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">字节码的内容并不难，我们来分析一下，这里先分析</span><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">外层函数 some_func 对应的字节码。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048264" data-ratio="0.6222222222222222" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=169e7145&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGGonEy0LqLUq4jiajrS21ibseAn8qYUoeqftGT18UcnywsbJ0OsuJnyEeTDlzo3qGYlDyXPD7kabwg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">函数 some_func 里面有三个局部变量，但只有 name 和 age 被内层函数引用了，所以开头有两个 MAKE_CELL 指令。参数为符号在符号表中的索引，对应的符号分别为 age 和 name。我们来看一下这个指令是做什么的。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(0, 252, 255);">TARGET</span>(MAKE_CELL) {<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1394 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 符号在符号表中的索引，和对应的值在 localplus 中的索引是一致的</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以这里会获取变量对应的值，对于当前来说就是 age 和 name 的值</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 但很明显此时 name 和 age 还没有完成赋值，所以结果为 NULL</span><br style="cursor: pointer;"/>    PyObject *initial = GETLOCAL(oparg);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 调用 PyCell_New 创建 Cell 对象</span><br style="cursor: pointer;"/>    PyObject *cell = PyCell_New(initial);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (cell == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) {<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> resume_with_error;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 用 Cell 对象替换掉原来的值</span><br style="cursor: pointer;"/>    SETLOCAL(oparg, cell);<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1909 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// Objects/cellobject.c</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;">PyObject *<br style="cursor: pointer;"/><span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">PyCell_New</span><span style="cursor: pointer;line-height: 26px;">(PyObject *obj)</span><br style="cursor: pointer;"/></span>{<br style="cursor: pointer;"/>    PyCellObject *op;<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 为 PyCellObject 申请内存</span><br style="cursor: pointer;"/>    op = (PyCellObject *)PyObject_GC_New(PyCellObject, &amp;PyCell_Type);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (op == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>)<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>;<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 增加 obj 指向对象的引用计数，并赋值给 op-&gt;ob_ref</span><br style="cursor: pointer;"/>    op-&gt;ob_ref = Py_XNewRef(obj);<br style="cursor: pointer;"/>    _PyObject_GC_TRACK(op);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> (PyObject *)op;<br style="cursor: pointer;"/>}<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// Include/cpython/cellobject.h</span><br style="cursor: pointer;"/><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">typedef</span> <span style="cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">struct</span> {</span><br style="cursor: pointer;"/>    PyObject_HEAD<br style="cursor: pointer;"/>    PyObject *ob_ref;<br style="cursor: pointer;"/>} PyCellObject;</span></code></pre><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">所以 MAKE_CELL 指令的作用是创建 PyCellObject，对于当前来说，会创建两个 PyCellObejct，它们的 ob_ref 字段分别为 age 和 name。只不过由于 name 和 age 还尚未完成赋值，所以此时为 NULL。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048267" data-ratio="0.7824074074074074" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=e9d2d3e5&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGGonEy0LqLUq4jiajrS21ibs3CfFcKs4uYZplMtYNmyFC6cJgG0lfXw4De9JXEGSz6Lb1qpWhJ7ORg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;font-size: 15px;">接下来就是变量赋值，这个显然没什么难度，我们只需要看一下 STORE_DEREF 指令。并且也<span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;">容易得出结论，如果局部变量被内层函数所引用，那么指令将不再是 LOAD_FAST 和 STORE_FAST，而是 LOAD_DEREF 和 STORE_DEREF。</span></span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;color: rgb(0, 252, 255);">TARGET</span><span style="font-size: 15px;">(STORE_DEREF) {<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 由于在 STORE_DEREF 之前调用了 LOAD_CONST</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以这里会获取上一步压入的常量，对于当前来说就是 17 和 &#34;satori&#34;</span><br style="cursor: pointer;"/>    PyObject *v = stack_pointer[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>];<br style="cursor: pointer;"/>    <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1463 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 这里的 oparg 和 MAKE_CELL 的 oparg 的含义是一样的</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 此时会拿到对应的 PyCellObject *</span><br style="cursor: pointer;"/>    PyObject *cell = GETLOCAL(oparg);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取 PyCellObject 内部的 ob_ref，并减少引用计数</span><br style="cursor: pointer;"/>    PyObject *oldobj = PyCell_GET(cell);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将 PyCellObject 内部的 ob_ref 设置成 v</span><br style="cursor: pointer;"/>    PyCell_SET(cell, v);<br style="cursor: pointer;"/>    Py_XDECREF(oldobj);<br style="cursor: pointer;"/>    <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1993 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 栈收缩</span><br style="cursor: pointer;"/>    STACK_SHRINK(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>);<br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span></code></pre><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">localplus 保存了局部变量的值，而符号在符号表中的索引，和对应的值在 localplus 中的索引是一致的。所以正常情况下，局部变量赋值就是 </span><span style="color: rgb(0, 82, 255);font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">localsplus[oparg] = v</span><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">。</span></p><p><span style="font-size: 15px;letter-spacing: 0.51px;">但在执行 MAKE_CELL 指令之后，局部变量赋值就变成了 <span style="color: rgb(0, 82, 255);letter-spacing: 0.51px;">localsplus[oparg]-&gt;ob_ref = v</span>，因为此时 localplus 保存的是 PyCellObject 的地址。<br/></span></p><p><span style="font-size: 15px;letter-spacing: 0.51px;">因此在两个 STORE_DEREF 执行完之后，localplus 会变成下面这样。<br/></span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048268" data-ratio="0.49074074074074076" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=94354f4a&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGGonEy0LqLUq4jiajrS21ibsibNoVwr6V8z8hiaxqh1xicLoc97xKhJMrtACefJWml56Xia9ndHx1yQNnA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">相信你明白 STORE_FAST 和 STORE_DEREF 之间的区别了，如果是 STORE_FAST，那么中间就没有 PyCellObject 这一层，localsplus 保存的 PyObject * 指向的就是具体的对象。</span></p><p><span style="font-size: 15px;">然后是 <span style="color: rgb(0, 82, 255);">gender = &#34;female&#34;</span>，它就很简单了，由于符号 &#34;gender&#34; 在符号表中的索引为 0，那么直接让 localplus[0] 指向字符串 &#34;female&#34; 即可。</span></p><p><span style="font-size: 15px;">到此变量 name、age、gender 均已赋值完毕，此时 localsplus 结构如下。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048269" data-ratio="0.487962962962963" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=ebe91253&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGGonEy0LqLUq4jiajrS21ibs0Q0ib5cUHJ3BgrVIHbhcQWCP60DxibP4FP30JldC22eRZUAIB61SVgUw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">localsplus[0]、localsplus[2]、localsplus[3] 分别对应变量 gender、age、name，可能有人觉得，这个索引好奇怪啊，我们实际测试一下。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">some_func</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    name = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span><br style="cursor: pointer;"/>    age = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">17</span><br style="cursor: pointer;"/>    gender = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;female&#34;</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        print(name, age)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/>print(<br style="cursor: pointer;"/>    some_func.__code__.co_varnames<br style="cursor: pointer;"/>)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># (&#39;gender&#39;, &#39;inner&#39;)</span></span></code></pre><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">我们看到 some_func 的符号表里面只有 gender 和 inner，因此 localplus[0] 表示变量 gender。至于 localplus[1] 则表示变量 inner，只不过此时它指向的对象还没有创建，所以暂时为 NULL。</span></p><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">那</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;font-size: 15px;">么问题来了，变量 name 和 age 呢？毫无疑问，由于它们被内层函数引用了，所以它们变成了 cell 变量，并且位置是 <span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;color: rgb(0, 82, 255);letter-spacing: 0.578px;">co-&gt;co_nlocals + i</span>。因为在 <span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.578px;">localsplus 中，cell 变量的位置是在局部变量之后的，这也完全符合我们之前说的 localsplus 的内存布局。</span></span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048270" data-ratio="0.6185185185185185" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=85a204ee&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsGGonEy0LqLUq4jiajrS21ibsFA9jgdFOKSa1RN32fF7nSSCjzVTPG7ua7HXtRG7VYNV119DibJpCryg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">并且我们看到无论是局部变量还是 cell 变量，都是通过数组索引访问的，并且索引在编译时就确定了，以指令参数的形式保存在字节码指令集中。</span></p><p><span style="font-size: 15px;font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;">接下来执行偏移量为 18 和 20 的两条指令，它们都是 LOAD_CLOSURE。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 加载 PyCellObject *，即 cell 变量，然后压入运行时栈<br style="cursor: pointer;"/></span><span style="font-size: 15px;color: rgb(0, 252, 255);">TARGET</span><span style="font-size: 15px;">(LOAD_CLOSURE) {<br style="cursor: pointer;"/>    PyObject *value;<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 179 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    value = GETLOCAL(oparg);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (value == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> unbound_local_error;<br style="cursor: pointer;"/>    Py_INCREF(value);<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 66 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    STACK_GROW(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>);<br style="cursor: pointer;"/>    stack_pointer[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>] = value;<br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span></code></pre><p><span style="font-size: 15px;">LOAD_CLOSURE 执行完毕后，接着执行 BUILD_TUPLE，将 cell 变量从栈中弹出，构建元组。然后继续执行 24 LOAD_CONST，将内层函数 inner 对应的 PyCodeObject 压入运行时栈。</span></p><p><span style="font-size: 15px;">接着执行 26 MAKE_FUNCTION，将栈中元素弹出，分别是 inner 对应的 PyCodeObject 和一个元组，元组里面包含了 inner 使用的外层函数的变量。当然这里的变量已经不再是普通的变量了，而是 cell 变量，它内部的 ob_ref 字段才是我们需要的。</span><br/></p><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">等元素弹出之后，开始构建函数，</span>我们看一下 MAKE_FUNCTION 指令，它的指令参数为 8。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">TARGET(MAKE_FUNCTION) {<br style="cursor: pointer;"/>    PyObject *codeobj = stack_pointer[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>];<br style="cursor: pointer;"/>    PyObject *closure = (oparg &amp; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x08</span>) ? stack_pointer[...] : <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>;<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 创建函数对象</span><br style="cursor: pointer;"/>    PyFunctionObject *func_obj = (PyFunctionObject *)<br style="cursor: pointer;"/>        PyFunction_New(codeobj, GLOBALS());<br style="cursor: pointer;"/>    Py_DECREF(codeobj);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (func_obj == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) {<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> error;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 由于指令参数为 8，所以 oparg &amp; 0x08 为真</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (oparg &amp; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x08</span>) {<br style="cursor: pointer;"/>        assert(PyTuple_CheckExact(closure));<br style="cursor: pointer;"/>        func_obj-&gt;func_closure = closure;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (oparg &amp; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x04</span>) {<br style="cursor: pointer;"/>        assert(PyTuple_CheckExact(annotations));<br style="cursor: pointer;"/>        func_obj-&gt;func_annotations = annotations;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (oparg &amp; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x02</span>) {<br style="cursor: pointer;"/>        assert(PyDict_CheckExact(kwdefaults));<br style="cursor: pointer;"/>        func_obj-&gt;func_kwdefaults = kwdefaults;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (oparg &amp; <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x01</span>) {<br style="cursor: pointer;"/>        assert(PyTuple_CheckExact(defaults));<br style="cursor: pointer;"/>        func_obj-&gt;func_defaults = defaults;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// ...</span><br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span><br style="cursor: pointer;"/></code></pre><p><span style="font-size: 15px;">所以 PyFunctionObject 再一次承担了工具人的角色，创建内层函数 inner 时，会将包含 cell 变量的元组赋值给 func_closure 字段。<span style="letter-spacing: 0.578px;">此时便将内层函数需要使用的变量和内层函数绑定在了一起，而这个绑定的结果我们就称之为闭包。</span><br/></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">但是从结构上来看，闭包仍是一个函数，所谓绑定，其实只是修改了它的 func_closure 字段。当函数创建完毕后，localplus 的结构变化如下。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048271" data-ratio="0.850925925925926" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=dcae34cd&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw6be6O8ViblDOib9r8jBYyjHoQHsQiclk8Ipve0MHvLkk6mH4cCiacn31z4g%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">函数即变量，对于函数 some_func 而言，内层函数 inner 也是一个局部变量，由于符号 inner 位于符号表中索引为 1 的位置。因此当函数创建完毕时，会修改 localplus[1]，让它保存函数的地址。不难发现，对于局部变量来说，如何访问内存在编译阶段就确定了。</span><br/></p><p><span style="font-size: 15px;">函数内部的 func_closure 字段指向一个元组，元组里面的每个元素会指向 PyCellObject。<br/></span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgba(0, 0, 0, 0);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048272" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">调用闭包</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">闭包的创建过程我们已经了解了，我们用 Python 代码再解释一下。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">some_func</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    name = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;satori&#34;</span><br style="cursor: pointer;"/>    age = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">17</span><br style="cursor: pointer;"/>    gender = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;female&#34;</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">inner</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>        print(name, age)<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">return</span> inner<br style="cursor: pointer;"/>func = some_func()<br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># some_func 调用之后会返回内层函数 inner</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 只不过 inner 的 func_closure 字段保存了 cell 变量</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 而 cell 变量指向的 PyCellObject 对外层作用域的局部变量进行了冻结</span><br style="cursor: pointer;"/><span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 所以我们也会称呼 inner 函数为闭包，但要知道闭包仍然是个函数</span><br style="cursor: pointer;"/>print(func.__name__)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># inner</span><br style="cursor: pointer;"/>print(func.__class__)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;class &#39;function&#39;&gt;</span><br style="cursor: pointer;"/>print(<br style="cursor: pointer;"/>    func.__closure__[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>]<br style="cursor: pointer;"/>)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;cell at 0x102019390: int object at 0x1000b02f0&gt;</span><br style="cursor: pointer;"/>print(<br style="cursor: pointer;"/>    func.__closure__[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>]<br style="cursor: pointer;"/>)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;cell at 0x1002126e0: str object at 0x1001f0b70&gt;</span><br style="cursor: pointer;"/>print(func.__closure__[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>].cell_contents)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 17</span><br style="cursor: pointer;"/>print(func.__closure__[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>].cell_contents)  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># satori</span></span><br style="cursor: pointer;"/></code></pre><p><span style="font-size: 15px;">调用 inner 函数时，外层函数 some_func 已经执行结束，但它的局部变量 name 和 age 仍可被内层函数 inner 访问，背后的原因我们算是彻底明白了。</span></p><p><span style="font-size: 15px;">因为 name 和 age 被内层函数引用了，所以虚拟机将它们封装成了 PyCellObject *，即 cell 变量，而 cell 变量指向的 cell 对象内部的 ob_ref 字段对应原来的变量。当创建内层函数时，将引用的 cell 变量组成元组，保存在内层函数的 func_closure 字段中。</span></p><p><span style="font-size: 15px;">所以当内层函数在访问 name 和 age 时，访问的其实是 <span style="letter-spacing: 0.578px;">PyCellObject 的 ob_ref 字段。至于变量 name 和 age 对应哪一个 <span style="letter-spacing: 0.578px;">PyCellObject，这些在编译阶段便确定了，我们看一下内层函数 inner 的字节码指令。</span></span></span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048273" data-ratio="0.4185185185185185" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=c79f61d3&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw6EGBloibgt9GbXcicJ13Dic8PJPU4QrDYPEh34jgu2sIAytugtuMrO3QVw%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">函数在执行时会创建栈帧，我们上面看到的 localsplus 是外层函数 some_func 对应的栈帧的 <span style="letter-spacing: 0.578px;">localsplus。而内层函数 inner 执行时，也会创建栈帧，然后在栈帧中执行字节码指令。</span></span></p><p><span style="font-size: 15px;letter-spacing: 0.578px;">首先第一个指令是 COPY_FREE_VARS，看一下它的逻辑。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 14px;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将 func_closure 里面的 cell 变量拷贝到 free 区域<br style="cursor: pointer;"/></span><span style="font-size: 14px;color: rgb(0, 252, 255);">TARGET</span><span style="font-size: 14px;">(COPY_FREE_VARS) {<br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1470 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">/* Copy closure variables to free variables */</span><br style="cursor: pointer;"/>    PyCodeObject *co = frame-&gt;f_code;<br style="cursor: pointer;"/>    assert(PyFunction_Check(frame-&gt;f_funcobj));<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取 func_closure，它指向一个元组，里面保存了 PyCellObject *</span><br style="cursor: pointer;"/>    PyObject *closure = ((PyFunctionObject *)frame-&gt;f_funcobj)-&gt;func_closure;<br style="cursor: pointer;"/>    assert(oparg == co-&gt;co_nfreevars);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// co_nlocalsplus 等于局部变量、cell 变量、free 变量的个数之和</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 显然 offset 表示 free 变量对应的内存区域</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> offset = co-&gt;co_nlocalsplus - oparg;<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将 func_closure 里面的 PyCellObject * 拷贝到 free 区域</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> (<span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> i = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>; i &lt; oparg; ++i) {<br style="cursor: pointer;"/>        PyObject *o = PyTuple_GET_ITEM(closure, i);<br style="cursor: pointer;"/>        frame-&gt;localsplus[offset + i] = Py_NewRef(o);<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 2010 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span></code></pre><p><span style="font-size: 15px;letter-spacing: 0.578px;">处理完之后，localplus 的布局如下，注意：此时是内层函数对应的 localplus。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048276" data-ratio="0.7925925925925926" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=c48a56c8&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw64ZIKjiaRjpDnKUECQMh73UqicGJp3QYIM3u92bVpXZyzib1lO8FRpERiaA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">在构建内层函数时，会将 cell 变量打包成一个元组，交给内层函数的 func_closure 字段。然后执行内层函数创建栈帧的时候，再将 func_closure 中的 cell 变量拷贝到 localsplus 的第三段内存中。当然对于内层函数而言，此时它应该叫做 free 变量。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">而在调用内层函数 inner 的过程中，当引用外层作用域的符号时，一定是到 localsplus 里面的 free 区域（第三段内存）去获取对应的 <span style="letter-spacing: 0.578px;color: rgb(0, 82, 255);">PyCellObject *</span>，然后通过内部的 ob_ref 进而获取符号对应的值。至于 name 和 age 分别对应哪一个 PyCellObject，这些都体现在字节码指令参数当中了。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">然后我们再来看看 free 变量是如何加载的，它由 LOAD_DEREF 指令完成。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;color: rgb(0, 252, 255);">TARGET</span><span style="font-size: 15px;">(LOAD_DEREF) {<br style="cursor: pointer;"/>    PyObject *value;<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1453 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 加载 PyCellObject *</span><br style="cursor: pointer;"/>    PyObject *cell = GETLOCAL(oparg);<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取 PyCellObject 对象的 ob_ref 字段的值</span><br style="cursor: pointer;"/>    value = PyCell_GET(cell);<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (value == <span style="font-size: 15px;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) {<br style="cursor: pointer;"/>        format_exc_unbound(tstate, frame-&gt;f_code, oparg);<br style="cursor: pointer;"/>        <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (<span style="font-size: 15px;color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">true</span>) <span style="font-size: 15px;color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> error;<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    Py_INCREF(value);<br style="cursor: pointer;"/>    <span style="font-size: 15px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 1980 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    STACK_GROW(<span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>);<br style="cursor: pointer;"/>    stack_pointer[<span style="font-size: 15px;color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>] = value;<br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span></code></pre><p><span style="letter-spacing: 0.578px;font-size: 15px;">这里再补充一点，我们说 localplus 是一个连续的数组，只是按照用途被划分成了四个区域：保存局部变量的内存空间、<span style="font-size: 15px;letter-spacing: 0.578px;">保存 cell 变量的内存空间、<span style="font-size: 15px;letter-spacing: 0.578px;">保存 free 变量的内存空间、运行时栈。</span></span></span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;"><span style="font-size: 15px;letter-spacing: 0.578px;">但对于当前的内层函数 inner 来说，它是没有局部变量和 cell 变量的，所以 localsplus 开始的位置便是 free 区域。</span></span></span></p><p><span style="font-size: 15px;">当然不管是局部变量、cell 变量，还是 free 变量，它们都按照顺序保存在 localplus 中，并且在编译阶段便知道它们在 localsplus 中的位置。比如我们将内层函数 inner 的逻辑修改一下。</span><br/></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048278" data-ratio="0.5555555555555556" data-s="300,640" style="" data-type="png" data-w="810" src="https://wechat2rss.xlab.app/img-proxy/?k=3023a8ae&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw63azXK5AeovS20CYGyORNwYfvcNOlFO6ru1Uko4y1fN0bQECJCIb4Wg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">在 inner 里面创建了三个局部变量，那么它的字节码会变成什么样子呢？这里我们直接看 print 函数执行时的字节码即可。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048280" data-ratio="0.46641074856046066" data-s="300,640" style="" data-type="png" data-w="1042" src="https://wechat2rss.xlab.app/img-proxy/?k=f6456ea5&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw6g4ic01apwqybfAX6EKFI5WJoj4Uic9ArBymibYoxMqX7r8lJgR1jaOKYA%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">因为 inner 里面没有函数了，所以它不存在 cell 变量，里面只有局部变量和 free 变量。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048281" data-ratio="0.25555555555555554" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=4dcd84b9&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsG3ap7tLpta47SOFRxohfw6jGc5CoIDceB7ic5ObGiazdXSp2ib6iaCbhoHeFZpERzwqr9XuL8u5vp0Fg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.034em;font-size: 15px;">所以虽然我们说 localplus 被分成了四份，但是 cell 区域和 free 区域很少会同时存在。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">对于外层函数 some_func 来说，它没有 free 变量，所以 free 区域长度为 0。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">而对于内层函数 inner 来说，它没有 cell 变量，所以</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;font-size: 15px;"> cell 区域长度为 0</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">。</span><span style="font-size: 15px;"></span></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">只有函数的里面存在内层函数，并且外面存在外层函数，那么它才有可能同时包含 cell 变量和 free 变量。</span></p><p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">但为</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">了方便描述，我们仍然认为 localplus 被分成了四个区域，只不过对于外层函数 some_func 而言，它的 free 区域长度为 0；对于 inner 函数而言，它的 cell 区域长度为 0。</span></p><p style="letter-spacing: 0.578px;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">当然这些都是概念上的东西，大家理解就好。但</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">不管在</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;font-size: 15px;">概念上</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;"> localplus 怎么划分，它本质上就是一个 C 数组，是一段连续的内存，</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">用于存储局部变量、cell 变量、free 变量（</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: 15px;letter-spacing: 0.034em;">这三种变量不一定同时存在），</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.578px;font-size: 15px;">以及作为运行时栈。</span></p><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">最重要的是，这三种变量都是基于数组实现的静态访问，并且怎么访问在编译阶段就已经确定，因为访问数组的索引会作为指令参数存储在字节码指令集中。</span></span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li><p><span style="font-size: 15px;"><span style="font-size: 15px;letter-spacing: 0.578px;">比如访问变量 a，底层会<span style="font-size: 15px;letter-spacing: 0.578px;">访问</span> localplus[0]；</span></span></p></li><li><p><span style="font-size: 15px;">比如<span style="font-size: 15px;letter-spacing: 0.578px;">访问</span>变量 age，底层会<span style="font-size: 15px;letter-spacing: 0.578px;">访问</span> <span style="font-size: 15px;letter-spacing: 0.578px;">localplus</span>[3]-&gt;ob_ref；</span></p><p><span style="font-size: 15px;"></span></p></li></ul><p><span style="letter-spacing: 0.578px;font-size: 15px;">这便是静态访问。</span></p><p><span style="font-size: 15px;"><br/></span></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgba(0, 0, 0, 0);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048282" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">小结</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="font-size: 15px;">本篇文章我们就介绍了闭包，比想象中的要更加简单。因为闭包仍是一个函数，只是将外层作用域的局部变量变成了 cell 变量，然后保存在内部的 func_closure 字段中。</span></p><p><span style="font-size: 15px;">然后执行内层函数的时候，再将 func_closure 里的 PyCellObject * 拷贝到 localplus 的 free 区域，此时我们叫它 free 变量。但不管什么变量，虚拟机在编译时便知道应该如何访问指定的内存。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247531936">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=5db71994&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531936%26idx%3D1%26sn%3D3894ab282272cdd7b7b1332a4ba9be32%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Mon, 25 Nov 2024 10:43:00 +0800</pubDate>
    </item>
    <item>
      <title>函数在底层是如何调用的？</title>
      <link>https://mp.weixin.qq.com/s?__biz=Mzg3NTczMDU2Mg==&amp;mid=2247531935&amp;idx=1&amp;sn=0a539f3f01f03d5e918fd2f192e352b5</link>
      <description></description>
      <content:encoded><![CDATA[<p>
原创 <span>古明地觉</span> <span>2024-11-19 10:29</span> <span style="display: inline-block;">北京</span>
</p>

<p></p>



<p>
<img src="https://wechat2rss.xlab.app/img-proxy/?k=4521e7ba&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_jpg%2Fib8ibwulXslsH7CzLCWENWGwymlGAOZ7PLPfuA1WvjyzibgqcKKxAhu5LkBiclgjokVce02sCwD0T8FIJUWUfzlZ2Q%2F0%3Fwx_fmt%3Djpeg"/>
</p>


<section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048250" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">楔子</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="font-size: 15px;letter-spacing: 0.578px;text-align: left;">上一篇文章我们说了 Python 函数的底层实现，并且还演示了如何通过函数的类型对象自定义一个函数，以及如何获取函数的参数。虽然这在工作中没有太大意义，但是可以让我们深刻理解函数的行为。</span></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">那么接下来看看函数是如何调用的。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-imgfileid="100048252" data-ratio="2.1153846153846154" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" data-type="png" data-w="52" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">PyCFunctionObject</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;font-size: 15px;">在介绍调用之前，我们需要补充一个知识点。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">()</span>:</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">pass</span><br style="cursor: pointer;"/><span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">class</span> <span style="color: rgb(230, 192, 123);cursor: pointer;line-height: 26px;">A</span>:</span><br style="cursor: pointer;"/>    <span style="font-size: 15px;cursor: pointer;line-height: 26px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">def</span> <span style="color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">foo</span><span style="cursor: pointer;line-height: 26px;">(self)</span>:</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">pass</span><br style="cursor: pointer;"/>print(type(foo))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;class &#39;function&#39;&gt;</span><br style="cursor: pointer;"/>print(type(A().foo))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;class &#39;method&#39;&gt;</span><br style="cursor: pointer;"/>print(type(sum))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;class &#39;builtin_function_or_method&#39;&gt;</span><br style="cursor: pointer;"/>print(type(<span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;</span>.join))  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># &lt;class &#39;builtin_function_or_method&#39;&gt;</span></span><br style="cursor: pointer;"/></code></pre><p><span style="font-size: 15px;">如果采用 Python 实现，那么函数的类型是 function，方法的类型是 method。而如果采用原生的 C 实现，那么函数和方法的类型都是 builtin_function_or_method。</span><br/></p><p><span style="font-size: 15px;">关于方法，等我们介绍类的时候再说，先来看看函数。</span></p><p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100048253" data-ratio="0.6925925925925925" data-s="300,640" style="" data-type="png" data-w="1080" src="https://wechat2rss.xlab.app/img-proxy/?k=bbf74abd&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fsz_mmbiz_png%2Fib8ibwulXslsH7CzLCWENWGwymlGAOZ7PLeJDZ0oFeXxNEI6GSzOVWMQiapHGlsKE1q0ibL2Yyia0UFk13BDQFicX2Fg%2F640%3Fwx_fmt%3Dpng%26from%3Dappmsg"/></p><p><span style="font-size: 15px;">所以函数分为两种：</span></p><ul class="list-paddingleft-1" style="list-style-type: disc;"><li style="font-size: 15px;"><p><span style="font-size: 15px;">Python 实现的函数，在底层由 PyFunctionObject 结构体实例表示，其类型对象 <span style="color: rgb(0, 82, 255);">&lt;class &#39;function&#39;&gt;</span> 在底层由 PyFunction_Type 表示。</span></p></li><li style="font-size: 15px;"><p><span style="font-size: 15px;">C 实现的函数（还有方法），在底层由 PyCFunctionObject 结构体实例表示，其类型对象 <span style="color: rgb(0, 82, 255);">&lt;class &#39;builtin_function_or_method&#39;&gt;</span> 在底层由 PyCFunction_Type 表示。</span></p><p><br/></p></li></ul><p><span style="letter-spacing: 0.578px;text-align: left;font-size: 15px;">像我们使用 def 关键字定义的就是 Python 实现的函数，而内置函数则是 C 实现的函数，它们在底层对应不同的结构，因为 C 实现的函数可以有更快的执行方式。</span></p><p><br/></p><section data-mpa-template="t" mpa-from-tpl="t" style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;width: 578px;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: flex-start;align-items: center;flex-direction: column;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="outline: 0px;display: flex;justify-content: center;align-items: center;visibility: visible;"><section data-mid="" mpa-from-tpl="t" style="margin-right: -36px;outline: 0px;width: 26px;height: 55px;z-index: 1;visibility: visible;"><img class="rich_pages wxw-img" data-ratio="2.1153846153846154" data-imgfileid="100048254" data-type="png" data-w="52" style="outline: 0px;display: block;visibility: visible !important;width: 52px !important;" src="https://wechat2rss.xlab.app/img-proxy/?k=7c976e7e&amp;u=https%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2FHlNkQjetfwiaeG2eibibS4RBQY8AFicia9q36jicvERnwdOiatCCicr8H3m5do4ZANHLuqF8yiawknQEaR6LFmL7e97iazfA%2F640%3Fwx_fmt%3Dpng"/></section><section data-mid="" mpa-from-tpl="t" style="outline: 0px;width: 45px;height: 45px;background: rgba(255, 238, 241, 0.56);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -12px;outline: 0px;text-align: center;visibility: visible;"><p data-mid="" style="padding-right: 12px;padding-left: 18px;outline: 0px;font-family: PingFangSC-Medium, &#34;PingFang SC&#34;;font-weight: bold;line-height: 22px;border-bottom: 1px solid rgb(109, 85, 75);visibility: visible;"><span style="outline: 0px;color: rgb(217, 33, 66);visibility: visible;font-size: 20px;">函数的调用</span></p></section><section data-mid="" mpa-from-tpl="t" style="margin-left: -8px;outline: 0px;width: 9px;height: 9px;background: rgba(255, 238, 241, 0.88);border-radius: 50%;visibility: visible;"><br style="outline: 0px;visibility: visible;"/></section></section></section></section></section><p style="margin-bottom: 0px;outline: 0px;color: rgb(34, 34, 34);font-family: -apple-system, BlinkMacSystemFont, &#34;Helvetica Neue&#34;, &#34;PingFang SC&#34;, &#34;Hiragino Sans GB&#34;, &#34;Microsoft YaHei UI&#34;, &#34;Microsoft YaHei&#34;, Arial, sans-serif;letter-spacing: 0.544px;caret-color: rgba(0, 0, 0, 0);background-color: rgb(255, 255, 255);visibility: visible;"><br mpa-from-tpl="t" style="letter-spacing: 0.544px;outline: 0px;visibility: visible;"/></p><p><span style="letter-spacing: 0.578px;text-align: left;font-size: 15px;">我们来调用一个函数，看看它的字节码是怎样的。</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">import</span> dis <br style="cursor: pointer;"/>code_string = <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&#34;&#34;<br style="cursor: pointer;"/>def foo(a, b):<br style="cursor: pointer;"/>    return a + b<br style="cursor: pointer;"/>foo(1, 2)<br style="cursor: pointer;"/>&#34;&#34;&#34;</span><br style="cursor: pointer;"/>dis.dis(compile(code_string, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;&lt;file&gt;&#34;</span>, <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;exec&#34;</span>))</span></code></pre><p style="text-align: left;"><span style="font-size: 15px;">字节码指令如下：</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 15px;">  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> RESUME                   <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载 PyCodeObject 对象，压入运行时栈</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (&lt;code object foo at <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x7f</span>...&gt;)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 从栈顶弹出 PyCodeObject 对象，构建函数</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> MAKE_FUNCTION            <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 将符号 foo 和函数对象绑定起来，存储在名字空间中</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span> STORE_NAME               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (foo)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">8</span> PUSH_NULL<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载全局变量 foo，压入运行时栈</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span> LOAD_NAME                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (foo)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载常量 1，压入运行时栈</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">12</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 加载常量 2，压入运行时栈</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">14</span> LOAD_CONST               <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> (<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>)<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 弹出 foo 和参数，进行调用</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 指令参数 2，表示给调用的函数传递了两个参数</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 函数调用结束后，将返回值压入栈中</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">16</span> CALL                     <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span><br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 因为没有用变量保存，所以从栈顶弹出返回值并丢弃</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">24</span> POP_TOP<br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 隐式的 return None</span><br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">26</span> RETURN_CONST             <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span> (<span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">None</span>)<br style="cursor: pointer;"/>  <br style="cursor: pointer;"/>  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;"># 函数内部逻辑对应的字节码，比较简单，就不说了</span><br style="cursor: pointer;"/>Disassembly of &lt;code object foo at <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0x7f6</span>...&gt;:<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> RESUME                   <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span><br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> LOAD_FAST                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (a)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">4</span> LOAD_FAST                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> (b)<br style="cursor: pointer;"/>  <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">6</span> BINARY_OP                <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span> (+)<br style="cursor: pointer;"/> <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">10</span> RETURN_VALUE</span></code></pre><p style="text-align: left;"><span style="font-size: 15px;">我们看到函数调用使用的是 CALL 指令，那么这个指令都做了哪些事情呢？</span></p><pre style="font-size: 16px;font-family: SFMono-Regular, Consolas, &#34;Liberation Mono&#34;, Menlo, Courier, monospace;margin-top: 10px;margin-bottom: 10px;overflow: auto;cursor: pointer;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;text-align: left;color: rgb(0, 0, 0);letter-spacing: normal;background-color: rgb(255, 255, 255);"><code style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;display: -webkit-box;overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);cursor: pointer;border-radius: 5px;"><span style="font-size: 14px;">TARGET(CALL) {<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 运行时栈从栈底到栈顶的元素分别是：NULL, 函数, 参数1, 参数 2, ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 至于为啥会有一个 NULL，我们再看一下刚才的字节码指令就明白了</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 在 LOAD_NAME 将函数对象的指针压入运行时栈之前，先执行了 PUSH_NULL</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以栈底元素是 NULL，不过问题又来了，为啥要往栈里面压入一个 NULL 呢</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// PUSH_NULL 这个指令我们之前也见过，只不过当时没有解释</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 它是干嘛的，接下来你就会明白</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// oparg 表示给函数传递的参数的个数，所以 args 指向第一个参数</span><br style="cursor: pointer;"/>    PyObject **args = (stack_pointer - oparg);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 等价于 *(args - 1)，显然这是函数</span><br style="cursor: pointer;"/>    PyObject *callable = stack_pointer[-(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span> + oparg)];<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// *(args - 2) 毫无疑问就是栈底元素 NULL</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 但它却被赋值为 method，难道和方法有关吗？</span><br style="cursor: pointer;"/>    PyObject *method = stack_pointer[-(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span> + oparg)];<br style="cursor: pointer;"/>    PyObject *res;  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 返回值</span><br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 2653 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/bytecodes.c&#34;</span></span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果 method 不为 NULL，说明执行的不是普通的函数，而是方法</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所谓方法其实就是将函数和 self 绑定起来的结果</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> is_meth = method != <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>;<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> total_args = oparg;<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 总之现在我们明白为什么要压入一个 NULL 了，就是为了和方法调用保持统一</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果调用的是方法，那么栈里的元素就是：函数, self, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 方法是对函数和 self 的绑定，调用方法本质上还是在调用函数</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 只不过调用的时候，会自动传递 self，举个例子</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">/* <br style="cursor: pointer;"/>     * class A:<br style="cursor: pointer;"/>     *     def foo(self):<br style="cursor: pointer;"/>     *         pass<br style="cursor: pointer;"/>     *<br style="cursor: pointer;"/>     * a = A()<br style="cursor: pointer;"/>     */</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果是 A.foo，那么拿到的就是普通的函数</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 因为函数定义在类里面，所以 A.foo 也叫类的成员函数，但它依旧是一个普通的函数</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果是 a.foo，那么拿到的就是方法，它会将 A.foo 和实例对象 a 自身绑定起来</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 调用方法时会自动传递 self，所以 a.foo() 本质上就是 A.foo(a)</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (is_meth) {  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 当 is_meth 为真时</span><br style="cursor: pointer;"/>        callable = method;  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// method 才是要调用的 callable</span><br style="cursor: pointer;"/>        args--;  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 此时 self 变成了真正意义上的第一个参数，因此 args--</span><br style="cursor: pointer;"/>        total_args++;  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 参数个数加 1，因此 total_args++</span><br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 通过 PUSH_NULL，可以让函数和方法的调用对应同一个指令</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 当然，即使不考虑方法，提前 PUSH 一个 NULL 在逻辑上也是正确的</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 因为任何函数都有返回值，执行完之后要设置在栈顶的位置</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 而一开始 PUSH 的 NULL 正好为返回值预留了空间</span><br style="cursor: pointer;"/>    <br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果调用的函数，那么栈里的元素是：NULL, 函数, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果调用的方法，那么栈里的元素是：函数, self, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 但对于方法而言，栈里的元素还有一种情况：NULL, 方法, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 对于这种情况，要将方法里面的函数和 self 提取出来</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以当 is_meth 为 0，但 callable 的类型是 &lt;class &#39;method&#39;&gt; 时</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (!is_meth &amp;&amp; Py_TYPE(callable) == &amp;PyMethod_Type) {<br style="cursor: pointer;"/>        is_meth = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>;  <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将 is_meth 设置为 1</span><br style="cursor: pointer;"/>        args--;       <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// args 依旧向前移动一个位置</span><br style="cursor: pointer;"/>        total_args++; <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 参数总个数加 1</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取方法里面的实例对象</span><br style="cursor: pointer;"/>        PyObject *self = ((PyMethodObject *)callable)-&gt;im_self;<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// args 向前移动一个位置之后，它指向了目前方法所在的位置</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将该位置的值换成 self</span><br style="cursor: pointer;"/>        args[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>] = Py_NewRef(self);<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取方法里面的函数</span><br style="cursor: pointer;"/>        method = ((PyMethodObject *)callable)-&gt;im_func;<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将 args 的前一个位置的值设置成函数</span><br style="cursor: pointer;"/>        args[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>] = Py_NewRef(method);<br style="cursor: pointer;"/>        Py_DECREF(callable);<br style="cursor: pointer;"/>        callable = method;<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以之前栈里的元素是：NULL, 方法, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// args 之前也指向`参数1`，但在 args-- 之后，便指向了`方法`</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 等到将 args[0] 设置成 self，将 args[-1] 设置成函数之后</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 栈里的元素就变成了：函数, self, 参数1, 参数2, ...</span><br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 到这里为止，不管是调用函数还是调用方法，逻辑都变得统一了</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 此时变量 callable 指向实际要调用的函数</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// args 指向第一个参数，total_args 表示参数的个数</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> positional_args = total_args - KWNAMES_LEN();<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 函数在初始化时，它的 vectorcall 字段会被设置为 _PyFunction_Vectorcall</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 所以对于函数来讲，下面这个条件是成立的，因此可以被内联</span><br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (Py_TYPE(callable) == &amp;PyFunction_Type &amp;&amp;<br style="cursor: pointer;"/>        tstate-&gt;interp-&gt;eval_frame == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span> &amp;&amp;<br style="cursor: pointer;"/>        ((PyFunctionObject *)callable)-&gt;vectorcall == _PyFunction_Vectorcall)<br style="cursor: pointer;"/>    {<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 获取 co_flags</span><br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> code_flags = ((PyCodeObject*)PyFunction_GET_CODE(callable))-&gt;co_flags;<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 如果是函数的 PyCodeObject，那么 local 名字空间指定为 NULL</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 因为局部变量不是从 local 名字空间中加载的，而是静态访问的</span><br style="cursor: pointer;"/>        PyObject *locals = code_flags &amp; CO_OPTIMIZED ? <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span> : \<br style="cursor: pointer;"/>                Py_NewRef(PyFunction_GET_GLOBALS(callable));<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 在当前栈帧之上创建新的栈帧，初始化相关字段</span><br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 然后推入到虚拟机为其准备的 C Stack 中</span><br style="cursor: pointer;"/>        _PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit(<br style="cursor: pointer;"/>            tstate, (PyFunctionObject *)callable, locals,<br style="cursor: pointer;"/>            args, positional_args, kwnames<br style="cursor: pointer;"/>        );<br style="cursor: pointer;"/>        kwnames = <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>;<br style="cursor: pointer;"/>        <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 将运行时栈清空</span><br style="cursor: pointer;"/>        STACK_SHRINK(oparg + <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">2</span>);<br style="cursor: pointer;"/>        <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (new_frame == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) {<br style="cursor: pointer;"/>            <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> error;<br style="cursor: pointer;"/>        }<br style="cursor: pointer;"/>        JUMPBY(INLINE_CACHE_ENTRIES_CALL);<br style="cursor: pointer;"/>        frame-&gt;return_offset = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>;<br style="cursor: pointer;"/>        DISPATCH_INLINED(new_frame);<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 到这里 callable 不是一个普通的 Python 函数，但它支持 vector 协议</span><br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// 进行调用</span><br style="cursor: pointer;"/>    res = PyObject_Vectorcall(<br style="cursor: pointer;"/>        callable, args,<br style="cursor: pointer;"/>        positional_args | PY_VECTORCALL_ARGUMENTS_OFFSET,<br style="cursor: pointer;"/>        kwnames);<br style="cursor: pointer;"/>    <span style="color: rgb(92, 99, 112);font-style: italic;cursor: pointer;line-height: 26px;">// ...</span><br style="cursor: pointer;"/>    kwnames = <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>;<br style="cursor: pointer;"/>    assert((res != <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) ^ (_PyErr_Occurred(tstate) != <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>));<br style="cursor: pointer;"/>    Py_DECREF(callable);<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">for</span> (<span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">int</span> i = <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">0</span>; i &lt; total_args; i++) {<br style="cursor: pointer;"/>        Py_DECREF(args[i]);<br style="cursor: pointer;"/>    }<br style="cursor: pointer;"/>    <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">if</span> (res == <span style="color: rgb(86, 182, 194);cursor: pointer;line-height: 26px;">NULL</span>) { STACK_SHRINK(oparg); <span style="color: rgb(198, 120, 221);cursor: pointer;line-height: 26px;">goto</span> pop_2_error; }<br style="cursor: pointer;"/>    <span style="font-size: 14px;color: rgb(97, 174, 238);cursor: pointer;line-height: 26px;">#<span style="cursor: pointer;line-height: 26px;">line</span> 3790 <span style="color: rgb(152, 195, 121);cursor: pointer;line-height: 26px;">&#34;Python/generated_cases.c.h&#34;</span></span><br style="cursor: pointer;"/>    STACK_SHRINK(oparg);<br style="cursor: pointer;"/>    STACK_SHRINK(<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">1</span>);<br style="cursor: pointer;"/>    stack_pointer[<span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">-1</span>] = res;<br style="cursor: pointer;"/>    next_instr += <span style="color: rgb(209, 154, 102);cursor: pointer;line-height: 26px;">3</span>;<br style="cursor: pointer;"/>    CHECK_EVAL_BREAKER();<br style="cursor: pointer;"/>    DISPATCH();<br style="cursor: pointer;"/>}</span><br style="cursor: pointer;"/></code></pre><p><span style="font-size: 15px;">当调用函数时，会执行 _PyFunction_Vectorcall，否则执行 PyObject_Vectorcall。</span></p><p><span style="letter-spacing: 0.578px;text-align: left;font-size: 15px;">以上就是函数的调用逻辑，然后再补充一点，我们说 PyFrameObject 是根据 PyCodeObject 创建的，而 PyFunctionObject 也是根据 PyCodeObject 创建的，那么 PyFrameObject 和 PyFunctionObject 之间有啥关系呢？</span></p><p><span style="font-size: 15px;"><span style="letter-spacing: 0.578px;text-align: left;">如果把 PyCodeObject 比喻成</span><span style="color: rgb(0, 122, 170);">妹子</span><span style="letter-spacing: 0.578px;text-align: left;">的话，那么 PyFunctionObject 就是妹子的</span><span style="color: rgb(0, 122, 170);">备胎</span><span style="letter-spacing: 0.578px;text-align: left;">，PyFrameObject 就是妹子的</span><span style="color: rgb(0, 122, 170);">心上人</span><span style="letter-spacing: 0.578px;text-align: left;">。其实在栈帧中执行指令的时候，PyFunctionObject 的影响就已经消失了。</span></span></p><p style="margin-bottom: 0px;text-align: left;"><span style="font-size: 15px;">也就是说，最终是 PyFrameObject 对象和 PyCodeObject 对象两者如胶似漆，跟 PyFunctionObject 对象之间没有关系，所以 PyFunctionObject 辛苦一场，实际上是为别人做了嫁衣。PyFunctionObject 主要是对 PyCodeObject 和 global 名字空间的一种打包和运输方式。</span></p><p style="display: none;"><mp-style-type data-value="3"></mp-style-type></p>



<p><a href="2247531935">阅读原文</a></p>
<p><a href="https://wechat2rss.xlab.app/link-proxy/?k=77c4d8c2&amp;r=1&amp;u=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzg3NTczMDU2Mg%3D%3D%26mid%3D2247531935%26idx%3D1%26sn%3D0a539f3f01f03d5e918fd2f192e352b5%26subscene%3D0">跳转微信打开</a></p>
]]></content:encoded>
      <pubDate>Tue, 19 Nov 2024 10:29:00 +0800</pubDate>
    </item>
  </channel>
</rss>